Skip to content

Commit 7b2399d

Browse files
authored
Merge pull request #4414 from evolvedbinary/hotfix/predicate-function-reference
Fix the use of Function Reference from with a Predicate
2 parents 03af210 + e3092fb commit 7b2399d

File tree

8 files changed

+254
-10
lines changed

8 files changed

+254
-10
lines changed

exist-core-jmh/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
<header>${project.parent.relativePath}/LGPL-21-license.template.txt</header>
7979
<excludes>
8080
<exclude>src/main/java/org/exist/storage/lock/LockTableBenchmark.java</exclude>
81+
<exclude>src/main/java/org/exist/xquery/utils/StringJoinBenchmark.java</exclude>
8182
<exclude>src/main/java/org/exist/xquery/utils/URIUtilsBenchmark.java</exclude>
8283
</excludes>
8384
</licenseSet>
@@ -89,6 +90,7 @@
8990
<header>${project.parent.relativePath}/FDB-backport-LGPL-21-ONLY-license.template.txt</header>
9091
<includes>
9192
<include>src/main/java/org/exist/storage/lock/LockTableBenchmark.java</include>
93+
<include>src/main/java/org/exist/xquery/utils/StringJoinBenchmark.java</include>
9294
<include>src/main/java/org/exist/xquery/utils/URIUtilsBenchmark.java</include>
9395
</includes>
9496

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Copyright (C) 2014, Evolved Binary Ltd
3+
*
4+
* This file was originally ported from FusionDB to eXist-db by
5+
* Evolved Binary, for the benefit of the eXist-db Open Source community.
6+
* Only the ported code as it appears in this file, at the time that
7+
* it was contributed to eXist-db, was re-licensed under The GNU
8+
* Lesser General Public License v2.1 only for use in eXist-db.
9+
*
10+
* This license grant applies only to a snapshot of the code as it
11+
* appeared when ported, it does not offer or infer any rights to either
12+
* updates of this source code or access to the original source code.
13+
*
14+
* The GNU Lesser General Public License v2.1 only license follows.
15+
*
16+
* ---------------------------------------------------------------------
17+
*
18+
* Copyright (C) 2014, Evolved Binary Ltd
19+
*
20+
* This library is free software; you can redistribute it and/or
21+
* modify it under the terms of the GNU Lesser General Public
22+
* License as published by the Free Software Foundation; version 2.1.
23+
*
24+
* This library is distributed in the hope that it will be useful,
25+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
26+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27+
* Lesser General Public License for more details.
28+
*
29+
* You should have received a copy of the GNU Lesser General Public
30+
* License along with this library; if not, write to the Free Software
31+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
32+
*/
33+
package org.exist.xquery.utils;
34+
35+
import org.openjdk.jmh.annotations.*;
36+
37+
import java.nio.charset.StandardCharsets;
38+
import java.util.ArrayList;
39+
import java.util.List;
40+
import java.util.Random;
41+
42+
/**
43+
* Benchmarks on variations of Java String Join operations.
44+
*
45+
* @author <a href="mailto:[email protected]">Adam Retter</a>
46+
*/
47+
@State(Scope.Benchmark)
48+
public class StringJoinBenchmark {
49+
50+
private final int maxStringLength = 20;
51+
52+
@Param({ "1", "2", "5", "10", "100", "1000", "10000" })
53+
private int numOfStrings;
54+
55+
private final List<String> strings = new ArrayList();
56+
57+
// @State(Scope.Thread)
58+
// public static class BuilderState {
59+
// StringBuilder builder;
60+
//
61+
// @Setup(Level.Invocation) //TODO(AR) check that Level.Invocation is correct
62+
// public void setUp() {
63+
// builder = new StringBuilder();
64+
// }
65+
// }
66+
67+
@Setup(Level.Trial)
68+
public void setUp() {
69+
final byte[] strData = new byte[maxStringLength];
70+
final Random random = new Random();
71+
for (int i = 0; i < numOfStrings; i++) {
72+
final int strLen = random.nextInt(maxStringLength) + 1;
73+
random.nextBytes(strData);
74+
strings.add(new String(strData, 0, strLen, StandardCharsets.UTF_8));
75+
}
76+
}
77+
78+
@Benchmark
79+
public StringBuilder forApproach(/*final BuilderState builderState*/) {
80+
// final StringBuilder builder = builderState.builder;
81+
final StringBuilder builder = new StringBuilder();
82+
83+
for (int i = 0; i < strings.size(); i++) {
84+
if (i > 0) {
85+
builder.append(", ");
86+
}
87+
builder.append(strings.get(i));
88+
}
89+
return builder;
90+
}
91+
92+
@Benchmark
93+
public StringBuilder forApproachRadek(/*final BuilderState builderState*/) {
94+
// final StringBuilder builder = builderState.builder;
95+
final StringBuilder builder = new StringBuilder();
96+
97+
for (int i = 0; i < strings.size(); i++) {
98+
builder.append(strings.get(i));
99+
builder.append(", ");
100+
}
101+
102+
builder.substring(0, builder.length() - 3);
103+
104+
return builder;
105+
}
106+
107+
@Benchmark
108+
public StringBuilder forEachApproach(/*final BuilderState builderState*/) {
109+
// final StringBuilder builder = builderState.builder;
110+
final StringBuilder builder = new StringBuilder();
111+
112+
boolean firstArgument = true;
113+
for (final String str : strings) {
114+
builder.append(str);
115+
if (firstArgument) {
116+
firstArgument = false;
117+
} else {
118+
builder.append(", ");
119+
}
120+
}
121+
return builder;
122+
}
123+
124+
@Benchmark
125+
public StringBuilder forEachApproachRadek(/*final BuilderState builderState*/) {
126+
// final StringBuilder builder = builderState.builder;
127+
final StringBuilder builder = new StringBuilder();
128+
129+
boolean firstArgument = true;
130+
for (final String str : strings) {
131+
builder.append(str);
132+
builder.append(", ");
133+
}
134+
135+
builder.substring(0, builder.length() - 3);
136+
137+
return builder;
138+
}
139+
140+
@Benchmark
141+
public String jdkApproach() {
142+
return String.join(", ", strings);
143+
}
144+
145+
public static void main(final String args[]) {
146+
// NOTE: just for running with the java debugger
147+
final StringJoinBenchmark stringJoinBenchmark = new StringJoinBenchmark();
148+
// stringJoinBenchmark.forApproach();
149+
// stringJoinBenchmark.forEachApproach();
150+
stringJoinBenchmark.jdkApproach();
151+
}
152+
}

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

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,31 @@
3131

