package org.xenei.jdbc4sparql.sparql.items; import java.sql.SQLDataException; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xenei.jdbc4sparql.iface.Column; import org.xenei.jdbc4sparql.iface.Table; import org.xenei.jdbc4sparql.iface.name.ColumnName; import org.xenei.jdbc4sparql.iface.name.NameSegments; import org.xenei.jdbc4sparql.iface.name.TableName; import org.xenei.jdbc4sparql.sparql.CheckTypeF; import org.xenei.jdbc4sparql.sparql.ForceTypeF; import org.xenei.jdbc4sparql.sparql.QueryInfoSet; import org.xenei.jdbc4sparql.sparql.SparqlQueryBuilder; import org.xenei.jdbc4sparql.sparql.parser.SparqlParser; import com.hp.hpl.jena.query.Query; import com.hp.hpl.jena.query.QueryException; import com.hp.hpl.jena.sparql.core.Var; import com.hp.hpl.jena.sparql.expr.E_Function; import com.hp.hpl.jena.sparql.expr.E_LogicalAnd; import com.hp.hpl.jena.sparql.expr.Expr; import com.hp.hpl.jena.sparql.lang.sparql_11.ParseException; import com.hp.hpl.jena.sparql.syntax.Element; import com.hp.hpl.jena.sparql.syntax.ElementBind; import com.hp.hpl.jena.sparql.syntax.ElementFilter; import com.hp.hpl.jena.sparql.syntax.ElementGroup; import com.hp.hpl.jena.sparql.syntax.ElementOptional; /** * Contains the information for the table in the query. If a defined table is * used multiple times with different aliases there will be multiple * QueryTableInfo instances with the same RdfTable but different names. */ public class QueryTableInfo extends QueryItemInfo<Table, TableName> { private final ElementGroup eg; private final ElementGroup egWrapper; private final QueryInfoSet infoSet; // list of bindings to add at the end of the query private final Set<QueryColumnInfo> dataFilterList; private final Set<Element> joinElementList; private static Logger LOG = LoggerFactory.getLogger(QueryTableInfo.class); /** * Verify that the table object is not null. * * @param table * The table to check * @return the table param * @throws IllegalArgumentException */ private static Table checkTable(final Table table) throws IllegalArgumentException { if (table == null) { throw new IllegalArgumentException("table may not be null"); } return table; } /** * Create QueryTableInfo. * * @param infoSet * The QueryInfoSet to add the table to. * @param queryElementGroup * The queryElementGroup that the table info will be added to. * @param table * The table that this QueyrTableInfo wraps. * @param optional * true if this table is optional. * @throws IllegalArgumentException */ public QueryTableInfo(final QueryInfoSet infoSet, final ElementGroup queryElementGroup, final Table table, final boolean optional) throws IllegalArgumentException { this(infoSet, queryElementGroup, table, checkTable(table).getName(), optional); } /** * Constructor * * @param infoSet * The QueryInfoSet to add the table to. * @param queryElementGroup * The queryElementGroup that the table info will be added to. * @param table * The table that this QueyrTableInfo wraps. * @param alias * The table name alias. * @param optional * true if this table is optional. * @throws IllegalArgumentException */ public QueryTableInfo(final QueryInfoSet infoSet, final ElementGroup queryElementGroup, final Table table, final TableName alias, final boolean optional) throws IllegalArgumentException { super(table, alias, optional); this.infoSet = infoSet; // this.table = checkTable(table); this.egWrapper = new ElementGroup(); this.eg = new ElementGroup(); egWrapper.addElement(eg); this.dataFilterList = new HashSet<QueryColumnInfo>(); this.joinElementList = new HashSet<Element>(); if (queryElementGroup == null) { if (LOG.isDebugEnabled()) { QueryTableInfo.LOG.debug("marking {} as not in query.", this); } } else { if (optional) { if (LOG.isDebugEnabled()) { QueryTableInfo.LOG.debug("marking {} as optional.", this); } queryElementGroup.addElement(new ElementOptional(egWrapper)); } else { if (LOG.isDebugEnabled()) { QueryTableInfo.LOG.debug("marking {} as required.", this); } queryElementGroup.addElement(egWrapper); } } } /** * Set the namesegments. The name segments will have the table set true and * the column name false. */ @Override public void setSegments(final NameSegments usedSegments) { final NameSegments ourSegments = NameSegments.getInstance( usedSegments.isCatalog(), usedSegments.isSchema(), true, false); super.setSegments(ourSegments); final List<QueryColumnInfo> lst = infoSet.listColumns(getName()); for (final QueryColumnInfo col : lst) { col.setSegments(usedSegments); } } /** * Adds the column to the columns defined in the query. * * @param column * The column to add. * @return The variable Node for the column. */ public QueryColumnInfo addColumnToQuery(final Column column) { return addColumnToQuery(column, column.getName(), column.isOptional()); } /** * Adds the column to the columns defined in the query. Result will be * optional if the Column is optional. * * @param column * The column to add * @param cName * The name for the column in the query. * @param optional * if true the column is optional. * @return The variable Node for the column. */ public QueryColumnInfo addColumnToQuery(final Column column, final ColumnName cName, final boolean optional) { QueryColumnInfo columnInfo = new QueryColumnInfo(column, getName() .getColumnName(column.getName().getShortName()), optional); // see if this is an alias if (!column.getName().getGUID().equals(cName.getGUID())) { columnInfo = columnInfo.createAlias(cName); } if (LOG.isDebugEnabled()) { QueryTableInfo.LOG.debug("Adding column: {} as {} ({})", columnInfo, columnInfo.getGUIDVar(), columnInfo.isOptional() ? "optional" : "required"); } // only add to group if it was not already in the column list. if (infoSet.addColumn(columnInfo) && column.hasQuerySegments()) { if (!columnInfo.isAlias()) { Element el = getQuerySegments(columnInfo.getColumn(), getGUIDVar(), columnInfo.getGUIDVar()); if ((el instanceof ElementGroup) && (((ElementGroup) el).getElements().size() == 1)) { final ElementGroup subGroup = (ElementGroup) el; el = subGroup.getElements().get(0); } if (optional) { eg.addElement(new ElementOptional(el)); } else { eg.addElement(el); } } } return columnInfo; } /** * Adds the column to the columns defined in the query. Result will be * optional if the Column is optional. * * @param column * The column to add * @param cName * The name for the column in the query. * @return The variable Node for the column. */ public QueryColumnInfo addColumnToQuery(final Column column, final ColumnName cName) { return addColumnToQuery(column, cName, column.isOptional()); } /** * Adds the column to the columns defined in the query. * * @param column * The column to add * @param optional * if True the column is optional * @return The variable Node for the column. */ public QueryColumnInfo addColumnToQuery(final Column column, final boolean optional) { return addColumnToQuery(column, column.getName(), optional); } /** * Add the column to the datafilter. * * @param columnInfo * The columninfo to add. */ public void addDataFilter(final QueryColumnInfo columnInfo) { if (columnInfo == null) { throw new IllegalArgumentException("ColumnInfo may not be null"); } dataFilterList.add(columnInfo); } /** * Add a filter to this table. * * @param expr * The expression to add. */ public void addFilter(final Expr expr) { if (LOG.isDebugEnabled()) { QueryTableInfo.LOG.debug("Adding filter: {}", expr); } egWrapper.addElementFilter(new ElementFilter(expr)); } /** * Add a filter to this table. * * @param expr * The expression to add. */ public void addJoinElement(final Expr expr) { if (LOG.isDebugEnabled()) { QueryTableInfo.LOG.debug("Adding join element: {}", expr); } joinElementList.add(new ElementFilter(expr)); } public Set<Element> getJoinElements() { return joinElementList; } /** * Adds the defined column and table definition to the query. * <p> * Columns that are in a SQL "USING" clause are added to the datafilter * list. * </p> * * @param columnsInUsing * columns that are in a SQL "Using" clause. * @throws SQLDataException */ public void addDefinedColumns(final List<String> columnsInUsing) throws SQLDataException { if (LOG.isDebugEnabled()) { QueryTableInfo.LOG .debug("adding defined columns for {}", getName()); } // add the table definition if (StringUtils.isNotBlank(getTable().getQuerySegmentFmt())) { final String queryFmt = new StringBuilder("{ ") .append(getTable().getQuerySegmentFmt()).append("}") .toString(); final String queryStr = String.format(queryFmt, getGUIDVar()); try { final ElementGroup subGroup = (ElementGroup) SparqlParser.Util .parse(queryStr); for (final Element subEl : subGroup.getElements()) { eg.addElement(subEl); } } catch (final ParseException e) { throw new IllegalStateException(getTable().getName() + " query segment " + queryStr, e); } catch (final QueryException e) { throw new IllegalStateException(getTable().getName() + " query segment " + queryStr, e); } } // add the column definitions for (final Iterator<Column> colIter = getTable().getColumns(); colIter .hasNext();) { final Column column = colIter.next(); if (columnsInUsing.contains(column.getName().getShortName())) { final QueryColumnInfo columnInfo = getColumn(column.getName()); if (columnInfo == null) { throw new SQLDataException(String.format( "Column in USING (%s) is not found in table %s", column.getName().getShortName(), this.getName())); } addDataFilter(columnInfo); } else { addColumnToQuery(column); } } if (LOG.isDebugEnabled()) { QueryTableInfo.LOG.debug("finished adding defined columns for {}", getName()); } } /** * Adds all of the table columns to the query. * * @param query * The query to add the columns to * */ public void addTableColumns(final Query query, final List<String> columnsInUsing) { final Iterator<Column> iter = getTable().getColumns(); while (iter.hasNext()) { final Column column = iter.next(); QueryColumnInfo columnInfo = null; if (columnsInUsing.contains(column.getName().getShortName())) { columnInfo = infoSet.getColumn(column.getName()); } else { columnInfo = addColumnToQuery(column); } final Var v = columnInfo.getVar(); if (!query.getResultVars().contains(v.toString())) { addDataFilter(columnInfo); query.addResultVar(v); } } } /** * Add the filters for the columns in the table. * * @param infoSet * @throws SQLDataException */ public void addQueryFilters(final QueryInfoSet infoSet) throws SQLDataException { final List<QueryColumnInfo> columnInfoList = new ArrayList<QueryColumnInfo>(); final Iterator<Column> iter = getTable().getColumns(); while (iter.hasNext()) { final QueryColumnInfo columnInfo = infoSet.findColumn(iter.next()); if (columnInfo != null) { columnInfoList.add(columnInfo); } } addTypeFilters(infoSet, columnInfoList, dataFilterList, joinElementList, eg, egWrapper); } /** * Add the type and data filters for the table. * * Type filters are columns that need to be filtered to ensure that the data * type in the graph matches or can be converted to the data type for JDBC * driver. * * Data filters are columns that need to be filtered to ensure that the data * is not null. * * @param typeFilterList * The list of QueryColumnInfos to be added to the type filter. * @param dataFilterList * The list of QueryColumnInfos to be added to the data filter. * @param filterGroup * The ElementGroup for the data filters. * @param typeGroup * The ElementGroup for the type filters. * @throws SQLDataException */ public static void addTypeFilters(final QueryInfoSet infoSet, final Collection<QueryColumnInfo> typeFilterList, final Collection<QueryColumnInfo> dataFilterList, final Collection<Element> joinFilterList, final ElementGroup filterGroup, final ElementGroup typeGroup) throws SQLDataException { Expr expr = null; for (final QueryColumnInfo columnInfo : typeFilterList) { final E_Function f = CheckTypeF.getFunction(columnInfo); if (LOG.isDebugEnabled()) { QueryTableInfo.LOG.debug("Adding filter: {} ({})", f, columnInfo); } if (expr == null) { expr = f; } else { expr = new E_LogicalAnd(expr, f); } } if (expr != null) { filterGroup.addElementFilter(new ElementFilter(expr)); } for (final QueryColumnInfo columnInfo : dataFilterList) { final ElementBind bind = ForceTypeF.getBinding(columnInfo); if (LOG.isDebugEnabled()) { QueryTableInfo.LOG.debug("Adding binding: {}", bind); } typeGroup.addElement(bind); } for (final Element element : joinFilterList ) { typeGroup.addElement( element ); } } /** * Returns the column or null if not found. * * Columns may will have the optional status of the base column. * * @param name * The name of the column to look for. * @return QueryColumnInfo for the column */ public QueryColumnInfo getColumn(final ColumnName cName) { return getColumn(cName, true); } /** * Returns the column or null if not found * * @param cName * The name of the column to look for. * @param optional * If false then column is required, if true then the * isOptional flag of the base column defines value * @return The QueryColumnInfo for the column. * @throws SQLException */ public QueryColumnInfo getColumn(final ColumnName cName, final boolean optional) { // columns not in tables are not found. if (!(cName.getTableName().matches(this.getName()))) { return null; } QueryColumnInfo retval = infoSet.findColumn(cName); if (retval == null) { // we have to check for the case where the column has the schema or // table def and the // infoSet does not. final Column col = getTable().getColumn(cName.getColumn()); if (col != null) { final boolean opt = optional ? col.isOptional() : SparqlQueryBuilder.REQUIRED; retval = addColumnToQuery(col, cName, opt); } } if ((retval != null) && retval.getName().getTableName().matches(this.getName())) { if (retval.isOptional() && !optional) { retval.setOptional(false); } return retval; } return null; } /** * Get the element query segments for the * * @param column * The column to add query segments for. * @param tableVar * The table variable. * @param columnVar * The column variable. * @return The Element */ private Element getQuerySegments(final Column column, final Var tableVar, final Var columnVar) { final String fmt = "{" + column.getQuerySegmentFmt() + "}"; try { return SparqlParser.Util.parse(String.format(fmt, tableVar, columnVar)); } catch (final ParseException e) { throw new IllegalStateException(column.getName() + " query segment " + fmt, e); } catch (final QueryException e) { throw new IllegalStateException(column.getName() + " query segment " + fmt, e); } } /** * Get the SQL name for the table. * * @return The name. */ public String getSQLName() { return getName().getDBName(); } /** * Get the table this QueryTableInfo wraps. * * @return The innter table. */ public Table getTable() { return getBaseObject(); } /** * Adds the variable for aliasTableInfo as the value of the tableColumnInfo * property. Optional state is determined from the optional state of the * tableColumnInfo. * * @param tableColumnInfo * The column to alias * @param aliasName * The name of the alias column * @throws IllegalArgumentException */ public void setEquals(final QueryColumnInfo tableColumnInfo, final ColumnName aliasName) { final QueryColumnInfo ci = infoSet.getColumn(aliasName); if (ci != null) { setEquals(tableColumnInfo, ci); } else { throw new IllegalArgumentException(String.format( "%s is not a Query Column", aliasName)); } } /** * Adds the variable for aliasTableInfo as the value of the tableColumnInfo * property. Optional state is determined from the optional state of the * tableColumnInfo. * * @param tableColumnInfo * The column to alias * @param aliasTableInfo * The alias column */ public void setEquals(final QueryColumnInfo tableColumnInfo, final QueryColumnInfo aliasTableInfo) { setEquals(tableColumnInfo, aliasTableInfo, tableColumnInfo.isOptional()); } /** * Adds the variable for aliasTableInfo as the value of the tableColumnInfo * property. * * @param tableColumnInfo * The column to alias * @param aliasColumnInfo * The alias column * @param optional * determines if the entry should be optional. */ public void setEquals(final QueryColumnInfo tableColumnInfo, final QueryColumnInfo aliasColumnInfo, final boolean optional) { if (!infoSet.listColumns(getName().getColumnName(null)).contains( tableColumnInfo)) { throw new IllegalStateException(String.format( SparqlQueryBuilder.NOT_FOUND_IN_, tableColumnInfo.getName(), getName())); } if (!tableColumnInfo.getColumn().hasQuerySegments()) { throw new IllegalArgumentException(String.format( "%s may not be aliased", tableColumnInfo.getName())); } if (optional) { eg.addElement(new ElementOptional(getQuerySegments( tableColumnInfo.getColumn(), getVar(), aliasColumnInfo.getVar()))); } else { eg.addElement(new ElementOptional(getQuerySegments( tableColumnInfo.getColumn(), getVar(), aliasColumnInfo.getVar()))); } } @Override public String toString() { return String.format("QueryTableInfo[%s(%s)]", getTable().getSQLName(), getName()); } }