Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/docs/sql-syntax/arrays.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,20 @@ Returns true when all elements in the array are true.
FROM `customers`;
```

### JOIN

`JOIN(array expr, delimiter)`

Joins all values into a string.

???+ example "Example `JOIN` usage"

```sql
SELECT id,
JOIN((SELECT Name FROM Rentals),',') AS RentalNames
FROM customers;
```

### ANY_ELEMENT_TRUE

`ANY_ELEMENT_TRUE(array expr)`
Expand Down
108 changes: 107 additions & 1 deletion lib/optimizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const projectIsRoot = require('./projectIsRoot');
const projectIsSimple = require('./projectIsSimple');
const lodash = require('lodash');
const arraySequenceIndexOf = require('./arraySequenceIndexOf');

const $check = require('check-types');
/**
* Extracts and returns the names of the stages in a MongoDB aggregation pipeline.
* @param {Array<object>} mongoAggregate - An array representing the MongoDB aggregation pipeline,
Expand Down Expand Up @@ -375,6 +375,98 @@ const _patternsToFix = [
},
];

/**
* Determines whether the provided object value satisfies a "simple match" condition based on the given prefix.
* @param {any} objVal - The object or value to evaluate. It can be an object, array, string, or other types.
* @param {string} prefix - The prefix string used for validating keys or string values in the object.
* @returns {boolean} Returns true if the object or value matches the "simple match" criteria with the prefix, otherwise false.
*/
function _matchPieceIsSimple(objVal, prefix) {
if ($check.object(objVal)) {
let isSimple = false;
for (const objKey of Object.keys(objVal)) {
if (!objKey.startsWith('$')) {
if (!objKey.startsWith(prefix)) {
return false;
} else {
isSimple =
isSimple || _matchPieceIsSimple(objVal[objKey], prefix);
}
} else {
isSimple =
isSimple || _matchPieceIsSimple(objVal[objKey], prefix);
}
}
return isSimple;
} else if ($check.array(objVal)) {
for (const obj of objVal) {
if (!_matchPieceIsSimple(obj, prefix)) {
return false;
}
}
return true;
} else if ($check.string(objVal)) {
return objVal.startsWith('$') ? objVal.startsWith('$' + prefix) : true;
} else {
return true;
}
}

/**
* Determines if a given match stage in a pipeline is simple.
* @param {object} stage - The pipeline stage to evaluate.
* @param {string} prefix - The prefix to use when processing the match object.
* @returns {boolean} Returns true if the match stage is simple, false otherwise.
*/
function _matchIsSimple(stage, prefix) {
if (!stage) {
return false;
}
if (!stage['$match']) {
return false;
}
const match = stage['$match'];
return _matchPieceIsSimple(match, prefix + '.');
}

/**
* Adjusts the order and reference of the `$match` and `$project` stages in a MongoDB aggregation pipeline.
* Used when the where is further down the pipeline stack.
* Ensures that the pipeline maintains proper structure when specific `$match` and `$project` stages are present.
* @param {Array} mongoAggregate - The MongoDB aggregation pipeline to be modified.
* @returns {Array} The modified MongoDB aggregation pipeline with corrected stage order and references.
*/
function _fixEndWhere(mongoAggregate) {
const stages = _getStageNames(mongoAggregate);

const firstStage = mongoAggregate[0];
if (projectIsRoot(firstStage) && stages.includes('$match')) {
const projectRootField = Object.keys(firstStage['$project'])[0];
let lastMatch = null;
let lastMatchIndex = -1;
for (let i = 1; i < mongoAggregate.length; i++) {
const stage = mongoAggregate[i];
if (stage['$project']) {
break;
}
if (stage['$match'] && _matchIsSimple(stage, projectRootField)) {
lastMatch = stage;
lastMatchIndex = i;
break;
}
}

if (lastMatch) {
mongoAggregate.splice(lastMatchIndex, 1);
mongoAggregate.unshift(
_changeReference(lastMatch, projectRootField)
);
}
}

return mongoAggregate;
}

/**
* Optimizes a given MongoDB aggregation pipeline by repeatedly applying transformation rules
* to remove redundant or unneeded operations for improved performance.
Expand All @@ -391,6 +483,19 @@ function optimizeMongoAggregate(mongoAggregate, options = {}) {
while (iteration > 0 && lastHash !== $hash(newAggregate)) {
lastHash = $hash(newAggregate);

for (const pipelineStage of newAggregate) {
if (
pipelineStage.$lookup &&
pipelineStage.$lookup.pipeline &&
pipelineStage.$lookup.pipeline.length > 0
) {
pipelineStage.$lookup.pipeline = optimizeMongoAggregate(
pipelineStage.$lookup.pipeline,
options
);
}
}

for (const pattern of _patternsToFix) {
newAggregate = _patternFixer(
newAggregate,
Expand All @@ -400,6 +505,7 @@ function optimizeMongoAggregate(mongoAggregate, options = {}) {
);
}

newAggregate = _fixEndWhere(newAggregate);
iteration--;
}

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@synatic/noql",
"version": "4.2.7",
"version": "4.2.8",
"description": "Convert SQL statements to mongo queries or aggregates",
"main": "index.js",
"files": [
Expand Down
Loading
Loading