Skip to content

Commit bfda343

Browse files
committed
Add bulk DatasetAccess.deleteBlocks() and use in N5Writer
1 parent a118d4f commit bfda343

4 files changed

Lines changed: 127 additions & 8 deletions

File tree

src/main/java/org/janelia/saalfeldlab/n5/GsonKeyValueN5Writer.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,4 +301,15 @@ default boolean deleteBlock(
301301
final PositionValueAccess posKva = PositionValueAccess.fromKva(getKeyValueAccess(), getURI(), N5URI.normalizeGroupPath(path), datasetAttributes);
302302
return datasetAttributes.getDatasetAccess().deleteBlock(posKva, gridPosition);
303303
}
304+
305+
@Override
306+
default boolean deleteBlocks(
307+
final String path,
308+
final List<long[]> gridPositions) throws N5Exception {
309+
310+
final String normalPath = N5URI.normalizeGroupPath(path);
311+
final DatasetAttributes datasetAttributes = getDatasetAttributes(normalPath);
312+
final PositionValueAccess posKva = PositionValueAccess.fromKva(getKeyValueAccess(), getURI(), N5URI.normalizeGroupPath(path), datasetAttributes);
313+
return datasetAttributes.getDatasetAccess().deleteBlocks(posKva, gridPositions);
314+
}
304315
}

src/main/java/org/janelia/saalfeldlab/n5/shard/DatasetAccess.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,14 @@
2828
*/
2929
package org.janelia.saalfeldlab.n5.shard;
3030

31-
import java.util.ArrayList;
32-
import java.util.Collection;
33-
import java.util.Collections;
3431
import java.util.List;
35-
import java.util.TreeMap;
3632
import java.util.concurrent.ExecutionException;
3733
import java.util.concurrent.ExecutorService;
3834
import org.janelia.saalfeldlab.n5.DataBlock;
3935
import org.janelia.saalfeldlab.n5.N5Exception;
4036
import org.janelia.saalfeldlab.n5.N5Exception.N5IOException;
4137
import org.janelia.saalfeldlab.n5.N5Writer.DataBlockSupplier;
4238
import org.janelia.saalfeldlab.n5.shard.Nesting.NestedGrid;
43-
import org.janelia.saalfeldlab.n5.shard.Nesting.NestedPosition;
4439

