• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.02
/extensions/indexes/sort/src/main/java/org/exist/xquery/modules/sort/CreateOrderIndex.java
1
/*
2
 * Elemental
3
 * Copyright (C) 2024, Evolved Binary Ltd
4
 *
5
 * admin@evolvedbinary.com
6
 * https://www.evolvedbinary.com | https://www.elemental.xyz
7
 *
8
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.xquery.modules.sort;
50

51
import org.apache.logging.log4j.LogManager;
52
import org.apache.logging.log4j.Logger;
53
import org.exist.EXistException;
54
import org.exist.dom.QName;
55
import org.exist.dom.persistent.NodeProxy;
56
import org.exist.indexing.sort.SortIndex;
57
import org.exist.indexing.sort.SortIndexWorker;
58
import org.exist.indexing.sort.SortItem;
59
import org.exist.util.FastQSort;
60
import org.exist.util.LockException;
61
import org.exist.xquery.*;
62
import org.exist.xquery.value.*;
63
import org.w3c.dom.Element;
64

65
import java.util.ArrayList;
66
import java.util.List;
67

68
public class CreateOrderIndex extends BasicFunction {
69

70
    public final static FunctionSignature[] signatures = {
1✔
71
            new FunctionSignature(
72
                    new QName("create-index", SortModule.NAMESPACE_URI, SortModule.PREFIX),
73
                    "Create a sort index to be used within an 'order by' expression.",
74
                    new SequenceType[]{
75
                            new FunctionParameterSequenceType("id", Type.STRING, Cardinality.EXACTLY_ONE,
76
                                    "The id by which the index will be known and distinguished from other indexes " +
77
                                            "on the same nodes."),
78
                            new FunctionParameterSequenceType("nodes", Type.NODE, Cardinality.ZERO_OR_MORE,
79
                                    "The node set to be indexed."),
80
                            new FunctionParameterSequenceType("values", Type.ANY_ATOMIC_TYPE, Cardinality.ZERO_OR_MORE,
81
                                    "The values to be indexed. There should be one value for each node in $nodes. " +
82
                                            "$values thus needs to contain as many items as $nodes. If not, a dynamic error " +
83
                                            "is triggered."),
84
                            new FunctionParameterSequenceType("options", Type.ELEMENT, Cardinality.ZERO_OR_ONE,
85
                                    "<options order='ascending|descending' empty='least|greatest'/>")
86
                    },
87
                    new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE, "")),
88
            new FunctionSignature(
89
                    new QName("create-index-callback", SortModule.NAMESPACE_URI, SortModule.PREFIX),
90
                    "Create a sort index to be used within an 'order by' expression.",
91
                    new SequenceType[]{
92
                            new FunctionParameterSequenceType("id", Type.STRING, Cardinality.EXACTLY_ONE,
93
                                    "The id by which the index will be known and distinguished from other indexes " +
94
                                            "on the same nodes."),
95
                            new FunctionParameterSequenceType("nodes", Type.NODE, Cardinality.ZERO_OR_MORE,
96
                                    "The node set to be indexed."),
97
                            new FunctionParameterSequenceType("callback", Type.FUNCTION, Cardinality.EXACTLY_ONE,
98
                                    "A callback function which will be called for every node in the $nodes input set. " +
99
                                            "The function receives the current node as single argument and should return " +
100
                                            "an atomic value by which the node will be sorted."),
101
                            new FunctionParameterSequenceType("options", Type.ELEMENT, Cardinality.ZERO_OR_ONE,
102
                                    "<options order='ascending|descending' empty='least|greatest'/>")
103
                    },
104
                    new FunctionReturnSequenceType(Type.ITEM, Cardinality.ZERO_OR_MORE, ""))
105
    };
106

107
    protected static final Logger LOG = LogManager.getLogger(CreateOrderIndex.class);
1✔
108

109
    private boolean descending = false;
1✔
110
    private boolean emptyLeast = false;
1✔
111

112
    public CreateOrderIndex(final XQueryContext context, final FunctionSignature signature) {
113
        super(context, signature);
1✔
114
    }
1✔
115

116
    @Override
117
    public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
118
        if (args[1].isEmpty())
1!
119
            return Sequence.EMPTY_SEQUENCE;
×
120
        final String id = args[0].getStringValue();
1✔
121
        // check how the function was called and prepare callback
122
        FunctionReference call = null;
1✔
123
        if (isCalledAs("create-index-callback")) {
1✔
124
            call = (FunctionReference) args[2].itemAt(0);
1✔
125
        } else if (args[2].getItemCount() != args[1].getItemCount())
1!
126
            throw new XPathException(this, "$nodes and $values sequences need to have the same length.");
×
127

128
        // options
129
        if (args[3].getItemCount() > 0) {
1!
130
            final NodeValue optionValue = (NodeValue) args[3].itemAt(0);
1✔
131
            final Element options = (Element) optionValue.getNode();
1✔
132
            String option = options.getAttribute("order");
1✔
133
            if (!option.isEmpty()) {
1!
134
                descending = option.equalsIgnoreCase("descending");
1✔
135
            }
136
            option = options.getAttribute("empty");
1✔
137
            if (!option.isEmpty()) {
1!
138
                emptyLeast = option.equalsIgnoreCase("least");
1✔
139
            }
140
        }
141

142
        // create the input list to be sorted below
143
        final List<SortItem> items = new ArrayList<>(args[1].getItemCount());
1✔
144
        final Sequence[] params = new Sequence[1];
1✔
145
        SequenceIterator valuesIter = null;
1✔
146
        if (call == null)
1✔
147
            valuesIter = args[2].iterate();
1✔
148
        int c = 0;
1✔
149
        final int len = args[1].getItemCount();
1✔
150

151
        final int logChunk = 1 + (len / 20);
1✔
152

153
        for (final SequenceIterator nodesIter = args[1].iterate(); nodesIter.hasNext(); ) {
1✔
154
            final NodeValue nv = (NodeValue) nodesIter.nextItem();
1✔
155
            if (nv.getImplementationType() == NodeValue.IN_MEMORY_NODE)
1!
156
                throw new XPathException(this, "Cannot create order-index on an in-memory node");
×
157
            final NodeProxy node = (NodeProxy) nv;
1✔
158
            final SortItem si = new SortItemImpl(node);
1✔
159

160

161
            if (LOG.isDebugEnabled() && ++c % logChunk == 0) {
1!
162
                LOG.debug("Storing item {} out of {} to sort index.", c, len);
×
163
            }
164

165
            if (call != null) {
1✔
166
                // call the callback function to get value
167
                params[0] = node;
1✔
168
                final Sequence r = call.evalFunction(contextSequence, null, params);
1✔
169
                if (!r.isEmpty()) {
1✔
170
                    AtomicValue v = r.itemAt(0).atomize();
1✔
171
                    if (v.getType() == Type.UNTYPED_ATOMIC)
1!
172
                        v = v.convertTo(Type.STRING);
1✔
173
                    si.setValue(v);
1✔
174
                }
175
            } else {
1✔
176
                // no callback, take value from second sequence
177
                AtomicValue v = valuesIter.nextItem().atomize();
1✔
178
                if (v.getType() == Type.UNTYPED_ATOMIC)
1!
179
                    v = v.convertTo(Type.STRING);
×
180
                si.setValue(v);
1✔
181
            }
182
            items.add(si);
1✔
183
        }
1✔
184
        // sort the set
185
        FastQSort.sort(items, 0, items.size() - 1);
1✔
186
        // create the index
187
        final SortIndexWorker index = (SortIndexWorker)
1✔
188
                context.getBroker().getIndexController().getWorkerByIndexId(SortIndex.ID);
1✔
189
        try {
190
            index.createIndex(id, items);
1✔
191
        } catch (final EXistException e) {
×
192
            throw new XPathException(this, e.getMessage(), e);
×
193
        } catch (final LockException e) {
×
194
            throw new XPathException(this, "Caught lock error while creating index. Giving up.", e);
×
195
        }
1✔
196

197
        return Sequence.EMPTY_SEQUENCE;
1✔
198
    }
199

200
    private class SortItemImpl implements SortItem {
201

202
        NodeProxy node;
203
        AtomicValue value = AtomicValue.EMPTY_VALUE;
1✔
204

205
        public SortItemImpl(final NodeProxy node) {
1✔
206
            this.node = node;
1✔
207
        }
1✔
208

209
        public NodeProxy getNode() {
210
            return node;
1✔
211
        }
212

213
        public AtomicValue getValue() {
214
            return value;
1✔
215
        }
216

217
        public void setValue(final AtomicValue value) {
218
            if (value.hasOne())
1!
219
                this.value = value;
1✔
220
        }
1✔
221

222
        public int compareTo(final SortItem other) {
223
            int cmp = 0;
1✔
224
            final AtomicValue a = this.value;
1✔
225
            final AtomicValue b = other.getValue();
1✔
226
            final boolean aIsEmpty = (a.isEmpty() || (Type.subTypeOfUnion(a.getType(), Type.NUMERIC) && ((NumericValue) a).isNaN()));
1!
227
            final boolean bIsEmpty = (b.isEmpty() || (Type.subTypeOfUnion(b.getType(), Type.NUMERIC) && ((NumericValue) b).isNaN()));
1!
228
            if (aIsEmpty) {
1✔
229
                if (bIsEmpty)
1!
230
                    // both values are empty
231
                    return Constants.EQUAL;
×
232
                else if (emptyLeast)
1✔
233
                    cmp = Constants.INFERIOR;
1✔
234
                else
235
                    cmp = Constants.SUPERIOR;
1✔
236
            } else if (bIsEmpty) {
1✔
237
                // we don't need to check for equality since we know a is not empty
238
                if (emptyLeast)
1✔
239
                    cmp = Constants.SUPERIOR;
1✔
240
                else
241
                    cmp = Constants.INFERIOR;
1✔
242
            } else if (a == AtomicValue.EMPTY_VALUE && b != AtomicValue.EMPTY_VALUE) {
1!
243
                if (emptyLeast)
×
244
                    cmp = Constants.INFERIOR;
×
245
                else
246
                    cmp = Constants.SUPERIOR;
×
247
            } else if (b == AtomicValue.EMPTY_VALUE && a != AtomicValue.EMPTY_VALUE) {
1!
248
                if (emptyLeast)
×
249
                    cmp = Constants.SUPERIOR;
×
250
                else
251
                    cmp = Constants.INFERIOR;
×
252
            } else
253
                cmp = a.compareTo(b);
1✔
254
            if (descending)
1!
255
                cmp = cmp * -1;
×
256
            return cmp;
1✔
257
        }
258
    }
259
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc