Skip to content

Commit df8b2e3

Browse files
authored
Merge pull request #35 from evolvedbinary/6.x.x/hotfix/deferred-function-call-type-check-errors
[6.x.x] Make sure that underlying type check errors are exposed for deferred function calls
2 parents b27d6e5 + 74f399e commit df8b2e3

File tree

4 files changed

+191
-50
lines changed

4 files changed

+191
-50
lines changed

exist-core/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@
731731
<include>src/test/resources/log4j2.xml</include>
732732
<include>src/test/resources-filtered/conf.xml</include>
733733
<include>src/test/resources/standalone-webapp/WEB-INF/web.xml</include>
734+
<include>src/test/xquery/tail-recursion.xml</include>
734735
<include>src/test/xquery/maps/maps.xqm</include>
735736
<include>src/test/xquery/util/util.xml</include>
736737
<include>src/test/xquery/xquery3/parse-xml.xqm</include>
@@ -911,6 +912,8 @@
911912
<include>src/main/java/org/exist/xqj/Marshaller.java</include>
912913
<include>src/test/java/org/exist/xqj/MarshallerTest.java</include>
913914
<include>src/test/java/org/exist/xquery/ConstructedNodesRecoveryTest.java</include>
915+
<include>src/main/java/org/exist/xquery/DeferredFunctionCall.java</include>
916+
<include>src/main/java/org/exist/xquery/DynamicCardinalityCheck.java</include>
914917
<include>src/main/java/org/exist/xquery/DynamicTypeCheck.java</include>
915918
<include>src/main/java/org/exist/xquery/ErrorCodes.java</include>
916919
<include>src/test/java/org/exist/xquery/ForwardReferenceTest.java</include>
@@ -998,6 +1001,7 @@
9981001
<exclude>src/test/resources-filtered/conf.xml</exclude>
9991002
<exclude>src/test/resources/standalone-webapp/WEB-INF/web.xml</exclude>
10001003
<exclude>src/test/xquery/binary-value.xqm</exclude>
1004+
<exclude>src/test/xquery/tail-recursion.xml</exclude>
10011005
<exclude>src/test/xquery/maps/maps.xqm</exclude>
10021006
<exclude>src/test/xquery/securitymanager/acl.xqm</exclude>
10031007
<exclude>src/test/xquery/util/util.xml</exclude>
@@ -1247,6 +1251,8 @@
12471251
<exclude>src/test/java/org/exist/xqj/MarshallerTest.java</exclude>
12481252
<exclude>src/main/java/org/exist/xquery/Cardinality.java</exclude>
12491253
<exclude>src/test/java/org/exist/xquery/ConstructedNodesRecoveryTest.java</exclude>
1254+
<include>src/main/java/org/exist/xquery/DeferredFunctionCall.java</include>
1255+
<include>src/main/java/org/exist/xquery/DynamicCardinalityCheck.java</include>
12501256
<exclude>src/main/java/org/exist/xquery/DynamicTypeCheck.java</exclude>
12511257
<exclude>src/main/java/org/exist/xquery/ErrorCodes.java</exclude>
12521258
<exclude>src/test/java/org/exist/xquery/ForwardReferenceTest.java</exclude>

exist-core/src/main/java/org/exist/xquery/DeferredFunctionCall.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
11
/*
2+
* Elemental
3+
* Copyright (C) 2024, Evolved Binary Ltd
4+
*
5+
6+
* https://www.evolvedbinary.com | https://www.elemental.xyz
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; version 2.1.
11+
*
12+
* This library is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this library; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+
*
21+
* NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22+
* The original license header is included below.
23+
*
24+
* =====================================================================
25+
*
226
* eXist-db Open Source Native XML Database
327
* Copyright (C) 2001 The eXist-db Authors
428
*
@@ -44,7 +68,7 @@ public abstract class DeferredFunctionCall implements Sequence {
4468

4569
private FunctionSignature signature;
4670
private Sequence sequence = null;
47-
private XPathException caughtException = null;
71+
private @Nullable XPathException caughtException = null;
4872

4973
protected DeferredFunctionCall(FunctionSignature signature) {
5074
this.signature = signature;
@@ -58,6 +82,15 @@ private void realize() throws XPathException {
5882
sequence = execute();
5983
}
6084
}
85+
86+
/**
87+
* Get any exception that has been caught.
88+
*
89+
* @return an XPathException or null if there is no caught exception.
90+
*/
91+
public @Nullable XPathException getCaughtException() {
92+
return caughtException;
93+
}
6194