4540
/**
4641
* Wrap an instantiated DataBlock/shard codec hierarchy to implement (single and
@@ -99,8 +94,7 @@ public interface DatasetAccess<T> {
9994

10095
boolean deleteBlock(PositionValueAccess pva, long[] gridPosition) throws N5IOException;
10196

102-
// TODO:
103-
// boolean deleteBlocks(PositionValueAccess pva, List<long[]> positions) throws N5IOException;
97+
boolean deleteBlocks(PositionValueAccess pva, List<long[]> positions) throws N5IOException;
10498

10599
/**
106100
*

src/main/java/org/janelia/saalfeldlab/n5/shard/DefaultDatasetAccess.java

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ public boolean deleteBlock(final PositionValueAccess pva, final long[] gridPosit
387387
throw new N5Exception("The shard at " + Arrays.toString(key) + " could not be deleted.", e);
388388
}
389389
} else {
390-
final ReadData existingData = pva.get(key);
390+
final ReadData existingData = pva.get(key); // TODO: use getExistingReadData() instead !?
391391
final ReadData modifiedData = deleteBlockRecursive(existingData, position, grid.numLevels() - 1);
392392
if (existingData != null && modifiedData == null) {
393393
return pva.remove(key);
@@ -438,6 +438,115 @@ private ReadData deleteBlockRecursive(
438438
}
439439
}
440440

441+
//
442+
// -- deleteBlocks --------------------------------------------------------
443+
444+
@Override
445+
public boolean deleteBlocks(final PositionValueAccess pva, final List<long[]> gridPositions) throws N5IOException {
446+
447+
boolean deleted = false;
448+
449+
// for non-sharded datasets, just delete the blocks individually
450+
if (grid.numLevels() == 1) {
451+
for (long[] pos : gridPositions) {
452+
deleted |= deleteBlock(pva, pos);
453+
}
454+
return deleted;
455+
}
456+
457+
// Create a list of DataBlockRequests and sort it such that requests
458+
// from the same (nested) shard are grouped contiguously.
459+
// Despite the name, createReadRequests() works for delete requests as well ...
460+
final DataBlockRequests<T> requests = createReadRequests(gridPositions);
461+
requests.removeDuplicates();
462+
463+
final List<DataBlockRequests<T>> split = requests.split();
464+
for (final DataBlockRequests<T> subRequests : split) {
465+
final boolean writeFully = subRequests.coversShard();
466+
final long[] key = subRequests.relativeGridPosition();
467+
final ReadData existingData = writeFully ? null : getExistingReadData(pva, key);
468+
final ReadData modifiedData = deleteBlocksRecursive(existingData, subRequests);
469+
if (existingData != null && modifiedData == null) {
470+
deleted |= pva.remove(key);
471+
} else if (modifiedData != existingData) {
472+
pva.put(key, modifiedData);
473+
deleted = true;
474+
}
475+
}
476+
477+
return deleted;
478+
}
479+
480+
/**
481+
* Bulk Delete operation on a shard.
482+
*
483+
* @param existingReadData encoded existing shard data (to decode and partially override)
484+
* @param requests for blocks within the shard to be deleted
485+
*/
486+
private ReadData deleteBlocksRecursive(
487+
final ReadData existingReadData, // may be null
488+
final DataBlockRequests<T> requests
489+
) {
490+
assert !requests.requests.isEmpty();
491+
assert requests.level > 0;
492+
493+
if (existingReadData == null) {
494+
return null;
495+
}
496+
497+
final int level = requests.level();
498+
@SuppressWarnings("unchecked")
499+
final BlockCodec<RawShard> codec = (BlockCodec<RawShard>) codecs[level];
500+
final long[] gridPos = requests.gridPosition();
501+
final RawShard shard = codec.decode(existingReadData, gridPos).getData();
502+
503+
boolean modified = false;
504+
boolean shardElementSetToNull = false;
505+
if ( level == 1 ) {
506+
// Base case, delete the blocks
507+
for (final DataBlockRequest<T> request : requests) {
508+
final long[] elementPos = request.position.relative(0);
509+
if (shard.getElementData(elementPos) != null) {
510+
shard.setElementData(null, elementPos);
511+
modified = true;
512+
shardElementSetToNull = true;
513+
}
514+
}
515+
} else { // level > 1
516+
final List<DataBlockRequests<T>> split = requests.split();
517+
for (final DataBlockRequests<T> subRequests : split) {
518+
final boolean writeFully = subRequests.coversShard();
519+
final long[] elementPos = subRequests.relativeGridPosition();
520+
final ReadData existingElementData = writeFully ? null : shard.getElementData(elementPos);
521+
final ReadData modifiedElementData = deleteBlocksRecursive(existingElementData, subRequests);
522+
if (modifiedElementData != existingElementData) {
523+
shard.setElementData(modifiedElementData, elementPos);
524+
modified = true;
525+
shardElementSetToNull |= (modifiedElementData == null);
526+
}
527+
}
528+
}
529+
530+
if (!modified) {
531+
// No nested shard or DataBlock was modified.
532+
// This shard remains unchanged.
533+
return existingReadData;
534+
}
535+
536+
if (shardElementSetToNull) {
537+
// At least one DataBlock or nested shard was removed.
538+
// Check whether this shard becomes empty.
539+
if (shard.index().allElementsNull()) {
540+
// This shard is empty and should be removed.
541+
return null;
542+
}
543+
}
544+
545+
return codec.encode(new RawShardDataBlock(gridPos, shard));
546+
}
547+
548+
549+
441550
//
442551
// -- readShard -----------------------------------------------------------
443552

src/test/java/org/janelia/saalfeldlab/n5/http/HttpReaderFsWriter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,11 @@ public <W extends GsonKeyValueN5Writer, R extends GsonKeyValueN5Reader> HttpRead
275275
return writer.deleteBlock(datasetPath, gridPosition);
276276
}
277277

278+
@Override public boolean deleteBlocks(String datasetPath, List<long[]> gridPositions) throws N5Exception {
279+
280+
return writer.deleteBlocks(datasetPath, gridPositions);
281+
}
282+
278283
@Override public void writeSerializedBlock(Serializable object, String datasetPath, DatasetAttributes datasetAttributes, long... gridPosition) throws N5Exception {
279284

280285
writer.writeSerializedBlock(object, datasetPath, datasetAttributes, gridPosition);

0 commit comments

Comments
 (0)