Skip to content

Commit cf4c880

Browse files
authored
feat: Add support for dot notation on array fields of Parse Object (#9115)
1 parent 892052c commit cf4c880

File tree

4 files changed

+90
-4
lines changed

4 files changed

+90
-4
lines changed

Diff for: spec/ParseObject.spec.js

+64
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,70 @@ describe('Parse.Object testing', () => {
568568
);
569569
});
570570

571+
it_only_db('mongo')('can increment array nested fields', async () => {
572+
const obj = new TestObject();
573+
obj.set('items', [ { value: 'a', count: 5 }, { value: 'b', count: 1 } ]);
574+
await obj.save();
575+
obj.increment('items.0.count', 15);
576+
obj.increment('items.1.count', 4);
577+
await obj.save();
578+
expect(obj.toJSON().items[0].value).toBe('a');
579+
expect(obj.toJSON().items[1].value).toBe('b');
580+
expect(obj.toJSON().items[0].count).toBe(20);
581+
expect(obj.toJSON().items[1].count).toBe(5);
582+
const query = new Parse.Query(TestObject);
583+
const result = await query.get(obj.id);
584+
expect(result.get('items')[0].value).toBe('a');
585+
expect(result.get('items')[1].value).toBe('b');
586+
expect(result.get('items')[0].count).toBe(20);
587+
expect(result.get('items')[1].count).toBe(5);
588+
expect(result.get('items')).toEqual(obj.get('items'));
589+
});
590+
591+
it_only_db('mongo')('can increment array nested fields missing index', async () => {
592+
const obj = new TestObject();
593+
obj.set('items', []);
594+
await obj.save();
595+
obj.increment('items.1.count', 15);
596+
await obj.save();
597+
expect(obj.toJSON().items[0]).toBe(null);
598+
expect(obj.toJSON().items[1].count).toBe(15);
599+
const query = new Parse.Query(TestObject);
600+
const result = await query.get(obj.id);
601+
expect(result.get('items')[0]).toBe(null);
602+
expect(result.get('items')[1].count).toBe(15);
603+
expect(result.get('items')).toEqual(obj.get('items'));
604+
});
605+
606+
it('can query array nested fields', async () => {
607+
const objects = [];
608+
for (let i = 0; i < 10; i++) {
609+
const obj = new TestObject();
610+
obj.set('items', [i, { value: i }]);
611+
objects.push(obj);
612+
}
613+
await Parse.Object.saveAll(objects);
614+
let query = new Parse.Query(TestObject);
615+
query.greaterThan('items.1.value', 5);
616+
let result = await query.find();
617+
expect(result.length).toBe(4);
618+
619+
query = new Parse.Query(TestObject);
620+
query.lessThan('items.0', 3);
621+
result = await query.find();
622+
expect(result.length).toBe(3);
623+
624+
query = new Parse.Query(TestObject);
625+
query.equalTo('items.0', 5);
626+
result = await query.find();
627+
expect(result.length).toBe(1);
628+
629+
query = new Parse.Query(TestObject);
630+
query.notEqualTo('items.0', 5);
631+
result = await query.find();
632+
expect(result.length).toBe(9);
633+
});
634+
571635
it('addUnique with object', function (done) {
572636
const x1 = new Parse.Object('X');
573637
x1.set('stuff', [1, { hello: 'world' }, { foo: 'bar' }]);

Diff for: src/Adapters/Storage/Postgres/PostgresStorageAdapter.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ const toPostgresSchema = schema => {
175175
return schema;
176176
};
177177

178+
const isArrayIndex = (arrayIndex) => Array.from(arrayIndex).every(c => c >= '0' && c <= '9');
179+
178180
const handleDotFields = object => {
179181
Object.keys(object).forEach(fieldName => {
180182
if (fieldName.indexOf('.') > -1) {
@@ -207,7 +209,11 @@ const transformDotFieldToComponents = fieldName => {
207209
if (index === 0) {
208210
return `"${cmpt}"`;
209211
}
210-
return `'${cmpt}'`;
212+
if (isArrayIndex(cmpt)) {
213+
return Number(cmpt);
214+
} else {
215+
return `'${cmpt}'`;
216+
}
211217
});
212218
};
213219

Diff for: src/Controllers/DatabaseController.js

+8
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,14 @@ class DatabaseController {
18511851
// only valid ops that produce an actionable result
18521852
// the op may have happened on a keypath
18531853
this._expandResultOnKeyPath(response, key, result);
1854+
// Revert array to object conversion on dot notation for arrays (e.g. "field.0.key")
1855+
if (key.includes('.')) {
1856+
const [field, index] = key.split('.');
1857+
const isArrayIndex = Array.from(index).every(c => c >= '0' && c <= '9');
1858+
if (isArrayIndex && Array.isArray(result[field]) && !Array.isArray(response[field])) {
1859+
response[field] = result[field];
1860+
}
1861+
}
18541862
}
18551863
});
18561864
return Promise.resolve(response);

Diff for: src/Controllers/SchemaController.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -1096,9 +1096,17 @@ export default class SchemaController {
10961096
maintenance?: boolean
10971097
) {
10981098
if (fieldName.indexOf('.') > 0) {
1099-
// subdocument key (x.y) => ok if x is of type 'object'
1100-
fieldName = fieldName.split('.')[0];
1101-
type = 'Object';
1099+
// "<array>.<index>" for Nested Arrays
1100+
// "<embedded document>.<field>" for Nested Objects
1101+
// JSON Arrays are treated as Nested Objects
1102+
const [x, y] = fieldName.split('.');
1103+
fieldName = x;
1104+
const isArrayIndex = Array.from(y).every(c => c >= '0' && c <= '9');
1105+
if (isArrayIndex && !['sentPerUTCOffset', 'failedPerUTCOffset'].includes(fieldName)) {
1106+
type = 'Array';
1107+
} else {
1108+
type = 'Object';
1109+
}
11021110
}
11031111
let fieldNameToValidate = `${fieldName}`;
11041112
if (maintenance && fieldNameToValidate.charAt(0) === '_') {

0 commit comments

Comments
 (0)