6295
protected FunctionSignature getSignature() {
6396
return signature;
Lines changed: 92 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,28 @@
11
/*
2+
* Elemental
3+
* Copyright (C) 2024, Evolved Binary Ltd
4+
*
5+
6+
* https://www.evolvedbinary.com | https://www.elemental.xyz
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; version 2.1.
11+
*
12+
* This library is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this library; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+
*
21+
* NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22+
* The original license header is included below.
23+
*
24+
* =====================================================================
25+
*
226
* eXist-db Open Source Native XML Database
327
* Copyright (C) 2001 The eXist-db Authors
428
*
@@ -27,6 +51,8 @@
2751
import org.exist.xquery.value.Item;
2852
import org.exist.xquery.value.Sequence;
2953

54+
import javax.annotation.Nullable;
55+
3056
/**
3157
* Runtime-check for the cardinality of a function parameter.
3258
*
@@ -47,52 +73,71 @@ public DynamicCardinalityCheck(final XQueryContext context, final Cardinality re
4773
setLocation(expression.getLine(), expression.getColumn());
4874
}
4975

50-
/* (non-Javadoc)
51-
* @see org.exist.xquery.Expression#analyze(org.exist.xquery.Expression)
52-
*/
53-
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
76+
@Override
77+
public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException {
5478
contextInfo.setParent(this);
5579
expression.analyze(contextInfo);
5680
}
5781

58-
/* (non-Javadoc)
59-
* @see org.exist.xquery.Expression#eval(org.exist.xquery.StaticContext, org.exist.dom.persistent.DocumentSet, org.exist.xquery.value.Sequence, org.exist.xquery.value.Item)
60-
*/
61-
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
82+
@Override
83+
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
6284
if (context.getProfiler().isEnabled()) {
6385
context.getProfiler().start(this);
64-
context.getProfiler().message(this, Profiler.DEPENDENCIES,
65-
"DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
66-
if (contextSequence != null)
67-
{context.getProfiler().message(this, Profiler.START_SEQUENCES,
68-
"CONTEXT SEQUENCE", contextSequence);}
69-
if (contextItem != null)
70-
{context.getProfiler().message(this, Profiler.START_SEQUENCES,
71-
"CONTEXT ITEM", contextItem.toSequence());}
86+
context.getProfiler().message(this, Profiler.DEPENDENCIES, "DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
87+
if (contextSequence != null) {
88+
context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT SEQUENCE", contextSequence);
89+
}
90+
if (contextItem != null) {
91+
context.getProfiler().message(this, Profiler.START_SEQUENCES, "CONTEXT ITEM", contextItem.toSequence());
92+
}
7293
}
94+
7395
final Sequence seq = expression.eval(contextSequence, contextItem);
74-
Cardinality actualCardinality;
75-
if (seq.isEmpty())
76-
{actualCardinality = Cardinality.EMPTY_SEQUENCE;}
77-
else if (seq.hasMany())
78-
{actualCardinality = Cardinality._MANY;}
79-
else
80-
{actualCardinality = Cardinality.EXACTLY_ONE;}
96+
97+
final Cardinality actualCardinality;
98+
if (isEmpty(seq)) {
99+
actualCardinality = Cardinality.EMPTY_SEQUENCE;
100+
} else if (hasMany(seq)) {
101+
actualCardinality = Cardinality._MANY;
102+
} else {
103+
actualCardinality = Cardinality.EXACTLY_ONE;
104+
}
105+
81106
if (!requiredCardinality.isSuperCardinalityOrEqualOf(actualCardinality)) {
82-
error.addArgs(ExpressionDumper.dump(expression),
83-
requiredCardinality.getHumanDescription(),
84-
seq.getItemCount());
107+
error.addArgs(ExpressionDumper.dump(expression), requiredCardinality.getHumanDescription(), seq.getItemCount());
85108
throw new XPathException(this, error.toString());
86109
}
87-
if (context.getProfiler().isEnabled())
88-
{context.getProfiler().end(this, "", seq);}
110+
111+
if (context.getProfiler().isEnabled()) {
112+
context.getProfiler().end(this, "", seq);
113+
}
114+
89115
return seq;
90116
}
91117

92-
/* (non-Javadoc)
93-
* @see org.exist.xquery.Expression#dump(org.exist.xquery.util.ExpressionDumper)
94-
*/
95-
public void dump(ExpressionDumper dumper) {
118+
private boolean isEmpty(final Sequence sequence) throws XPathException {
119+
final boolean empty = sequence.isEmpty();
120+
throwIfDeferredFunctionCallAndHasException(sequence);
121+
return empty;
122+
}
123+
124+
private boolean hasMany(final Sequence sequence) throws XPathException {
125+
final boolean hasMany = sequence.hasMany();
126+
throwIfDeferredFunctionCallAndHasException(sequence);
127+
return hasMany;
128+
}
129+
130+
private void throwIfDeferredFunctionCallAndHasException(final Sequence sequence) throws XPathException {
131+
if (sequence instanceof DeferredFunctionCall) {
132+
@Nullable final XPathException caughtException = ((DeferredFunctionCall) sequence).getCaughtException();
133+
if (caughtException != null) {
134+
throw caughtException;
135+
}
136+
}
137+
}
138+
139+
@Override
140+
public void dump(final ExpressionDumper dumper) {
96141
if(dumper.verbosity() > 1) {
97142
dumper.display("dynamic-cardinality-check");
98143
dumper.display("(");
@@ -108,44 +153,42 @@ public String toString() {
108153
return expression.toString();
109154
}
110155

111-
/* (non-Javadoc)
112-
* @see org.exist.xquery.Expression#returnsType()
113-
*/
156+
@Override
114157
public int returnsType() {
115158
return expression.returnsType();
116159
}
117160

118-
/* (non-Javadoc)
119-
* @see org.exist.xquery.AbstractExpression#getDependencies()
120-
*/
161+
@Override
121162
public int getDependencies() {
122163
return expression.getDependencies();
123164
}
124165

125-
public void setContextDocSet(DocumentSet contextSet) {
166+
public void setContextDocSet(final DocumentSet contextSet) {
126167
super.setContextDocSet(contextSet);
127168
expression.setContextDocSet(contextSet);
128169
}
129170

130-
/* (non-Javadoc)
131-
* @see org.exist.xquery.AbstractExpression#resetState()
132-
*/
133-
public void resetState(boolean postOptimization) {
171+
@Override
172+
public void resetState(final boolean postOptimization) {
134173
super.resetState(postOptimization);
135174
expression.resetState(postOptimization);
136175
}
137176

138-
public void accept(ExpressionVisitor visitor) {
177+
@Override
178+
public void accept(final ExpressionVisitor visitor) {
139179
expression.accept(visitor);
140180
}
141181

182+
@Override
142183
public int getSubExpressionCount() {
143184
return 1;
144185
}
145-
146-
public Expression getSubExpression(int index) {
147-
if (index == 0)
148-
{return expression;}
149-
throw new IndexOutOfBoundsException("Index: " + index + ", Size: "+getSubExpressionCount());
186+
187+
@Override
188+
public Expression getSubExpression(final int index) {
189+
if (index == 0) {
190+
return expression;
191+
}
192+
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + getSubExpressionCount());
150193
}
151194
}

exist-core/src/test/xquery/tail-recursion.xml

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!--
33
4+
Elemental
5+
Copyright (C) 2024, Evolved Binary Ltd
6+
7+
8+
https://www.evolvedbinary.com | https://www.elemental.xyz
9+
10+
This library is free software; you can redistribute it and/or
11+
modify it under the terms of the GNU Lesser General Public
12+
License as published by the Free Software Foundation; version 2.1.
13+
14+
This library is distributed in the hope that it will be useful,
15+
but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17+
Lesser General Public License for more details.
18+
19+
You should have received a copy of the GNU Lesser General Public
20+
License along with this library; if not, write to the Free Software
21+
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22+
23+
NOTE: Parts of this file contain code from 'The eXist-db Authors'.
24+
The original license header is included below.
25+
26+
=====================================================================
27+
428
eXist-db Open Source Native XML Database
529
Copyright (C) 2001 The eXist-db Authors
630
@@ -29,6 +53,7 @@
2953
<author>LordGeoffrey</author>
3054
</description>
3155
<setup>
56+
<create-collection parent="/db" name="test-correct-error"/>
3257
</setup>
3358
<!--
3459
<test output="text">
@@ -56,4 +81,38 @@ local:plus(1000, 0)
5681
</code>
5782
<expected>500500</expected>
5883
</test>
84+
<test output="text">
85+
<!-- See: https://github.com/eXist-db/exist/issues/5139 -->
86+
<task>correct-error</task>
87+
<code><![CDATA[
88+
xquery version "3.1";
89+
90+
declare function local:parent-collection($collection-path as xs:string) as xs:string? {
91+
let $parent-collection-path := replace($collection-path, "(/.+)/.*", "$1")
92+
return
93+
if ($parent-collection-path eq $collection-path or not(starts-with($parent-collection-path, "/db")))
94+
then
95+
()
96+
else
97+
$parent-collection-path
98+
};
99+
100+
declare %private function local:ascend($collection-path as xs:string, $group-name as xs:string, $collection-permission as xs:string) as empty-sequence() {
101+
if (not(starts-with($collection-path, "/db")))
102+
then
103+
()
104+
else
105+
let $_ := sm:add-group-ace(xs:anyURI($collection-path), $group-name, true(), $collection-permission)
106+
return
107+
local:ascend(local:parent-collection($collection-path), $group-name, $collection-permission)
108+
};
109+
110+
try {
111+
local:ascend("/db/test-correct-error", "guest", "r-x")
112+
} catch * {
113+
substring-before($err:description, ", source:")
114+
}
115+
]]></code>
116+
<expected>exerr:ERROR XPTY0004: The actual cardinality for parameter 1 does not match the cardinality declared in the function's signature: local:ascend($collection-path as xs:string, $group-name as xs:string, $collection-permission as xs:string) empty-sequence(). Expected cardinality: exactly one, got 0. [at line 21, column 26</expected>
117+
</test>
59118
</TestSet>

0 commit comments

Comments
 (0)