Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,15 @@ public Stream<Entry<BigId, ICommandStatus>> selectEntries(CommandStatusFilter fi
.flatMap(entry -> getStatusByCommand(entry.getKey()));
}

// create comparator for time sort
Comparator<Entry<StatusKey, ICommandStatus>> comparator = Comparator.comparing(e -> e.getKey().reportTime);
if (filter.getReportTime() != null && filter.getReportTime().isDescendingOrder())
comparator = comparator.reversed();

// filter with predicate and apply limit
resultStream = resultStream
.filter(e -> filter.test(e.getValue()))
.sorted(comparator)
.limit(filter.getLimit());

// casting is ok since keys are subtypes of BigId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,16 @@ else if (filter.getCommandStreamFilter() == null)
.anyMatch(status -> filter.getStatusFilter().test(status.getValue()));
});
}

// create comparator for time sort
Comparator<Entry<CmdKey, ICommandData>> comparator = Comparator.comparing(e -> e.getKey().issueTime);
if (filter.getIssueTime() != null && filter.getIssueTime().isDescendingOrder())
comparator = comparator.reversed();

// filter with predicate and apply limit
// filter with predicate, sort by time and apply limit
resultStream = resultStream
.filter(e -> filter.test(e.getValue()))
.sorted(comparator)
.limit(filter.getLimit());

// casting is ok since keys are subtypes of BigId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ Stream<Entry<ObsKey, IObsData>> getObsByDataStreamAndFoi(BigId dataStreamID, Big
public Stream<Entry<BigId, IObsData>> selectEntries(ObsFilter filter, Set<ObsField> fields)
{
Stream<Entry<ObsKey, IObsData>> resultStream = null;

// fetch obs directly in case of filtering by internal IDs
if (filter.getInternalIDs() != null)
{
Expand Down Expand Up @@ -272,19 +272,31 @@ else if (filter.getFoiFilter() != null && filter.getDataStreamFilter() == null)
if (dataStreamIDs.isEmpty())
return Stream.empty();

// create set of selected FOIs
Set<BigId> foiIDs = DataStoreUtils.selectFeatureIDs(foiStore, filter.getFoiFilter())
.collect(Collectors.toSet());
if (foiIDs.isEmpty())
return Stream.empty();

// cross product between list of datastream IDs and foiIDs
resultStream = dataStreamIDs.stream()
.flatMap(dsID -> {
return DataStoreUtils.selectFeatureIDs(foiStore, filter.getFoiFilter())
return foiIDs.stream()
.flatMap(foiID -> {
return getObsByDataStreamAndFoi(dsID, foiID);
});
});
}

// create comparator for time sort
Comparator<Entry<ObsKey, IObsData>> comparator = Comparator.comparing(e -> e.getKey().phenomenonTime);
if (filter.getPhenomenonTime() != null && filter.getPhenomenonTime().isDescendingOrder())
comparator = comparator.reversed();

// filter with predicate and apply limit
// filter with predicate, sort by time and apply limit
resultStream = resultStream
.filter(e -> filter.test(e.getValue()))
.sorted(comparator)
.limit(filter.getLimit());

// casting is ok since keys are subtypes of BigId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -758,6 +759,69 @@ public void testSelectObsByDataStreamFilter() throws Exception
}


@Test
public void testSelectObsDescendingOrder() throws Exception
{
Stream<Entry<BigId, IObsData>> resultStream;
Map<BigId, IObsData> expectedResults;
ObsFilter filter;

var ds1 = addSimpleDataStream(bigId(1), "out1");
Instant startProc1Batch1 = Instant.parse("2015-06-23T18:24:15.233Z");
Map<BigId, IObsData> proc1Batch1 = addSimpleObsWithoutResultTime(ds1, bigId(23), startProc1Batch1, 10, 30*24*3600*1000L);
Instant startProc1Batch2 = Instant.parse("2018-02-11T08:12:06.897Z");
Map<BigId, IObsData> proc1Batch2 = addSimpleObsWithoutResultTime(ds1, bigId(46), startProc1Batch2, 3, 100*24*3600*1000L);
Instant startProc1Batch3 = Instant.parse("2025-06-23T18:24:15.233Z");
Map<BigId, IObsData> proc1Batch3 = addSimpleObsWithoutResultTime(ds1, BigId.NONE, startProc1Batch3, 10, 30*24*3600*1000L);

forceReadBackFromStorage();

// ds1 and all times
filter = new ObsFilter.Builder()
.withDataStreams(ds1)
.withPhenomenonTime()
.descendingOrder(true)
.done()
.build();
resultStream = obsStore.selectEntries(filter);
expectedResults = new LinkedHashMap<>();
expectedResults.putAll(proc1Batch1);
expectedResults.putAll(proc1Batch2);
expectedResults.putAll(proc1Batch3);
checkSelectedEntries(resultStream, expectedResults, filter);

// check order is descending
var obsList = obsStore.selectEntries(filter)
.map(e -> e.getValue())
.collect(Collectors.toList());
var sortedObsList = new ArrayList<>(obsList);
sortedObsList.sort(Comparator.comparing(obs -> obs.getPhenomenonTime()));
Collections.reverse(sortedObsList);
assertEquals(sortedObsList, obsList);

// ds1 and single FOI
filter = new ObsFilter.Builder()
.withDataStreams(ds1)
.withFois(bigId(23))
.withPhenomenonTime()
.descendingOrder(true)
.done()
.build();
resultStream = obsStore.selectEntries(filter);
expectedResults = new LinkedHashMap<>();
expectedResults.putAll(proc1Batch1);
checkSelectedEntries(resultStream, expectedResults, filter);

obsList = obsStore.selectEntries(filter)
.map(e -> e.getValue())
.collect(Collectors.toList());
sortedObsList = new ArrayList<>(obsList);
sortedObsList.sort(Comparator.comparing(obs -> obs.getPhenomenonTime()));
Collections.reverse(sortedObsList);
assertEquals(sortedObsList, obsList);
}