3232
public class DynamicFunctionCall extends AbstractExpression {
3333

34-
private Expression functionExpr;
35-
private List<Expression> arguments;
36-
private boolean isPartial = false;
34+
private final Expression functionExpr;
35+
private final List<Expression> arguments;
36+
private final boolean isPartial;
3737

3838
private AnalyzeContextInfo cachedContextInfo;
3939

40-
public DynamicFunctionCall(XQueryContext context, Expression fun, List<Expression> args, boolean partial) {
40+
public DynamicFunctionCall(final XQueryContext context, final Expression fun, final List<Expression> args, final boolean partial) {
4141
super(context);
4242
setLocation(fun.getLine(), fun.getColumn());
4343
this.functionExpr = fun;
4444
this.arguments = args;
4545
this.isPartial = partial;
4646
}
4747

48+
@Override
49+
public int getDependencies() {
50+
int dependencies = functionExpr.getDependencies();
51+
if (arguments != null) {
52+
for (final Expression argument : arguments) {
53+
dependencies |= argument.getDependencies();
54+
}
55+
}
56+
return dependencies;
57+
}
58+
4859
@Override
4960
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
5061
cachedContextInfo = new AnalyzeContextInfo(contextInfo);
@@ -94,7 +105,7 @@ public Sequence eval(Sequence contextSequence, Item contextItem)
94105
ref.analyze(new AnalyzeContextInfo(cachedContextInfo));
95106
// Evaluate the function
96107
try {
97-
return ref.eval(contextSequence);
108+
return ref.eval(contextSequence, contextItem);
98109
} catch (XPathException e) {
99110
if (e.getLine() <= 0) {
100111
e.setLocation(getLine(), getColumn(), getSource());
@@ -117,4 +128,31 @@ public void resetState(final boolean postOptimization) {
117128
functionExpr.resetState(postOptimization);
118129
arguments.forEach(arg -> arg.resetState(postOptimization));
119130
}
131+
132+
@Override
133+
public String toString() {
134+
final StringBuilder builder = new StringBuilder();
135+
if (functionExpr != null) {
136+
builder.append(functionExpr);
137+
} else {
138+
builder.append("DynamicFunctionCall{arguments=");
139+
}
140+
141+
builder.append('(');
142+
if (arguments != null) {
143+
for (int i = 0; i < arguments.size(); i++) {
144+
if (i > 0) {
145+
builder.append(", ");
146+
}
147+
builder.append(arguments.get(i).toString());
148+
}
149+
}
150+
builder.append(')');
151+
152+
if (functionExpr == null) {
153+
builder.append('}');
154+
}
155+
156+
return builder.toString();
157+
}
120158
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ public void setContextDocSet(final DocumentSet contextSet) {
591591
}
592592
}
593593

594-
public ExecutionMode getExecutionMode() {
594+
ExecutionMode getExecutionMode() {
595595
return executionMode;
596596
}
597597

exist-core/src/main/java/org/exist/xquery/functions/array/ArrayType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ public Sequence eval(Sequence contextSequence) throws XPathException {
214214
return accessorFunc.eval(contextSequence);
215215
}
216216

217+
@Override
218+
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
219+
return accessorFunc.eval(contextSequence, contextItem);
220+
}
221+
217222
@Override
218223
public Sequence evalFunction(final Sequence contextSequence, final Item contextItem, final Sequence[] seq) throws XPathException {
219224
final AccessorFunc af = (AccessorFunc) accessorFunc.getFunction();

exist-core/src/main/java/org/exist/xquery/functions/map/AbstractMapType.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ public Sequence eval(Sequence contextSequence) throws XPathException {
111111
return getAccessorFunc().eval(contextSequence);
112112
}
113113

114+
@Override
115+
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
116+
return getAccessorFunc().eval(contextSequence, contextItem);
117+
}
118+
114119
@Override
115120
public Sequence evalFunction(final Sequence contextSequence, final Item contextItem, final Sequence[] seq) throws XPathException {
116121
final AccessorFunc accessorFunc = (AccessorFunc) getAccessorFunc().getFunction();

exist-core/src/main/java/org/exist/xquery/value/FunctionReference.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ public class FunctionReference extends AtomicValue implements AutoCloseable {
3838

3939
private final static Logger LOG = LogManager.getLogger(FunctionReference.class);
4040

41-
protected FunctionCall functionCall;
41+
protected final FunctionCall functionCall;
4242

43-
public FunctionReference(FunctionCall fcall) {
44-
this.functionCall = fcall;
43+
public FunctionReference(final FunctionCall functionCall) {
44+
this.functionCall = functionCall;
4545
}
4646

4747
public FunctionCall getCall() {
@@ -86,6 +86,18 @@ public Sequence eval(Sequence contextSequence) throws XPathException {
8686
return functionCall.eval(contextSequence);
8787
}
8888

89+
/**
90+
* Calls {@link FunctionCall#eval(Sequence, Item)}.
91+
*
92+
* @param contextSequence the input sequence
93+
* @param contextItem optional: the current context item
94+
* @return evaluation result of the function call
95+
* @throws XPathException in case of dynamic error
96+
*/
97+
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
98+
return functionCall.eval(contextSequence, contextItem);
99+
}
100+
89101
/**
90102
* Calls {@link FunctionCall#evalFunction(Sequence, Item, Sequence[])}.
91103
*
@@ -170,4 +182,9 @@ public AtomicValue min(Collator collator, AtomicValue other)
170182
public AtomicValue atomize() throws XPathException {
171183
throw new XPathException(ErrorCodes.FOTY0013, "A function item other than an array cannot be atomized");
172184
}
185+
186+
@Override
187+
public String toString() {
188+
return "anonymous-function#" + functionCall.getArgumentCount();
189+
}
173190
}

exist-core/src/test/xquery/xquery3/fnRefs.xql

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ xquery version "3.0";
2424
module namespace fnRefs="http://exist-db.org/xquery/test/function_reference";
2525

2626
declare namespace test="http://exist-db.org/xquery/xqsuite";
27-
declare namespace xpf = "http://www.w3.org/2005/xpath-functions";
2827

2928
declare
3029
%test:assertEquals("/db")
@@ -53,3 +52,29 @@ function fnRefs:concat-arity2() {
5352
fn:concat#2
5453
)
5554
};
55+
56+
declare
57+
%test:assertEquals("b")
58+
function fnRefs:call-from-predicate() {
59+
let $predicate := function($x as xs:string, $y as xs:string) as xs:boolean { $x eq $y }
60+
return
61+
("a", "b", "c")[$predicate("b", .)]
62+
};
63+
64+
declare function fnRefs:pred($x as xs:string, $y as xs:string) as xs:boolean {
65+
$x eq $y
66+
};
67+
68+
declare
69+
%test:assertEquals("b")
70+
function fnRefs:call-from-predicate-via-variable() {
71+
let $predicate := fnRefs:pred#2
72+
return
73+
("a", "b", "c")[$predicate("b", .)]
74+
};
75+
76+
declare
77+
%test:assertEquals("b")
78+
function fnRefs:call-from-predicate-inline() {
79+
("a", "b", "c")[function($x as xs:string, $y as xs:string) as xs:boolean { $x eq $y }("b", .)]
80+
};

0 commit comments

Comments
 (0)