@@ -35,8 +35,8 @@ def _query_rel_to_abs(x_sel, rel_indices, rank):
3535 """
3636 slices = x_sel .slices
3737 if len (rel_indices ) == 0 :
38- return np .zeros ((0 , rank ), dtype = 'u8 ' )
39- abs_result = np .zeros ((len (rel_indices ), rank ), dtype = 'u8 ' )
38+ return np .zeros ((0 , rank ), dtype = 'int64 ' )
39+ abs_result = np .zeros ((len (rel_indices ), rank ), dtype = 'int64 ' )
4040 for d in range (rank ):
4141 s = slices [d ]
4242 if isinstance (s , slice ):
@@ -1110,13 +1110,16 @@ def getChunkIterator(self, dset_id, sel=None):
11101110 """
11111111 return ChunkIterator (self , dset_id , sel = sel )
11121112
1113- def queryDataset (self , dset_id , query , sel = None , limit = 0 ):
1113+ def queryDataset (self , dset_id , query , sel = None , limit = 0 , update_value = None ):
11141114 """
11151115 Query the given dataset using the selection and query expression
11161116 If sel is provided, only the elements in the selection will be queried,
11171117 otherwise the entire dataset will be queried.
11181118 If limit is provided, only the first limit number of elements that match the query will be returned.
1119-
1119+ If update_value is provied, elements matching the query (up to limit elements if limit is non-zero)
1120+ will be updated to the given value. For a compound dtype, update_value may be a dict mapping
1121+ field names to the value to set for that field - only those fields are modified, and the rest
1122+ of each matching element is left unchanged.
11201123 Return a numpy array of indices for the elements that match the query
11211124 """
11221125
@@ -1155,10 +1158,75 @@ def queryReader(dset_id, query, sel=None, limit=0):
11551158 if limit > 0 and nhits >= limit :
11561159 break
11571160
1158- result = np .concatenate (hits , axis = 0 ) if hits else np .zeros ((0 , rank ), dtype = 'u8 ' )
1161+ result = np .concatenate (hits , axis = 0 ) if hits else np .zeros ((0 , rank ), dtype = 'int64 ' )
11591162 if limit > 0 and len (result ) > limit :
11601163 result = result [:limit ]
11611164 return result
1165+
1166+ def queryWriter (dset_id , query , sel = None , limit = 0 , update_value = None ):
1167+ result = None
1168+ try :
1169+ result = self .writer .queryDataset (dset_id , query , sel = sel , limit = limit , update_value = update_value )
1170+ except NotImplementedError :
1171+ # This reader doesn't support queryDataset
1172+ pass
1173+
1174+ if result is None :
1175+ rank = len (sel .shape )
1176+ try :
1177+ chunk_iter = ChunkIterator (self , dset_id , sel = sel )
1178+ except ValueError :
1179+ # ChunkIterator doesn't support this selection (e.g. a fancy/point
1180+ # selection, or a scalar dataset) - fall back to querying the
1181+ # entire selection at once
1182+ arr = self .getDatasetValues (dset_id , sel )
1183+ result = arrayQuery (query , arr , limit = limit )
1184+ result = _query_rel_to_abs (sel , result , rank )
1185+ else :
1186+ # query the dataset chunk by chunk so the whole selection is
1187+ # never loaded into memory at once
1188+ hits = []
1189+ nhits = 0
1190+ for chunk_arr in chunk_iter :
1191+ chunk_rel = arrayQuery (query , chunk_arr )
1192+ if len (chunk_rel ) == 0 :
1193+ continue
1194+ hits .append (_query_rel_to_abs (chunk_iter .sel , chunk_rel , rank ))
1195+ nhits += len (chunk_rel )
1196+ if limit > 0 and nhits >= limit :
1197+ break
1198+
1199+ result = np .concatenate (hits , axis = 0 ) if hits else np .zeros ((0 , rank ), dtype = 'int64' )
1200+ if limit > 0 and len (result ) > limit :
1201+ result = result [:limit ]
1202+
1203+ if update_value is not None and len (result ) > 0 :
1204+ # update the values at the matching indices
1205+ dtype = self .getDtype (self .getObjectById (dset_id ))
1206+ if isinstance (update_value , dict ):
1207+ # a dict maps field names to the value to set for that field -
1208+ # only those fields are modified, the rest of each record is
1209+ # left as-is
1210+ if len (dtype ) == 0 :
1211+ raise TypeError ("update_value dict is only supported for compound dtypes" )
1212+ fields = [f for f in dtype .names if f in update_value ]
1213+ if not fields :
1214+ raise ValueError (
1215+ f"None of the requested fields { list (update_value .keys ())} found in dtype" )
1216+ if len (fields ) == 1 :
1217+ value = np .asarray (update_value [fields [0 ]], dtype = dtype .fields [fields [0 ]][0 ])
1218+ else :
1219+ value_dtype = np .dtype ([(f , dtype .fields [f ][0 ]) for f in fields ])
1220+ value = np .zeros ((), dtype = value_dtype )
1221+ for f in fields :
1222+ value [f ] = update_value [f ]
1223+ update_sel = selections .select (sel .shape , result , fields = fields )
1224+ else :
1225+ value = np .asarray (update_value , dtype = dtype )
1226+ update_sel = selections .select (sel .shape , result )
1227+ self .setDatasetValues (dset_id , update_sel , value )
1228+ return result
1229+
11621230 #
11631231 # start of queryDataset
11641232 #
@@ -1180,6 +1248,12 @@ def queryReader(dset_id, query, sel=None, limit=0):
11801248 if sel is None :
11811249 sel = selections .select (dims , ...)
11821250
1251+ if update_value is not None :
1252+ # do flush so we can be sure to do an atomic operation if the writer supports it
1253+ self .flush ()
1254+ results = queryWriter (dset_id , query , sel = sel , limit = limit , update_value = update_value )
1255+ return results
1256+
11831257 updates = self ._getDatasetUpdates (dset_id )
11841258
11851259 if sel .shape != dims :
@@ -1213,13 +1287,25 @@ def queryReader(dset_id, query, sel=None, limit=0):
12131287 result_mask &= ~ inter_mask
12141288
12151289 # Query the updated values at the intersection
1216- local_sel = selections .translate (update_sel , x_sel )
1217- x_vals = update_val [local_sel .slices ]
1218- x_rel = arrayQuery (query , x_vals )
1219-
1220- if len (x_rel ) > 0 :
1221- abs_result = _query_rel_to_abs (x_sel , x_rel , rank )
1222- result_mask [tuple (abs_result [:, d ].astype (int ) for d in range (rank ))] = True
1290+ if update_sel .select_type == selections .H5S_SEL_POINTS :
1291+ # update_val is 1-D, indexed by position in update_sel (not
1292+ # sliceable via translate(), which only handles hyperslabs)
1293+ x_points = list (selections ._iter_points (x_sel ))
1294+ if not x_points :
1295+ continue
1296+ upd_pt_to_idx = {pt : j for j , pt in enumerate (selections ._iter_points (update_sel ))}
1297+ x_vals = update_val [[upd_pt_to_idx [pt ] for pt in x_points ]]
1298+ x_rel = arrayQuery (query , x_vals )
1299+ if len (x_rel ) > 0 :
1300+ abs_coords = np .array ([x_points [i ] for i in x_rel [:, 0 ]], dtype = 'u8' )
1301+ result_mask [tuple (abs_coords [:, d ] for d in range (rank ))] = True
1302+ else :
1303+ local_sel = selections .translate (update_sel , x_sel )
1304+ x_vals = update_val [local_sel .slices ]
1305+ x_rel = arrayQuery (query , x_vals )
1306+ if len (x_rel ) > 0 :
1307+ abs_result = _query_rel_to_abs (x_sel , x_rel , rank )
1308+ result_mask [tuple (abs_result [:, d ].astype (int ) for d in range (rank ))] = True
12231309
12241310 indices = np .argwhere (result_mask )
12251311 if limit > 0 and len (indices ) > limit :
@@ -1276,7 +1362,11 @@ def setDatasetValues(self, dset_id, sel, arr):
12761362 raise TypeError (f"arr.dtype { src_dt } doesn't match expected dtype { expected_dt } " )
12771363
12781364 if sel .select_type == selections .H5S_SEL_POINTS :
1279- if sel .nselect != arr .shape [0 ]:
1365+ if arr .shape == ():
1366+ # broadcast the scalar to match the number of selected points, so
1367+ # the stored update value can be indexed like any other point update
1368+ arr = np .full (sel .mshape , arr [()], dtype = arr .dtype )
1369+ elif sel .nselect != arr .shape [0 ]:
12801370 raise TypeError ("Selection shape does not match number of points" )
12811371 elif sel .select_type == selections .H5S_SEL_FANCY :
12821372 if arr .shape != sel .mshape :
@@ -1289,7 +1379,7 @@ def setDatasetValues(self, dset_id, sel, arr):
12891379 raise TypeError ("Selection shape does not match dataset shape" )
12901380 # Allow scalar arrays when writing a field-restricted selection
12911381 # (the scalar will be broadcast to all selected positions).
1292- if arr .shape != () or sel . fields is None :
1382+ if arr .shape != ():
12931383 if 0 < arr .ndim < len (dims ):
12941384 # arr has fewer dims than the dataset rank (e.g. a 1-D array
12951385 # written to a slice of a 3-D dataset). Validate against
0 commit comments