Skip to content

Commit dc8cff5

Browse files
authored
Support array field ops in upsert (#568)
Signed-off-by: ryjiang <jiangruiyi@gmail.com>
1 parent 33ec83b commit dc8cff5

10 files changed

Lines changed: 2451 additions & 59 deletions

File tree

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ on:
66
- main
77
- 2.4
88
- 2.5
9+
- 2.6
910
types: [opened, synchronize]
1011
paths-ignore:
1112
- '.github/**'
@@ -17,6 +18,7 @@ on:
1718
- main
1819
- 2.4
1920
- 2.5
21+
- 2.6
2022
paths-ignore:
2123
- '.github/**'
2224
- '*.md'

milvus/const/milvus.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,13 @@ export enum DataType {
303303
Struct = 201,
304304
}
305305

306+
// partial update operation enum for array fields in upsert
307+
export enum FieldPartialUpdateOpType {
308+
REPLACE = 0,
309+
ARRAY_APPEND = 1,
310+
ARRAY_REMOVE = 2,
311+
}
312+
306313
export enum PlaceholderType {
307314
None = 0,
308315
BinaryVector = 100,

milvus/grpc/Data.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ import {
7777
GetQuerySegmentInfoResponse,
7878
SearchData,
7979
FloatVector,
80+
FieldPartialUpdateOpType,
81+
FieldPartialUpdateOp,
8082
} from '../';
8183
import { Collection } from './Collection';
8284

@@ -142,6 +144,9 @@ export class Data extends Collection {
142144
throw new Error(ERROR_REASONS.INSERT_CHECK_FIELD_DATA_IS_REQUIRED);
143145
}
144146
const { collection_name } = data;
147+
const fieldOps = upsert
148+
? this.normalizeFieldOps((data as UpsertReq).field_ops)
149+
: [];
145150

146151
const describeReq = { collection_name, cache: enable_cache };
147152
if (data.db_name) {
@@ -228,7 +233,8 @@ export class Data extends Collection {
228233

229234
// Track which fields are present in the original row data for partial updates
230235
const isPartialUpdate =
231-
'partial_update' in data && data.partial_update === true;
236+
('partial_update' in data && data.partial_update === true) ||
237+
fieldOps.length > 0;
232238
const originalRowKeys = new Set<string>();
233239

234240
// Loop through each row and set the corresponding field values in the Map.
@@ -293,10 +299,9 @@ export class Data extends Collection {
293299
schema_timestamp:
294300
collectionInfo.update_timestamp_str ||
295301
(collectionInfo.update_timestamp as string | number | undefined),
296-
// Ensure partial_update is passed for upsert operations
297-
...(upsert && (data as UpsertReq).partial_update
298-
? { partial_update: true }
299-
: {}),
302+
// Ensure partial_update is passed for explicit partial updates and field_ops.
303+
...(upsert && isPartialUpdate ? { partial_update: true } : {}),
304+
...(upsert && fieldOps.length > 0 ? { field_ops: fieldOps } : {}),
300305
};
301306
/* istanbul ignore next if */
302307
if (data.skip_check_schema) {
@@ -478,6 +483,35 @@ export class Data extends Collection {
478483
return promise;
479484
}
480485

486+
private normalizeFieldOps(
487+
fieldOps?: FieldPartialUpdateOp[]
488+
): { field_name: string; op: keyof typeof FieldPartialUpdateOpType }[] {
489+
if (!fieldOps || fieldOps.length === 0) {
490+
return [];
491+
}
492+
493+
return fieldOps.map(({ field_name, op }) => {
494+
if (!field_name) {
495+
throw new Error('field_ops field_name is required');
496+
}
497+
498+
if (typeof op === 'number') {
499+
const opName = FieldPartialUpdateOpType[op] as
500+
| keyof typeof FieldPartialUpdateOpType
501+
| undefined;
502+
if (typeof opName === 'undefined') {
503+
throw new Error(`unsupported field partial update op: ${op}`);
504+
}
505+
return { field_name, op: opName };
506+
}
507+
508+
if (typeof FieldPartialUpdateOpType[op] === 'undefined') {
509+
throw new Error(`unsupported field partial update op: ${op}`);
510+
}
511+
return { field_name, op };
512+
});
513+
}
514+
481515
/**
482516
* Delete entities in a Milvus collection.
483517
*

0 commit comments

Comments
 (0)