This proposal defines extension functions and data models to enable Faceted navigation/search support in XQuery.

Status of this document

This document is in an initial submission stage. Comments are welcomed at [email protected] mailing list (archive).

Introduction

Faceted search has proven to be enormously popular in the real world applications. Faceted search allows user to navigate and access information via a structured facet classification system. Combined with full text search, it provides user with enormous power and flexibility to discover information.

This proposal defines a standardized approach to support the Faceted search in XQuery. It has been designed to be compatible with XPath 3.0, and is intended to be used in conjunction with XQuery and XPath Full Text 3.0.

Namespace conventions

The module defined by this document defines functions and elements in the namespace http://expath.org/ns/facet. In this document, the facet prefix is bound to this namespace URI.

Facet terminologies

The XQuery Extension

The faceted search support consists of the definitions of the facet data models, and the XQuery functions that manipulate the data models to perform facet aggregation and drills. The following sections contain the detailed specification of the data models and the XQuery functions. The Use Cases section contains the examples demonstrating the application of this facet proposal with some sample data.

Schema

Below is the RelaxNG Compact grammar for the facet data models:

Example:

element facet

element key

  • attribute value : The facet value

  • attribute count : Number of occurrences counted for this facet value in the results

  • attribute type : Facet value data type, always one of xs:anyAtomicType. Optional attribute: when not specified, the default value is "xs:string".

  • AnyElement* : Any elements that do not belong to the facet namespace, for customization.

  • element facet* : Optional nested facets, for supporting hierarchical facets.

element facets

element facet-definition

element order-by

  • attribute direction : One of "ascending" or "descending". See OrderModifier

  • attribute empty : One of "greatest" or "least". See OrderModifier

  • content is one of

    • "value" : Order by /facet/key/@value

    • "count" : Order by /facet/key/@count, as xs:integer

  • When ordering by "value", where the facet values are of xs:string type, and attribute collation is specified in the group-by element, implementation must order the string values using the specified collation.

  • If order-by is not specified, then implementation must by default order by "count", with direction "descending".

element group-by

  • attribute function : An optional string containing the QName of a function that returns customized facet values

  • attribute collation : An optional string defined as URILiteral. Collation is used to determine the equality of facet values of xs:string type. See group-by-clause for the detailed semantics of this attribute. If not specified, the default collation is used.

  • attribute type : An optional string containing a xs:QName plus an optional OccurrenceIndicator. xs:QName must refer to a xs:anyAtomicType. When specified, strict type and cardinality checks are enforced on the facet values before they are counted. See group-by-clause for the detailed semantics of this attribute.

  • element sub-path

    • As defined by selector-XPath, the sub-path is relative to each item in the results sequence passed to the function "facet:count".

    • If attribute function is provided, then more than 1 sub-path may be specified , otherwise only 1 sub-path is allowed

  • AnyElement* : For customization.

The facet value grouping rules to obtain and count the facet values are mostly identical to the rules specified by group-by-clause. More specifically:

  1. For every item of the results sequence, the sub-path expressions are evaluated with the item as the context item.

  2. The results are then atomized to a sequence of zero or more atomic values.

  3. Apply the group-by function if specified.

    • If no group-by function exists, the atomized results from step 2 are the facet values.

    • If group-by function exists, the results from step 2 are passed to the group-by function. The returned result from the group-by function are the facet values.

  4. If attribute type is specified, strong type and cardinality checks are performed on the facet values. Error err:XPTY0004 should be raised if check fails.

  5. Facet values are then counted using the same equality rule as defined by group-by-clause, using the collation attribute when specified.

There exists a key difference between the grouping-key defined in group-by-clause and the facet value. The following rule for "grouping-key" does not apply to facet-value:

For facet, it's perfectly logical to have a result item to be counted towards multiple facet values or none at all, thus there may exist zero or more facet values per result item.

For conciseness, the following examples show the equivalent group-by clauses for some group-by elements, assuming there is exactly one facet value (grouping key) per result item.

Example 1:

Example 2:

Example 3:

Example 4:

The function definitions

facet:count

SIGNATURE

PROPERTIES

This function is: deterministic, context-independent, focus-independent

RULES

Given a result sequence, and a sequence of facet definitions, count the facet-values for each facet defined by the facet definition(s).

facet:drill

SIGNATURE

PROPERTIES