@Test(expected = IllegalStateException.class)
public void testErrorWithFoiFilterJoin() throws Exception
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/***************************** BEGIN LICENSE BLOCK ***************************

The contents of this file are subject to the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one
at http://mozilla.org/MPL/2.0/.

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.

Copyright (C) 2026 GeoRobotix Inc. All Rights Reserved.

******************************* END LICENSE BLOCK ***************************/

package org.h2.mvstore;

import java.util.Iterator;
import java.util.NoSuchElementException;


/**
* A cursor to iterate over elements in ascending order.
*
* @param <K> the key type
* @param <V> the value type
*/
public class DescendingCursor<K, V> implements Iterator<K> {
private final K to;
private CursorPos cursorPos;
private CursorPos keeper;
private K current;
private K last;
private V lastValue;
private Page lastPage;

public DescendingCursor(Page root, K from) {
this(root, from, null);
}

public DescendingCursor(Page root, K from, K to) {
this.cursorPos = traverseDown(root, from);
this.to = to;
}

@Override
@SuppressWarnings("unchecked")
public boolean hasNext() {
if (cursorPos != null) {
while (current == null) {
Page page = cursorPos.page;
int index = cursorPos.index;
if (index < 0) {
CursorPos tmp = cursorPos;
cursorPos = cursorPos.parent;
tmp.parent = keeper;
keeper = tmp;
if(cursorPos == null)
{
return false;
}
} else {
while (!page.isLeaf()) {
page = page.getChildPage(index);
int numKeys = page.isLeaf() ? page.getKeyCount() : page.map.getChildPageCount(page);
if (keeper == null) {
cursorPos = new CursorPos(page, numKeys-1, cursorPos);
} else {
CursorPos tmp = keeper;
keeper = keeper.parent;
tmp.parent = cursorPos;
tmp.page = page;
tmp.index = numKeys-1;
cursorPos = tmp;
}
index = numKeys-1;
}
if (index >= 0) {
K key = (K) page.getKey(index);
if (to != null && page.map.getKeyType().compare(key, to) < 0) {
return false;
}
current = last = key;
lastValue = (V) page.getValue(index);
lastPage = page;
}
}
--cursorPos.index;
}
}
return current != null;
}

@Override
public K next() {
if(!hasNext()) {
throw new NoSuchElementException();
}
current = null;
return last;
}

/**
* Get the last read key if there was one.
*
* @return the key or null
*/
public K getKey() {
return last;
}

/**
* Get the last read value if there was one.
*
* @return the value or null
*/
public V getValue() {
return lastValue;
}

/**
* Get the page where last retrieved key is located.
*
* @return the page
*/
Page getPage() {
return lastPage;
}

/**
* Skip over that many entries. This method is relatively fast (for this map
* implementation) even if many entries need to be skipped.
*
* @param n the number of entries to skip
*/
public void skip(long n) {
if (n < 10) {
while (n-- > 0 && hasNext()) {
next();
}
} else if(hasNext()) {
assert cursorPos != null;
CursorPos cp = cursorPos;
CursorPos parent;
while ((parent = cp.parent) != null) cp = parent;
Page root = cp.page;
@SuppressWarnings("unchecked")
MVMap<K, ?> map = (MVMap<K, ?>) root.map;
long index = map.getKeyIndex(next());
last = map.getKey(index + n);
this.cursorPos = traverseDown(root, last);
}
}

@Override
public void remove() {
throw DataUtils.newUnsupportedOperationException(
"Removal is not supported");
}

/**
* Fetch the next entry that is equal or larger than the given key, starting
* from the given page. This method retains the stack.
*
* @param p the page to start from
* @param key the key to search, null means search for the first key
*/
private static CursorPos traverseDown(Page p, Object key) {
CursorPos cursorPos = key == null ? p.getPrependCursorPos(null) : CursorPos.traverseDown(p, key);
if (cursorPos.index < 0) {
cursorPos.index = -cursorPos.index - 1;
// select previous instead of next
cursorPos.index--;
}
return cursorPos;
}
}

Loading
Loading