org.modeshape.jcr.query.QueryResults Maven / Gradle / Ivy
/*
* ModeShape (http://www.modeshape.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modeshape.jcr.query;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import org.modeshape.common.annotation.Immutable;
import org.modeshape.common.collection.Problems;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.cache.CachedNode;
import org.modeshape.jcr.cache.CachedNodeSupplier;
import org.modeshape.jcr.query.NodeSequence.Batch;
import org.modeshape.jcr.query.model.Column;
/**
* The resulting output of a query.
*/
@Immutable
public interface QueryResults {
/**
* Get the description of the columns contained in these results.
*
* @return the column descriptions; never null
*/
public Columns getColumns();
/**
* Get the rows that make up these query results.
*
* @return the sequence of rows; never null
*/
public NodeSequence getRows();
/**
* Get the supplier with which a node can be found by key.
*
* @return the supplier of {@link CachedNode} instances; never null
*/
public CachedNodeSupplier getCachedNodes();
/**
* Get the number of rows in the results, if that information is available.
*
* @return the number of rows; may be equal to -1 if the number of rows is not known without significant overhead
*/
public long getRowCount();
/**
* Determine whether this results is known to be empty.
*
* @return the true if there are no results, or false if there is at least some rows or if the number of rows is not known
*/
public boolean isEmpty();
/**
* Get a description of the query plan, if requested.
*
* @return the query plan, or null if the plan was not requested
*/
public String getPlan();
/**
* Get the problems encountered during execution.
*
* @return the problems; never null but possibly empty
*/
public Problems getProblems();
/**
* Return true if there is at least one error recorded in the {@link #getProblems() problems}.
*
* @return true if there is one or more errors associated with the query, or false otherwise
*/
public boolean hasErrors();
/**
* Return true if there is at least one warning recorded in the {@link #getProblems() problems}.
*
* @return true if there is one or more warnings associated with the query, or false otherwise
*/
public boolean hasWarnings();
/**
* Get the statistics that describe the time metrics for this query.
*
* @return the statistics; never null
*/
public Statistics getStatistics();
/**
* Definition of the columns that are available in the results.
*/
@Immutable
public interface Columns extends Serializable, Iterable {
/**
* Get the columns.
*
* @return the immutable list of columns; never null
*/
public List getColumns();
/**
* Get the names of the columns.
*
* @return the immutable list of column names, with size equal to the number of {@link #getColumns() columns}; never null
*/
public List getColumnNames();
/**
* Get the type name for each column.
*
* @return the immutable list of type names, with size equal to the number of {@link #getColumns() columns}; never null
*/
public List getColumnTypes();
/**
* Get the type of the column given the name of the selector and the property name from where the column should be
* obtained.
*
* @param selectorName the selector name
* @param propertyName the name of the property
* @return the type for the named column
* @throws NoSuchElementException if the selector name or the property name are invalid
*/
public String getColumnTypeForProperty( String selectorName,
String propertyName );
/**
* Get the index of the nodes for this selector in each of the {@link Batch node sequence batches}.
*
* @param selectorName the selector name
* @return the index of the node for the named selector, or negative if the selector is not known
*/
public int getSelectorIndex( String selectorName );
/**
* Get the names of the selectors that are associated with these results.
*
* @return the immutable list of selector names; never null
*/
public List getSelectorNames();
/**
* Get the name of the property that corresponds to the named column in each tuple.
*
* @param columnName the column name
* @return the property name, or the supplied column name if there is no property for it
*/
public String getPropertyNameForColumnName( String columnName );
/**
* Get the name of the selector that produced the column with the given name.
*
* @param columnName the column name
* @return the selector name
* @throws NoSuchElementException if the column name is invalid or doesn't match an existing column
*/
public String getSelectorNameForColumnName( String columnName );
/**
* Determine whether these results include full-text search scores.
*
* @return true if the full-text search scores are included in the results, or false otherwise
*/
public boolean hasFullTextSearchScores();
/**
* Return a new Columns that is a superset combination of both this Columns and the supplied Columns.
*
* @param other the other columns; may not be null
* @return a new Columns instance that is a superset of {@code this} and {@code other}; never null
*/
public Columns with( Columns other );
}
@Immutable
public static class Statistics implements Comparable, Serializable {
private static final long serialVersionUID = 1L;
protected static final Statistics EMPTY_STATISTICS = new Statistics();
private final long planningNanos;
private final long optimizationNanos;
private final long resultFormulationNanos;
private final long executionNanos;
public Statistics() {
this(0L, 0L, 0L, 0L);
}
public Statistics( long planningNanos ) {
this(planningNanos, 0L, 0L, 0L);
}
public Statistics( long planningNanos,
long optimizationNanos,
long resultFormulationNanos,
long executionNanos ) {
this.planningNanos = planningNanos;
this.optimizationNanos = optimizationNanos;
this.resultFormulationNanos = resultFormulationNanos;
this.executionNanos = executionNanos;
}
/**
* Get the time required to come up with the canonical plan.
*
* @param unit the time unit that should be used
* @return the time to plan, in the desired units
* @throws IllegalArgumentException if the unit is null
*/
public long getPlanningTime( TimeUnit unit ) {
CheckArg.isNotNull(unit, "unit");
return unit.convert(planningNanos, TimeUnit.NANOSECONDS);
}
/**
* Get the time required to determine or select a (more) optimal plan.
*
* @param unit the time unit that should be used
* @return the time to determine an optimal plan, in the desired units
* @throws IllegalArgumentException if the unit is null
*/
public long getOptimizationTime( TimeUnit unit ) {
CheckArg.isNotNull(unit, "unit");
return unit.convert(optimizationNanos, TimeUnit.NANOSECONDS);
}
/**
* Get the time required to formulate the structure of the results.
*
* @param unit the time unit that should be used
* @return the time to formulate the results, in the desired units
* @throws IllegalArgumentException if the unit is null
*/
public long getResultFormulationTime( TimeUnit unit ) {
CheckArg.isNotNull(unit, "unit");
return unit.convert(resultFormulationNanos, TimeUnit.NANOSECONDS);
}
/**
* Get the time required to execute the query.
*
* @param unit the time unit that should be used
* @return the time to execute the query, in the desired units
* @throws IllegalArgumentException if the unit is null
*/
public long getExecutionTime( TimeUnit unit ) {
return unit.convert(executionNanos, TimeUnit.NANOSECONDS);
}
/**
* Get the time required to execute the query.
*
* @param unit the time unit that should be used
* @return the time to execute the query, in the desired units
* @throws IllegalArgumentException if the unit is null
*/
public long getTotalTime( TimeUnit unit ) {
return unit.convert(totalTime(), TimeUnit.NANOSECONDS);
}
protected long totalTime() {
return planningNanos + optimizationNanos + resultFormulationNanos + executionNanos;
}
/**
* Create a new statistics object that has the supplied planning time.
*
* @param planningNanos the number of nanoseconds required by planning
* @return the new statistics object; never null
* @throws IllegalArgumentException if the time value is negative
*/
public Statistics withPlanningTime( long planningNanos ) {
CheckArg.isNonNegative(planningNanos, "planningNanos");
return new Statistics(planningNanos, optimizationNanos, resultFormulationNanos, executionNanos);
}
/**
* Create a new statistics object that has the supplied optimization time.
*
* @param optimizationNanos the number of nanoseconds required by optimization
* @return the new statistics object; never null
* @throws IllegalArgumentException if the time value is negative
*/
public Statistics withOptimizationTime( long optimizationNanos ) {
CheckArg.isNonNegative(optimizationNanos, "optimizationNanos");
return new Statistics(planningNanos, optimizationNanos, resultFormulationNanos, executionNanos);
}
/**
* Create a new statistics object that has the supplied result formulation time.
*
* @param resultFormulationNanos the number of nanoseconds required by result formulation
* @return the new statistics object; never null
* @throws IllegalArgumentException if the time value is negative
*/
public Statistics withResultsFormulationTime( long resultFormulationNanos ) {
CheckArg.isNonNegative(resultFormulationNanos, "resultFormulationNanos");
return new Statistics(planningNanos, optimizationNanos, resultFormulationNanos, executionNanos);
}
/**
* Create a new statistics object that has the supplied execution time.
*
* @param executionNanos the number of nanoseconds required to execute the query
* @return the new statistics object; never null
* @throws IllegalArgumentException if the time value is negative
*/
public Statistics withExecutionTime( long executionNanos ) {
CheckArg.isNonNegative(executionNanos, "executionNanos");
return new Statistics(planningNanos, optimizationNanos, resultFormulationNanos, executionNanos);
}
/**
* Create a new statistics object that has the supplied planning time.
*
* @param planning the time required to plan the query
* @param unit the time unit
* @return the new statistics object; never null
* @throws IllegalArgumentException if the unit is null or if the time value is negative
*/
public Statistics withPlanningTime( long planning,
TimeUnit unit ) {
CheckArg.isNonNegative(planning, "planning");
CheckArg.isNotNull(unit, "unit");
long planningNanos = TimeUnit.NANOSECONDS.convert(planning, unit);
return new Statistics(planningNanos, optimizationNanos, resultFormulationNanos, executionNanos);
}
/**
* Create a new statistics object that has the supplied optimization time.
*
* @param optimization the time required by optimization
* @param unit the time unit
* @return the new statistics object; never null
* @throws IllegalArgumentException if the unit is null or if the time value is negative
*/
public Statistics withOptimizationTime( long optimization,
TimeUnit unit ) {
CheckArg.isNonNegative(optimization, "optimization");
CheckArg.isNotNull(unit, "unit");
long optimizationNanos = TimeUnit.NANOSECONDS.convert(optimization, unit);
return new Statistics(planningNanos, optimizationNanos, resultFormulationNanos, executionNanos);
}
/**
* Create a new statistics object that has the supplied result formulation time.
*
* @param resultFormulation the time required to formulate the results
* @param unit the time unit
* @return the new statistics object; never null
* @throws IllegalArgumentException if the unit is null or if the time value is negative
*/
public Statistics withResultsFormulationTime( long resultFormulation,
TimeUnit unit ) {
CheckArg.isNonNegative(resultFormulation, "resultFormulation");
CheckArg.isNotNull(unit, "unit");
long resultFormulationNanos = TimeUnit.NANOSECONDS.convert(resultFormulation, unit);
return new Statistics(planningNanos, optimizationNanos, resultFormulationNanos, executionNanos);
}
/**
* Create a new statistics object that has the supplied execution time.
*
* @param execution the time required to execute the query
* @param unit the time unit
* @return the new statistics object; never null
* @throws IllegalArgumentException if the unit is null or if the time value is negative
*/
public Statistics withExecutionTime( long execution,
TimeUnit unit ) {
CheckArg.isNonNegative(execution, "execution");
CheckArg.isNotNull(unit, "unit");
long executionNanos = TimeUnit.NANOSECONDS.convert(execution, unit);
return new Statistics(planningNanos, optimizationNanos, resultFormulationNanos, executionNanos);
}
@Override
public int compareTo( Statistics that ) {
if (that == this) return 0;
long diff = this.totalTime() - that.totalTime();
if (diff < 0) return -1;
if (diff > 0) return 1;
return 0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
readable(totalTime(), sb);
boolean first = false;
if (planningNanos != 0L) {
sb.append(" (plan=");
readable(planningNanos, sb);
first = false;
}
if (optimizationNanos != 0L) {
if (first) {
first = false;
sb.append(" (");
} else {
sb.append(", ");
}
sb.append("optim=");
readable(optimizationNanos, sb);
}
if (resultFormulationNanos != 0L) {
if (first) {
first = false;
sb.append(" (");
} else {
sb.append(", ");
}
sb.append("resultform=");
readable(resultFormulationNanos, sb);
}
if (executionNanos != 0L) {
if (first) {
first = false;
sb.append(" (");
} else {
sb.append(", ");
}
sb.append("exec=");
readable(executionNanos, sb);
}
if (!first) sb.append(')');
return sb.toString();
}
protected void readable( long nanos,
StringBuilder sb ) {
// 3210987654321
// XXXXXXXXXXXXX nanos
// XXXXXXXXXX micros
// XXXXXXX millis
// XXXX seconds
if (nanos < 1000) {
sb.append(nanos).append(" ns");
} else if (nanos < 1000000) {
double value = nanos / 1000d;
sb.append(FORMATTER.get().format(value)).append(" usec");
} else if (nanos < 1000000000) {
double value = nanos / 1000000d;
sb.append(FORMATTER.get().format(value)).append(" ms");
} else {
double value = nanos / 1000000000d;
sb.append(FORMATTER.get().format(value)).append(" sec");
}
}
}
static ThreadLocal FORMATTER = new ThreadLocal() {
@Override
protected synchronized DecimalFormat initialValue() {
return new DecimalFormat("###,###,##0.0##");
}
};
}