Skip to content
577 changes: 568 additions & 9 deletions exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g

Large diffs are not rendered by default.

622 changes: 622 additions & 0 deletions exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions exist-core/src/main/java/org/exist/xquery/ErrorCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,19 @@ public class ErrorCodes {

public static final ErrorCode XTSE0165 = new W3CErrorCode("XTSE0165","It is a static error if the processor is not able to retrieve the resource identified by the URI reference [ in the href attribute of xsl:include or xsl:import] , or if the resource that is retrieved does not contain a stylesheet module conforming to this specification.");

// W3C XQuery and XPath Full Text 3.0 error codes
public static final ErrorCode FTST0001 = new W3CErrorCode("FTST0001", "It is a static error if an operand of mild not (not in) contains ftnot or occurs.");
public static final ErrorCode FTST0003 = new W3CErrorCode("FTST0003", "It is a static error if a tokenizer for the language specified by the language option is not available.");
public static final ErrorCode FTST0004 = new W3CErrorCode("FTST0004", "It is a static error if sentence/paragraph boundaries are required but not supported by the tokenizer.");
public static final ErrorCode FTST0006 = new W3CErrorCode("FTST0006", "It is a static error if a stop word list cannot be found.");
public static final ErrorCode FTST0008 = new W3CErrorCode("FTST0008", "It is a static error if a stop word list is not in the correct format.");
public static final ErrorCode FTST0009 = new W3CErrorCode("FTST0009", "It is a static error if the specified language is not supported.");
public static final ErrorCode FTDY0016 = new W3CErrorCode("FTDY0016", "It is a dynamic error if a weight value is not within the required range.");
public static final ErrorCode FTDY0017 = new W3CErrorCode("FTDY0017", "It is a dynamic error if the right-hand match of mild not has any include-matches matching tokens not matched by include-matches of the left-hand match.");
public static final ErrorCode FTST0013 = new W3CErrorCode("FTST0013", "It is a static error if, in an implementation which does not support the Stop Word Languages feature, a stop word option includes a language specification.");
public static final ErrorCode FTST0018 = new W3CErrorCode("FTST0018", "It is a static error if a thesaurus is not available.");
public static final ErrorCode FTST0019 = new W3CErrorCode("FTST0019", "It is a static error if match options in a single contains text expression conflict with each other.");

/* eXist specific XQuery and XPath errors
*
* Codes have the format [EX][XQ|XP][DY|SE|ST][nnnn]
Expand Down
38 changes: 38 additions & 0 deletions exist-core/src/main/java/org/exist/xquery/ForExpr.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
public class ForExpr extends BindingExpression {

private QName positionalVariable = null;
private QName scoreVariable = null;
private boolean allowEmpty = false;
private boolean isOuterFor = true;

Expand All @@ -60,6 +61,17 @@ public void setPositionalVariable(final QName variable) {
positionalVariable = variable;
}

/**
* XQFT 3.0 §2.3: A "for" expression may have an optional score variable
* whose QName can be set via this method. The score variable is bound to
* an xs:double value representing the relevance score for each item.
*
* @param variable the name of the score variable
*/
public void setScoreVariable(final QName variable) {
scoreVariable = variable;
}

/* (non-Javadoc)
* @see org.exist.xquery.Expression#analyze(org.exist.xquery.Expression)
*/
Expand All @@ -83,6 +95,13 @@ public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
posVar.setStaticType(Type.INTEGER);
context.declareVariableBinding(posVar);
}
// Declare score variable (XQFT 3.0 §2.3)
if (scoreVariable != null) {
final LocalVariable scoreVar = new LocalVariable(scoreVariable);
scoreVar.setSequenceType(new SequenceType(Type.DOUBLE, Cardinality.EXACTLY_ONE));
scoreVar.setStaticType(Type.DOUBLE);
context.declareVariableBinding(scoreVar);
}

