Skip to content

Commit ce3bc0c

Browse files
authored
Feat/flatten (#226)
2 parents d7714ac + 5fd6c22 commit ce3bc0c

File tree

9 files changed

+369
-134
lines changed

9 files changed

+369
-134
lines changed

docs/docs/sql-syntax/objects.md

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ Specifying `$$ROOT` as a column alias sets the value to root object but only wor
77
???+ example "Example `$$ROOT` usage"
88

99
```sql
10-
SELECT
11-
t AS `$$ROOT`
12-
FROM
10+
SELECT
11+
t AS `$$ROOT`
12+
FROM
1313
(
14-
SELECT
14+
SELECT
1515
id
1616
,`First Name`
1717
,`Last Name`
18-
,LENGTHOFARRAY(Rentals,'id') AS numRentals
19-
FROM customers)
18+
,LENGTHOFARRAY(Rentals,'id') AS numRentals
19+
FROM customers)
2020
AS t
2121
```
2222

@@ -27,9 +27,9 @@ Only available in aggregates. Use a `SELECT` without specifying a table to creat
2727
???+ example "Creating a new object"
2828

2929
```sql
30-
SELECT
31-
(SELECT id,`First Name` AS Name) AS t
32-
FROM
30+
SELECT
31+
(SELECT id,`First Name` AS Name) AS t
32+
FROM
3333
customers
3434
```
3535

@@ -38,18 +38,16 @@ Create a new Object and assign to root
3838
???+ example "Creating a new object and assigning to root"
3939

4040
```sql
41-
SELECT
41+
SELECT
4242
(SELECT id,`First Name` AS Name) AS t1
4343
,(SELECT id,`Last Name` AS LastName) AS t2
44-
,MERGE_OBJECTS(t1,t2) AS `$$ROOT`
45-
FROM
44+
,MERGE_OBJECTS(t1,t2) AS `$$ROOT`
45+
FROM
4646
customers
4747
```
4848

4949
## Supported Object Functions
5050

51-
52-
5351
### PARSE_JSON
5452

5553
`PARSE_JSON(expr)`
@@ -59,19 +57,20 @@ Parses the JSON string. Use in conjunction with `ARRAY_TO_OBJECT` to convert an
5957
???+ example "Example `PARSE_JSON` usage"
6058

6159
```sql
62-
SELECT
60+
SELECT
6361
id,
6462
ARRAY_TO_OBJECT(PARSE_JSON('[{"k":"val","v":1}]')) AS test
6563
FROM `customers`;
6664
```
65+
6766
### MERGE_OBJECTS
6867

6968
`MERGE_OBJECTS(expr)`
7069

7170
???+ example "Example `MERGE_OBJECTS` usage"
7271

7372
```sql
74-
SELECT
73+
SELECT
7574
id,
7675
MERGE_OBJECTS(`Address`,PARSE_JSON('{"val":1}')) AS test
7776
FROM `customers`;
@@ -80,7 +79,7 @@ Parses the JSON string. Use in conjunction with `ARRAY_TO_OBJECT` to convert an
8079
???+ example "Example `MERGE_OBJECTS` usage with sub select"
8180

8281
```sql
83-
SELECT
82+
SELECT
8483
id,
8584
MERGE_OBJECTS(`Address`,(SELECT 1 AS val)) AS test
8685
FROM `customers`;
@@ -115,27 +114,28 @@ Creates an empty object.
115114
FROM `customers`;
116115
```
117116

118-
[//]: # (todo add back when flatten implemented)
119-
[//]: # (### FLATTEN)
120-
121-
[//]: # ()
122-
[//]: # (`FLATTEN(field, prefix)`)
123-
124-
[//]: # ()
125-
[//]: # (Flattens an object into a set of fields.)
117+
### FLATTEN
126118

127-
[//]: # ()
128-
[//]: # (???+ example "Example `FLATTEN` usage")
119+
`FLATTEN(field, prefix)`
129120

130-
[//]: # ()
131-
[//]: # ( ```sql)
121+
Flattens an object into a set of fields. You can optionally add a
132122