This function is: deterministic, context-independent, focus-independent

RULES

Given a result sequence, a facet definition, and a selected facet value contained in the facet element, return the results that match the selected facet value. This function can be used by both drill-down and drill-sideway queries.

In the case of hierarchical facets, the $selected-facet must have compatible hierarchical structure as $facet-definition. This should be true by default if an application constructed $selected_facet from the facets element returned by facet:count function using the same $facet-definition). See use cases for more details.

group-by function

SIGNATURE

PROPERTIES

This function is: deterministic, context-independent, focus-independent

RULES

The group-by function is supplied by the application, called by both facet:count and facet:drill.

The group-by function is a function that generates facet values from the original values. Each item in the returned sequence is a facet value that must be counted by facet:count, or compared against by facet:drill. An empty return sequence is also allowed.

As facet-definition's group-by element may define multiple sub-path child elements, the group-by function has arity ranging from 2 to infinity. The atomized sub-path values are passed to the group-by function in the same order as defined in the group-by element.

Element facet-definition is also passed to the group-by function to allow an application to pass customized parameters to the group-by function.

In the case of hierarchical facet definition, facet:count and facet:drill must pass in the matching facet-definition element in the hierarchical structure to the group-by function. For example:

Use cases

For the use cases, we use the sample "employee" data in Appendix B. Here is what one employee element looks like:

Case 1: Simple facet based on existing attribute

The XQuery using this facet proposal:

The equivalent XQuery using group-by-clause, since employee belongs to one and only one organization:

Expected result:

Case 2: Simple customized facet based on group-by function

The XQuery using this facet proposal:

The equivalent XQuery using group-by-clause:

Expected result:

Case 3: Counting facets when the grouping key consists of more than 1 value

The XQuery using this facet proposal:

There is no equivalent XQuery using group-by-clause, because skill is a repeatable element. Following XQuery will throw err:XPTY0004.

Expected result:

Case 4: Counting facets with group-by function strict type checking, and order by value with a collation

The XQuery using this facet proposal:

The equivalent XQuery using group-by-clause:

Expected result:

Case 5: Hierarchical facet

The XQuery using this facet proposal:

Expected result:

The application displays:

The application then constructs following facet element, based on user's selection:

The application then calls facet:drill, which is able to map the selected facet element to the facet-definition, and effectively applies following XQuery filter expression to the result set:

Case 6: Hierarchical facet and drill sideway

Continuing from previous case, if the application allows user to simultaneously select multiple skills:

The application then constructs following two facet elements, based on user's selection:

And constructs the following XQuery:

Which is effectively the same as:

Issues list

Issue 1. Use annotations to associate a grouping function to a facet-definition

As an alternative to specifying a function QName in /facet-definition/group-by/@function, annotation may be used to associate a grouping function to a facet-definition.

Example:

In the example above, annotation "age-range" is used to associated the facet-definition to the function local:age-range.

Annotation introduces an additional level of indirection to link a facet-definition to a group-by function. The indirection does not appear to improve or simplify the API. Thus currently we chose the straight forward function QName association via attribute.

Issue 2. Facet drill optimization and customizations

When facet:drill encounters a facet-definition that defines a group-by function, it's effectively filtering results using the following XQuery expression:

One main draw back of the above filtering implementation is that it voids any indexing optimization. To illustrate this, lets use a common numeric range facet as an example.

Suppose we've customized the following age range facet based on the same employee data in the use-cases above:

Default facet:filter implementation translates to the following equivalent XQuery expression:

Application may choose to perform facet drill by directly constructing following XQuery expression:

The above XQuery constraint expression can be optimized by the engine to use the index, which is clearly faster than using the default facet:drill implementation.

Another example is the 'dynamic-age-range' facet, where the age range is determined by the minimum and the maximum age values in the result set. In this case the result set must be pre-scanned before facet:count in order to determine the dynamic range, and the range information must be persisted into facet element to make facet drill query possible later on. These types of dynamic facets will require an application to implement customization, in addition to just using the standard methods/approaches described in this proposal.

To completely support all possible real world facet requirements may be too complex for this proposal. For this reason we've decided to make facet and facet-definition elements extensible by allowing the inclusion of non facet namespace elements. The implementation will then be able to choose to offer full support for above mentioned optimization and customizations via extensions.

Sample data for the use cases

The complete sample data used by the use cases, presented as an XQuery.