final AnalyzeContextInfo newContextInfo = new AnalyzeContextInfo(contextInfo);
newContextInfo.addFlag(SINGLE_STEP_EXECUTION);
Expand Down Expand Up @@ -135,6 +154,15 @@ public Sequence eval(Sequence contextSequence, Item contextItem)
at.setSequenceType(POSITIONAL_VAR_TYPE);
context.declareVariableBinding(at);
}
// Declare score variable (XQFT 3.0 §2.3)
LocalVariable score = null;
if (scoreVariable != null) {
score = new LocalVariable(scoreVariable);
score.setSequenceType(new SequenceType(Type.DOUBLE, Cardinality.EXACTLY_ONE));
context.declareVariableBinding(score);
// Naive implementation: always bind score to 1.0
score.setValue(new DoubleValue(this, 1.0));
}
// Assign the whole input sequence to the bound variable.
// This is required if we process the "where" or "order by" clause
// in one step.
Expand Down Expand Up @@ -262,6 +290,8 @@ private boolean callPostEval() {
case ORDERBY:
case GROUPBY:
return true;
default:
break;
}
prev = prev.getPreviousClause();
}
Expand All @@ -288,6 +318,8 @@ public void dump(ExpressionDumper dumper) {
}
if (positionalVariable != null)
{dumper.display(" at ").display(positionalVariable);}
if (scoreVariable != null)
{dumper.display(" score ").display(scoreVariable);}
dumper.display(" in ");
inputSequence.dump(dumper);
dumper.endIndent().nl();
Expand All @@ -314,6 +346,9 @@ public String toString() {
if (positionalVariable != null) {
result.append(" at ").append(positionalVariable);
}
if (scoreVariable != null) {
result.append(" score ").append(scoreVariable);
}
result.append(" in ");
result.append(inputSequence.toString());
result.append(" ");
Expand All @@ -337,6 +372,9 @@ public Set<QName> getTupleStreamVariables() {
if (positionalVariable != null) {
variables.add(positionalVariable);
}
if (scoreVariable != null) {
variables.add(scoreVariable);
}

final QName variable = getVariable();
if (variable != null) {
Expand Down
21 changes: 19 additions & 2 deletions exist-core/src/main/java/org/exist/xquery/LetExpr.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,21 @@
*/
public class LetExpr extends BindingExpression {

private boolean scoreBinding = false;

public LetExpr(XQueryContext context) {
super(context);
}

/**
* XQFT 3.0 §2.3: Mark this let binding as a score variable binding.
* When true, the variable is bound to the score (xs:double in [0,1])
* of the input expression rather than the expression's value.
*/
public void setScoreBinding(final boolean scoreBinding) {
this.scoreBinding = scoreBinding;
}

@Override
public ClauseType getType() {
return ClauseType.LET;
Expand Down Expand Up @@ -102,9 +113,15 @@ public Sequence eval(Sequence contextSequence, Item contextItem)
var = createVariable(varName);
var.setSequenceType(sequenceType);
context.declareVariableBinding(var);
var.setValue(in);
if (scoreBinding) {
// XQFT 3.0 §2.3: score binding — bind variable to the score
// of the expression. Naive implementation: 1.0 if non-empty, 0.0 if empty.
var.setValue(new DoubleValue(this, in.isEmpty() ? 0.0 : 1.0));
} else {
var.setValue(in);
}
if (sequenceType == null)
{var.checkType();} //Just because it makes conversions !
{var.checkType();} //Just because it makes conversions !
var.setContextDocs(inputSequence.getContextDocSet());
registerUpdateListener(in);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
*/
package org.exist.xquery;

import org.exist.xquery.ErrorCodes.ErrorCode;

public class StaticXQueryException extends XPathException
{
private static final long serialVersionUID = -8229758099980343418L;
Expand Down Expand Up @@ -53,7 +55,15 @@ public StaticXQueryException(final Expression expression, String message, Throwa
super(expression, message, cause);
}

//TODO add in ErrorCode and ErrorVal
public StaticXQueryException(int line, int column, ErrorCode errorCode, String message) {
super(line, column, errorCode, message);
}

public StaticXQueryException(int line, int column, ErrorCode errorCode, String message, Throwable cause) {
super(line, column, errorCode, message);
initCause(cause);
}

public StaticXQueryException(int line, int column, String message, Throwable cause) {
super(line, column, message, cause);
}
Expand Down
4 changes: 2 additions & 2 deletions exist-core/src/main/java/org/exist/xquery/XQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ private CompiledXQuery compile(final XQueryContext context, final Reader reader,
if (msg.endsWith(", found 'null'")) {
msg = msg.substring(0, msg.length() - ", found 'null'".length());
}
throw new StaticXQueryException(e.getLine(), e.getColumn(), msg);
throw new StaticXQueryException(e.getLine(), e.getColumn(), ErrorCodes.XPST0003, msg);
} catch(final TokenStreamException e) {
final String es = e.toString();
if(es.matches("^line \\d+:\\d+:.+")) {
Expand All @@ -298,7 +298,7 @@ private CompiledXQuery compile(final XQueryContext context, final Reader reader,
final int line = Integer.parseInt(es.substring(5, es.indexOf(':')));
final String tmpColumn = es.substring(es.indexOf(':') + 1);
final int column = Integer.parseInt(tmpColumn.substring(0, tmpColumn.indexOf(':')));
throw new StaticXQueryException(line, column, e.getMessage(), e);
throw new StaticXQueryException(line, column, ErrorCodes.XPST0003, e.getMessage(), e);
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Error compiling query: {}", e.getMessage(), e);
Expand Down
29 changes: 29 additions & 0 deletions exist-core/src/main/java/org/exist/xquery/XQueryContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import org.exist.xquery.ft.FTMatchOptions;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
Expand Down Expand Up @@ -307,6 +308,18 @@ public class XQueryContext implements BinaryValueManager, Context {
*/
private String defaultCollation = Collations.UNICODE_CODEPOINT_COLLATION_URI;

/**
* XQFT 3.0: default full-text match options declared via "declare ft-option".
*/
private FTMatchOptions defaultFTMatchOptions;

/**
* XQFT 3.0: thesaurus URI-to-file mapping.
* Maps thesaurus URIs (e.g., "http://bstore1.example.com/UsabilityThesaurus.xml")
* to local file paths.
*/
private final Map<String, Path> thesaurusRegistry = new HashMap<>();

/**
* The default language
*/
Expand Down Expand Up @@ -1090,6 +1103,22 @@ public String getDefaultCollation() {
return defaultCollation;
}

public void setDefaultFTMatchOptions(final FTMatchOptions opts) {
this.defaultFTMatchOptions = opts;
}

public FTMatchOptions getDefaultFTMatchOptions() {
return defaultFTMatchOptions;
}

public void registerThesaurus(final String uri, final Path file) {
thesaurusRegistry.put(uri, file);
}

public Path resolveThesaurusURI(final String uri) {
return thesaurusRegistry.get(uri);
}

@Override
public Collator getCollator(String uri) throws XPathException {
return getCollator(uri, ErrorCodes.XQST0076);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* info@exist-db.org
* http://www.exist-db.org
*
* 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.exist.xquery.ft;

import org.exist.xquery.AbstractExpression;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Type;

/**
* Abstract base class for Full Text expression nodes.
*
* FT expression nodes participate in the expression tree for analysis
* and serialization but are not independently evaluable — evaluation
* is driven by {@link FTContainsExpr}.
*/
public abstract class FTAbstractExpr extends AbstractExpression {

protected FTAbstractExpr(final XQueryContext context) {
super(context);
}

@Override
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
throw new XPathException(this, getClass().getSimpleName() + " cannot be evaluated directly");
}

@Override
public int returnsType() {
return Type.ITEM;
}
}
92 changes: 92 additions & 0 deletions exist-core/src/main/java/org/exist/xquery/ft/FTAnd.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* eXist-db Open Source Native XML Database
* Copyright (C) 2001 The eXist-db Authors
*
* info@exist-db.org
* http://www.exist-db.org
*
* 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.exist.xquery.ft;

import org.exist.xquery.AnalyzeContextInfo;
import org.exist.xquery.Expression;
import org.exist.xquery.XPathException;
import org.exist.xquery.XQueryContext;
import org.exist.xquery.util.ExpressionDumper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* W3C XQFT 3.0 — FTAnd.
*
* <pre>FTAnd ::= FTMildNot ( "ftand" FTMildNot )*</pre>
*/
public class FTAnd extends FTAbstractExpr {

private final List<Expression> operands = new ArrayList<>();

public FTAnd(final XQueryContext context) {
super(context);
}

public void addOperand(final Expression operand) {
operands.add(operand);
}

public List<Expression> getOperands() {
return Collections.unmodifiableList(operands);
}

@Override
public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException {
contextInfo.setParent(this);
for (final Expression operand : operands) {
operand.analyze(contextInfo);
}
}

@Override
public void dump(final ExpressionDumper dumper) {
for (int i = 0; i < operands.size(); i++) {
if (i > 0) {
dumper.display(" ftand ");
}
operands.get(i).dump(dumper);
}
}

@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < operands.size(); i++) {
if (i > 0) {
sb.append(" ftand ");
}
sb.append(operands.get(i).toString());
}
return sb.toString();
}

@Override
public void resetState(final boolean postOptimization) {
super.resetState(postOptimization);
for (final Expression operand : operands) {
operand.resetState(postOptimization);
}
}
}
Loading
Loading