|
14 | 14 | # handles regauests to read/write chunk data |
15 | 15 | # |
16 | 16 |
|
| 17 | +import json |
17 | 18 | import numpy as np |
18 | 19 | import traceback |
19 | 20 | from aiohttp.web_exceptions import HTTPBadRequest, HTTPInternalServerError |
20 | | -from aiohttp.web_exceptions import HTTPNotFound, HTTPServiceUnavailable, HTTPNotImplemented |
| 21 | +from aiohttp.web_exceptions import HTTPNotFound, HTTPServiceUnavailable |
21 | 22 | from aiohttp.web import json_response, StreamResponse |
22 | 23 |
|
23 | 24 | from h5json.hdf5dtype import createDataType, getSubType |
@@ -53,10 +54,20 @@ async def PUT_Chunk(request): |
53 | 54 | bucket = None |
54 | 55 | input_arr = None |
55 | 56 | element_count = None |
| 57 | + limit = 0 |
56 | 58 |
|
57 | 59 | if "query" in params: |
58 | 60 | query = params["query"] |
59 | 61 | log.info(f"PUT_Chunk query: {query}") |
| 62 | + if "Limit" in params: |
| 63 | + param_limit = params["Limit"] |
| 64 | + try: |
| 65 | + limit = int(param_limit) |
| 66 | + except ValueError: |
| 67 | + msg = f"invalid Limit param: {param_limit}" |
| 68 | + log.warn(msg) |
| 69 | + raise HTTPBadRequest(reason=msg) |
| 70 | + log.debug(f"PUT_Chunk limit: {limit}") |
60 | 71 | chunk_id = request.match_info.get("id") |
61 | 72 | if not chunk_id: |
62 | 73 | msg = "Missing chunk id" |
@@ -184,13 +195,74 @@ async def PUT_Chunk(request): |
184 | 195 | raise HTTPNotFound() |
185 | 196 |
|
186 | 197 | if query: |
187 | | - # TBD: query+update support was dropped when chunkUtil.chunkQuery was |
188 | | - # removed in favor of h5json.query_util.arrayQuery. arrayQuery returns |
189 | | - # match coordinates rather than rows, so this needs to be rewritten to |
190 | | - # update chunk_arr at those coordinates and rebuild a response array. |
191 | | - msg = "PUT_Chunk with query is not currently supported" |
192 | | - log.error(msg) |
193 | | - raise HTTPNotImplemented(reason=msg) |
| 198 | + try: |
| 199 | + indices = arrayQuery(query, chunk_arr, selection=selection, limit=limit) |
| 200 | + except (TypeError, ValueError) as e: |
| 201 | + msg = f"query: {query} is not valid, got exception: {e}" |
| 202 | + log.warn(msg) |
| 203 | + raise HTTPBadRequest(reason=msg) |
| 204 | + |
| 205 | + log.debug(f"PUT_Chunk - query matched {len(indices)} elements") |
| 206 | + |
| 207 | + try: |
| 208 | + update_value = await request.json() |
| 209 | + except json.JSONDecodeError: |
| 210 | + msg = "Unable to load JSON body for query update" |
| 211 | + log.warn(msg) |
| 212 | + raise HTTPBadRequest(reason=msg) |
| 213 | + |
| 214 | + rank = len(chunk_arr.shape) |
| 215 | + fancy_index = tuple(indices[:, i] for i in range(rank)) |
| 216 | + |
| 217 | + if len(indices) > 0: |
| 218 | + # query_update is only allowed when the value is one element - |
| 219 | + # that element gets broadcast across all matching positions |
| 220 | + if select_dt.names: |
| 221 | + # compound type - value is a JSON object of field name to |
| 222 | + # value; only the given fields are updated, others are |
| 223 | + # left as-is |
| 224 | + if not isinstance(update_value, dict): |
| 225 | + msg = "expected a JSON object for compound type query update" |
| 226 | + log.warn(msg) |
| 227 | + raise HTTPBadRequest(reason=msg) |
| 228 | + for field_name, field_value in update_value.items(): |
| 229 | + if field_name not in select_dt.names: |
| 230 | + msg = f"field: {field_name} not found in dataset type" |
| 231 | + log.warn(msg) |
| 232 | + raise HTTPBadRequest(reason=msg) |
| 233 | + chunk_arr[field_name][fancy_index] = field_value |
| 234 | + else: |
| 235 | + # simple type - value is the (scalar) element itself |
| 236 | + if isinstance(update_value, dict) and "value" in update_value: |
| 237 | + update_value = update_value["value"] |
| 238 | + chunk_arr[fancy_index] = update_value |
| 239 | + is_dirty = True |
| 240 | + |
| 241 | + # return the global dataset indices of the matching elements - |
| 242 | + # the chunk's offset within the dataset (per dimension) is its |
| 243 | + # grid index times the chunk dims along that dimension |
| 244 | + chunk_index = getChunkIndex(chunk_id) |
| 245 | + offset = np.array([chunk_index[i] * dims[i] for i in range(rank)], dtype=indices.dtype) |
| 246 | + global_indices = indices + offset |
| 247 | + |
| 248 | + read_resp = arrayToBytes(global_indices) |
| 249 | + try: |
| 250 | + resp = StreamResponse() |
| 251 | + resp.headers["Content-Type"] = "application/octet-stream" |
| 252 | + resp.content_length = len(read_resp) |
| 253 | + await resp.prepare(request) |
| 254 | + await resp.write(read_resp) |
| 255 | + except Exception as e: |
| 256 | + log.error(f"Exception during binary data write: {e}") |
| 257 | + raise HTTPInternalServerError() |
| 258 | + finally: |
| 259 | + await resp.write_eof() |
| 260 | + |
| 261 | + if is_dirty or config.get("write_zero_chunks", default=False): |
| 262 | + save_chunk(app, chunk_id, dset_json, chunk_arr, bucket=bucket) |
| 263 | + |
| 264 | + log.response(request, resp=resp) |
| 265 | + return resp |
194 | 266 | else: |
195 | 267 | # regular chunk update |
196 | 268 | # check that the content_length is what we expect |
|
0 commit comments