133-
[//]: # ( SELECT)
123+
???+ example "Example `FLATTEN` usage"
134124

135-
[//]: # ( id,)
125+
```sql'
126+
SELECT
127+
id,
128+
FLATTEN(`address`,'addr_')
129+
FROM `customers`;
130+
```
136131

137-
[//]: # ( FLATTEN(`address`,'addr_'))
132+
???+ example "Example `FLATTEN` usage with unset"
138133

139-
[//]: # ( FROM `customers`;)
134+
```sql'
135+
SELECT
136+
id,
137+
FLATTEN(`address`,'addr_',true)
138+
FROM `customers`;
139+
```
140140

141-
[//]: # ( ```)
141+
> Will remove the `address` field from the output and will only have the `addr_` prefixed fields.

lib/MongoFunctions.js

Lines changed: 114 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -2586,103 +2586,120 @@ class AllowableFunctions {
25862586
},
25872587
},
25882588
// todo add flatten back, has a error with no as which we're handling for unset
2589-
// {
2590-
// name: 'flatten',
2591-
// allowQuery: false,
2592-
// parse: (parameters) => {
2593-
// // todo fix return type
2594-
//
2595-
// if (!$check.array(parameters))
2596-
// throw new Error('Invalid parameters for flatten');
2597-
// if (parameters.length !== 2) {
2598-
// throw new Error(
2599-
// `Invalid parameter length for flatten, should be two but was ${parameters.length}`
2600-
// );
2601-
// }
2602-
// const field = parameters[0];
2603-
// const prefix = parameters[1];
2604-
// if ($check.emptyString(field) || !$check.string(field)) {
2605-
// throw new Error(
2606-
// `The first parameter passed to flatten should be a non empty string but was ${field}`
2607-
// );
2608-
// }
2609-
// if ($check.emptyString(prefix) || !$check.string(prefix)) {
2610-
// throw new Error(
2611-
// `The second parameter passed to flatten should be a non empty string but was ${prefix}`
2612-
// );
2613-
// }
2614-
// // todo decide how to unset or if we should? Leave to user?
2615-
//
2616-
// if (field.indexOf('.') > -1) {
2617-
// const fieldParts = field.split('.');
2618-
// const fieldName = fieldParts
2619-
// .slice(0, fieldParts.length - 1)
2620-
// .join('.');
2621-
//
2622-
// return {
2623-
// $set: {
2624-
// [fieldName]: {
2625-
// $mergeObjects: [
2626-
// '$' + fieldName,
2627-
// {
2628-
// $arrayToObject: {
2629-
// $map: {
2630-
// input: {
2631-
// $objectToArray:
2632-
// '$' + field,
2633-
// },
2634-
// as: 'temp_flatten',
2635-
// in: {
2636-
// k: {
2637-
// $concat: [
2638-
// prefix,
2639-
// '$$temp_flatten.k',
2640-
// ],
2641-
// },
2642-
// v: '$$temp_flatten.v',
2643-
// },
2644-
// },
2645-
// },
2646-
// },
2647-
// ],
2648-
// },
2649-
// },
2650-
// };
2651-
// } else {
2652-
// return {
2653-
// $replaceRoot: {
2654-
// newRoot: {
2655-
// $mergeObjects: [
2656-
// '$$ROOT',
2657-
// {
2658-
// $arrayToObject: {
2659-
// $map: {
2660-
// input: {
2661-
// $objectToArray:
2662-
// '$' + field,
2663-
// },
2664-
// as: 'temp_flatten',
2665-
// in: {
2666-
// k: {
2667-
// $concat: [
2668-
// prefix,
2669-
// '$$temp_flatten.k',
2670-
// ],
2671-
// },
2672-
// v: '$$temp_flatten.v',
2673-
// },
2674-
// },
2675-
// },
2676-
// },
2677-
// ],
2678-
// },
2679-
// },
2680-
// };
2681-
// }
2682-
// },
2683-
// requiresAs: false,
2684-
// jsonSchemaReturnType: 'null',
2685-
// },
2589+
{
2590+
name: 'flatten',
2591+
allowQuery: true,
2592+
parse: (parameters) => {
2593+
// todo fix return type
2594+
2595+
if (!$check.array(parameters))
2596+
throw new Error(
2597+
'Invalid parameters for flatten, should be an array'
2598+
);
2599+
if (parameters.length < 2) {
2600+
throw new Error(
2601+
`Invalid parameter length for flatten, should be two but was ${parameters.length}`
2602+
);
2603+
}
2604+
const unset = !!AllowableFunctions._getLiteral(
2605+
parameters[2]
2606+
);
2607+
let field = parameters[0];
2608+
if (field.startsWith('$')) {
2609+
field = field.substring(1);
2610+
}
2611+
const prefix = AllowableFunctions._getLiteral(
2612+
parameters[1]
2613+
);
2614+
if ($check.emptyString(field) || !$check.string(field)) {
2615+
throw new Error(
2616+
`The first parameter passed to flatten should be a non empty string but was ${field}`
2617+
);
2618+
}
2619+
if ($check.emptyString(prefix) || !$check.string(prefix)) {
2620+
throw new Error(
2621+
`The second parameter passed to flatten should be a non empty string but was ${prefix}`
2622+
);
2623+
}
2624+
2625+
// todo decide how to unset or if we should? Leave to user?
2626+
2627+
if (field.indexOf('.') > -1) {
2628+
const fieldParts = field.split('.');
2629+
const fieldName = fieldParts
2630+
.slice(0, fieldParts.length - 1)
2631+
.join('.');
2632+
2633+
const result = {
2634+
$set: {
2635+
[fieldName]: {
2636+
$mergeObjects: [
2637+
'$' + fieldName,
2638+
{
2639+
$arrayToObject: {
2640+
$map: {
2641+
input: {
2642+
$objectToArray:
2643+
'$' + field,
2644+
},
2645+
as: 'temp_flatten',
2646+
in: {
2647+
k: {
2648+
$concat: [
2649+
prefix,
2650+
'$$temp_flatten.k',
2651+
],
2652+
},
2653+
v: '$$temp_flatten.v',
2654+
},
2655+
},
2656+
},
2657+
},
2658+
],
2659+
},
2660+
},
2661+
};
2662+
if (unset) {
2663+
result.unsetAfterReplaceOrSet = {$unset: [field]};
2664+
}
2665+
return result;
2666+
}
2667+
const result = {
2668+
$replaceRoot: {
2669+
newRoot: {
2670+
$mergeObjects: [
2671+
'$$ROOT',
2672+
{
2673+
$arrayToObject: {
2674+
$map: {
2675+
input: {
2676+
$objectToArray: '$' + field,
2677+
},
2678+
as: 'temp_flatten',
2679+
in: {
2680+
k: {
2681+
$concat: [
2682+
prefix,
2683+
'$$temp_flatten.k',
2684+
],
2685+
},
2686+
v: '$$temp_flatten.v',
2687+
},
2688+
},
2689+
},
2690+
},
2691+
],
2692+
},
2693+
},
2694+
};
2695+
if (unset) {
2696+
result.unsetAfterReplaceOrSet = {$unset: [field]};
2697+
}
2698+
return result;
2699+
},
2700+
requiresAs: false,
2701+
jsonSchemaReturnType: 'null',
2702+
},
26862703
/* #endregion */
26872704

26882705
// separate action

lib/make/createResultObject.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ function createResultObject() {
1919
countDistinct: null,
2020
windowFields: [],
2121
subQueryRootProjections: [],
22+
set: null,
23+
unsetAfterReplaceOrSet: null,
2224
};
2325
}
2426

lib/make/makeAggregatePipeline.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,12 @@ function makeAggregatePipeline(ast, context = {}) {
452452
if (result.count.length > 0) {
453453
result.count.forEach((countStep) => pipeline.push(countStep));
454454
}
455+
if (result.set) {
456+
pipeline.push(result.set);
457+
if (result.unsetAfterReplaceOrSet) {
458+
pipeline.push(result.unsetAfterReplaceOrSet);
459+
}
460+
}
455461
if (result.unset) {
456462
pipeline.push(result.unset);
457463
}
@@ -555,6 +561,9 @@ function makeAggregatePipeline(ast, context = {}) {
555561

556562
if (result.replaceRoot) {
557563
pipeline.push(result.replaceRoot);
564+
if (result.unsetAfterReplaceOrSet) {
565+
pipeline.push(result.unsetAfterReplaceOrSet);
566+
}
558567
}
559568

560569
if (result.unwind && result.unwind.length > 0) {

0 commit comments

Comments
 (0)