From 1979b8418c3ead25e06e2c1cb6e8837c9804457e Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 13 Oct 2023 14:24:39 +0200 Subject: [PATCH 01/95] Initial commit --- LICENSE | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 203 insertions(+) create mode 100644 LICENSE create mode 100644 README.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 000000000..fe3a44227 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# mongo-php-builder From 68cf384a1e621da16c108e8700ca099055ba7b9c Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 13 Oct 2023 14:29:51 +0200 Subject: [PATCH 02/95] Add composer package config --- composer.json | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 composer.json diff --git a/composer.json b/composer.json new file mode 100644 index 000000000..c39dd374e --- /dev/null +++ b/composer.json @@ -0,0 +1,50 @@ +{ + "name": "mongodb/builder", + "description": "MongoDB driver query and aggregation builders", + "keywords": ["database", "driver", "mongodb", "persistence"], + "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "license": "Apache-2.0", + "authors": [ + { "name": "Andreas Braun", "email": "andreas.braun@mongodb.com" }, + { "name": "Jeremy Mikola", "email": "jmikola@gmail.com" }, + { "name": "Jérôme Tamarelle", "email": "jerome.tamarelle@mongodb.com" } + ], + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^11.1", + "rector/rector": "^0.16.0", + "squizlabs/php_codesniffer": "^3.7", + "symfony/phpunit-bridge": "^5.2", + "vimeo/psalm": "^5.13" + }, + "autoload": { + "psr-4": { "MongoDB\\": "src/" } + }, + "autoload-dev": { + "psr-4": { + "MongoDB\\Tests\\": "tests/" + } + }, + "scripts": { + "checks": [ + "@check:cs", + "@check:psalm", + "@check:rector" + ], + "check:cs": "phpcs", + "check:psalm": "psalm", + "check:rector": "rector --ansi --dry-run", + "fix:cs": "phpcbf", + "fix:psalm:baseline": "psalm --set-baseline=psalm-baseline.xml", + "fix:rector": "rector process --ansi", + "test": "simple-phpunit" + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "sort-packages": true + } +} From 29d1705fa1ebf3040c0eb64aec858549b336327f Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 13 Oct 2023 14:31:54 +0200 Subject: [PATCH 03/95] Add git config files --- .gitattributes | 13 +++++++++++++ .gitignore | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..ac0e88093 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,13 @@ +.* export-ignore +*.md export-ignore +tests export-ignore +docs export-ignore +examples export-ignore +mongo-orchestration export-ignore +stubs export-ignore +tools export-ignore +phpcs.xml.dist export-ignore +phpunit.evergreen.xml export-ignore +phpunit.xml.dist export-ignore +psalm.xml.dist export-ignore +psalm-baseline.xml export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..9e02e50af --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Composer +composer.phar +composer.lock +vendor/ + +# PHPUnit +phpunit.phar +phpunit.xml +.phpunit.result.cache + +# phpcs +.phpcs-cache +phpcs.xml + +# psalm +psalm.xml + +# phpbench +.phpbench/ +phpbench.json + From a52d35ef92007dc282e5369d9544456978e135dc Mon Sep 17 00:00:00 2001 From: Tom Selander <105729911+tom-selander@users.noreply.github.com> Date: Mon, 16 Oct 2023 14:51:12 -0400 Subject: [PATCH 04/95] Chore: Enable Issue Template (#2) --- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..586553497 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: MongoDB PHP library + url: https://github.com/mongodb/mongo-php-library/issues/new/choose + about: Experiencing a problem with mongo-php-builder? Please report it through the mongo-php-library repository. From cf5b119df7c3b5543ec0090caab1903207d29b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 17 Oct 2023 22:23:16 +0200 Subject: [PATCH 05/95] Create experimental builder (#1) --- .gitattributes | 15 +- .github/ISSUE_TEMPLATE/config.yml | 7 + .github/dependabot.yml | 6 + .github/workflows/coding-standards.yml | 63 + .github/workflows/static-analysis.yml | 62 + .github/workflows/tests.yml | 112 + CONTRIBUTING.md | 116 + README.md | 22 +- composer.json | 9 +- generator/README.md | 18 + generator/composer.json | 34 + generator/config/accumulator/accumulator.yaml | 54 + generator/config/accumulator/addToSet.yaml | 15 + generator/config/accumulator/avg.yaml | 15 + generator/config/accumulator/bottom.yaml | 23 + generator/config/accumulator/bottomN.yaml | 30 + generator/config/accumulator/count.yaml | 11 + .../config/accumulator/covariancePop.yaml | 18 + .../config/accumulator/covarianceSamp.yaml | 18 + generator/config/accumulator/denseRank.yaml | 9 + generator/config/accumulator/derivative.yaml | 23 + .../config/accumulator/documentNumber.yaml | 9 + .../config/accumulator/expMovingAvg.yaml | 31 + generator/config/accumulator/first.yaml | 15 + generator/config/accumulator/firstN.yaml | 22 + generator/config/accumulator/last.yaml | 15 + generator/config/accumulator/lastN.yaml | 21 + generator/config/accumulator/max.yaml | 15 + generator/config/accumulator/maxN.yaml | 22 + generator/config/accumulator/median.yaml | 27 + .../config/accumulator/mergeObjects.yaml | 16 + generator/config/accumulator/min.yaml | 15 + generator/config/accumulator/minN.yaml | 22 + generator/config/accumulator/percentile.yaml | 37 + generator/config/accumulator/push.yaml | 15 + generator/config/accumulator/shift.yaml | 34 + generator/config/accumulator/stdDevPop.yaml | 15 + generator/config/accumulator/stdDevSamp.yaml | 15 + generator/config/accumulator/sum.yaml | 15 + generator/config/accumulator/top.yaml | 24 + generator/config/accumulator/topN.yaml | 30 + generator/config/definitions.php | 65 + generator/config/expression/abs.yaml | 13 + generator/config/expression/acos.yaml | 18 + generator/config/expression/acosh.yaml | 18 + generator/config/expression/add.yaml | 18 + .../config/expression/allElementsTrue.yaml | 14 + generator/config/expression/and.yaml | 18 + .../config/expression/anyElementTrue.yaml | 13 + generator/config/expression/arrayElemAt.yaml | 17 + .../config/expression/arrayToObject.yaml | 13 + generator/config/expression/asin.yaml | 18 + generator/config/expression/asinh.yaml | 18 + generator/config/expression/atan.yaml | 18 + generator/config/expression/atan2.yaml | 22 + generator/config/expression/atanh.yaml | 18 + generator/config/expression/avg.yaml | 15 + generator/config/expression/binarySize.yaml | 15 + generator/config/expression/bitAnd.yaml | 17 + generator/config/expression/bitNot.yaml | 16 + generator/config/expression/bitOr.yaml | 17 + generator/config/expression/bitXor.yaml | 17 + generator/config/expression/bsonSize.yaml | 14 + generator/config/expression/ceil.yaml | 15 + generator/config/expression/cmp.yaml | 17 + generator/config/expression/concat.yaml | 14 + generator/config/expression/concatArrays.yaml | 14 + generator/config/expression/cond.yaml | 21 + generator/config/expression/convert.yaml | 35 + generator/config/expression/cos.yaml | 17 + generator/config/expression/cosh.yaml | 17 + generator/config/expression/dateAdd.yaml | 35 + generator/config/expression/dateDiff.yaml | 45 + .../config/expression/dateFromParts.yaml | 84 + .../config/expression/dateFromString.yaml | 46 + generator/config/expression/dateSubtract.yaml | 35 + generator/config/expression/dateToParts.yaml | 31 + generator/config/expression/dateToString.yaml | 40 + generator/config/expression/dateTrunc.yaml | 47 + generator/config/expression/dayOfMonth.yaml | 24 + generator/config/expression/dayOfWeek.yaml | 24 + generator/config/expression/dayOfYear.yaml | 24 + .../config/expression/degreesToRadians.yaml | 17 + generator/config/expression/divide.yaml | 19 + generator/config/expression/eq.yaml | 17 + generator/config/expression/exp.yaml | 13 + generator/config/expression/filter.yaml | 34 + generator/config/expression/floor.yaml | 13 + generator/config/expression/function.yaml | 26 + generator/config/expression/getField.yaml | 25 + generator/config/expression/gt.yaml | 17 + generator/config/expression/gte.yaml | 17 + generator/config/expression/hour.yaml | 24 + generator/config/expression/ifNull.yaml | 14 + generator/config/expression/in.yaml | 21 + generator/config/expression/indexOfArray.yaml | 37 + generator/config/expression/indexOfBytes.yaml | 39 + generator/config/expression/indexOfCP.yaml | 39 + generator/config/expression/integral.yaml | 24 + generator/config/expression/isArray.yaml | 14 + generator/config/expression/isNumber.yaml | 16 + generator/config/expression/isoDayOfWeek.yaml | 24 + generator/config/expression/isoWeek.yaml | 24 + generator/config/expression/isoWeekYear.yaml | 24 + generator/config/expression/let.yaml | 23 + generator/config/expression/linearFill.yaml | 15 + generator/config/expression/literal.yaml | 15 + generator/config/expression/ln.yaml | 16 + generator/config/expression/locf.yaml | 15 + generator/config/expression/log.yaml | 21 + generator/config/expression/log10.yaml | 15 + generator/config/expression/lt.yaml | 17 + generator/config/expression/lte.yaml | 17 + generator/config/expression/ltrim.yaml | 25 + generator/config/expression/map.yaml | 28 + generator/config/expression/max.yaml | 15 + generator/config/expression/maxN.yaml | 21 + generator/config/expression/median.yaml | 26 + generator/config/expression/meta.yaml | 13 + generator/config/expression/millisecond.yaml | 24 + generator/config/expression/min.yaml | 15 + generator/config/expression/minN.yaml | 21 + generator/config/expression/minute.yaml | 24 + generator/config/expression/mod.yaml | 19 + generator/config/expression/month.yaml | 24 + generator/config/expression/multiply.yaml | 17 + generator/config/expression/ne.yaml | 17 + generator/config/expression/not.yaml | 14 + .../config/expression/objectToArray.yaml | 15 + generator/config/expression/or.yaml | 15 + generator/config/expression/percentile.yaml | 36 + generator/config/expression/pow.yaml | 17 + .../config/expression/radiansToDegrees.yaml | 14 + generator/config/expression/rand.yaml | 8 + generator/config/expression/range.yaml | 28 + generator/config/expression/rank.yaml | 9 + generator/config/expression/reduce.yaml | 32 + generator/config/expression/regexFind.yaml | 28 + generator/config/expression/regexFindAll.yaml | 28 + generator/config/expression/regexMatch.yaml | 28 + generator/config/expression/replaceAll.yaml | 32 + generator/config/expression/replaceOne.yaml | 31 + generator/config/expression/reverseArray.yaml | 15 + generator/config/expression/round.yaml | 29 + generator/config/expression/rtrim.yaml | 24 + generator/config/expression/sampleRate.yaml | 16 + generator/config/expression/second.yaml | 24 + .../config/expression/setDifference.yaml | 21 + generator/config/expression/setEquals.yaml | 14 + generator/config/expression/setField.yaml | 29 + .../config/expression/setIntersection.yaml | 14 + generator/config/expression/setIsSubset.yaml | 17 + generator/config/expression/setUnion.yaml | 14 + generator/config/expression/sin.yaml | 17 + generator/config/expression/sinh.yaml | 17 + generator/config/expression/size.yaml | 15 + generator/config/expression/slice.yaml | 33 + generator/config/expression/sortArray.yaml | 23 + generator/config/expression/split.yaml | 21 + generator/config/expression/sqrt.yaml | 15 + generator/config/expression/stdDevPop.yaml | 16 + generator/config/expression/stdDevSamp.yaml | 15 + generator/config/expression/strLenBytes.yaml | 13 + generator/config/expression/strLenCP.yaml | 13 + generator/config/expression/strcasecmp.yaml | 17 + generator/config/expression/substr.yaml | 25 + generator/config/expression/substrBytes.yaml | 25 + generator/config/expression/substrCP.yaml | 25 + generator/config/expression/subtract.yaml | 20 + generator/config/expression/sum.yaml | 15 + generator/config/expression/switch.yaml | 26 + generator/config/expression/tan.yaml | 17 + generator/config/expression/tanh.yaml | 17 + generator/config/expression/toBool.yaml | 14 + generator/config/expression/toDate.yaml | 14 + generator/config/expression/toDecimal.yaml | 14 + generator/config/expression/toDouble.yaml | 14 + generator/config/expression/toInt.yaml | 14 + generator/config/expression/toLong.yaml | 14 + generator/config/expression/toLower.yaml | 13 + generator/config/expression/toObjectId.yaml | 14 + generator/config/expression/toString.yaml | 14 + generator/config/expression/toUpper.yaml | 13 + generator/config/expression/trim.yaml | 25 + generator/config/expression/trunc.yaml | 23 + generator/config/expression/tsIncrement.yaml | 14 + generator/config/expression/tsSecond.yaml | 14 + generator/config/expression/type.yaml | 13 + generator/config/expression/unsetField.yaml | 22 + generator/config/expression/week.yaml | 24 + generator/config/expression/year.yaml | 24 + generator/config/expression/zip.yaml | 32 + generator/config/expressions.php | 163 ++ generator/config/projection/elemMatch.yaml | 13 + generator/config/projection/filter.yaml | 34 + generator/config/projection/slice.yaml | 17 + generator/config/query/all.yaml | 14 + generator/config/query/and.yaml | 14 + generator/config/query/bitsAllClear.yaml | 15 + generator/config/query/bitsAllSet.yaml | 15 + generator/config/query/bitsAnyClear.yaml | 15 + generator/config/query/bitsAnySet.yaml | 15 + generator/config/query/box.yaml | 13 + generator/config/query/center.yaml | 13 + generator/config/query/centerSphere.yaml | 13 + generator/config/query/comment.yaml | 13 + generator/config/query/elemMatch.yaml | 13 + generator/config/query/eq.yaml | 13 + generator/config/query/exists.yaml | 13 + generator/config/query/expr.yaml | 13 + generator/config/query/geoIntersects.yaml | 13 + generator/config/query/geoWithin.yaml | 13 + generator/config/query/geometry.yaml | 21 + generator/config/query/gt.yaml | 13 + generator/config/query/gte.yaml | 13 + generator/config/query/in.yaml | 13 + generator/config/query/jsonSchema.yaml | 13 + generator/config/query/lt.yaml | 13 + generator/config/query/lte.yaml | 13 + generator/config/query/maxDistance.yaml | 13 + generator/config/query/minDistance.yaml | 14 + generator/config/query/mod.yaml | 17 + generator/config/query/natural.yaml | 8 + generator/config/query/ne.yaml | 13 + generator/config/query/near.yaml | 27 + generator/config/query/nearSphere.yaml | 27 + generator/config/query/nin.yaml | 13 + generator/config/query/nor.yaml | 14 + generator/config/query/not.yaml | 14 + generator/config/query/or.yaml | 14 + generator/config/query/polygon.yaml | 13 + generator/config/query/rand.yaml | 8 + generator/config/query/regex.yaml | 13 + generator/config/query/size.yaml | 13 + generator/config/query/text.yaml | 38 + generator/config/query/type.yaml | 15 + generator/config/query/where.yaml | 13 + generator/config/schema.json | 165 ++ generator/config/stage/addFields.yaml | 16 + generator/config/stage/bucket.yaml | 42 + generator/config/stage/bucketAuto.yaml | 37 + generator/config/stage/changeStream.yaml | 59 + .../stage/changeStreamSplitLargeEvent.yaml | 9 + generator/config/stage/collStats.yaml | 13 + generator/config/stage/count.yaml | 16 + generator/config/stage/currentOp.yaml | 8 + generator/config/stage/densify.yaml | 30 + generator/config/stage/documents.yaml | 19 + generator/config/stage/facet.yaml | 14 + generator/config/stage/fill.yaml | 42 + generator/config/stage/geoNear.yaml | 76 + generator/config/stage/graphLookup.yaml | 62 + generator/config/stage/group.yaml | 22 + generator/config/stage/indexStats.yaml | 8 + generator/config/stage/limit.yaml | 13 + generator/config/stage/listLocalSessions.yaml | 23 + .../config/stage/listSampledQueries.yaml | 14 + generator/config/stage/listSearchIndexes.yaml | 23 + generator/config/stage/listSessions.yaml | 23 + generator/config/stage/lookup.yaml | 54 + generator/config/stage/match.yaml | 13 + generator/config/stage/merge.yaml | 46 + generator/config/stage/out.yaml | 28 + generator/config/stage/planCacheStats.yaml | 8 + generator/config/stage/project.yaml | 18 + generator/config/stage/redact.yaml | 13 + generator/config/stage/replaceRoot.yaml | 13 + generator/config/stage/replaceWith.yaml | 14 + generator/config/stage/sample.yaml | 15 + generator/config/stage/search.yaml | 14 + generator/config/stage/searchMeta.yaml | 14 + generator/config/stage/set.yaml | 15 + generator/config/stage/setWindowFields.yaml | 36 + .../config/stage/shardedDataDistribution.yaml | 9 + generator/config/stage/skip.yaml | 13 + generator/config/stage/sort.yaml | 13 + generator/config/stage/sortByCount.yaml | 13 + generator/config/stage/unionWith.yaml | 24 + generator/config/stage/unset.yaml | 15 + generator/config/stage/unwind.yaml | 31 + generator/generate | 17 + generator/src/AbstractGenerator.php | 95 + generator/src/Command/GenerateCommand.php | 90 + .../src/Definition/ArgumentDefinition.php | 47 + .../src/Definition/ExpressionDefinition.php | 37 + .../src/Definition/GeneratorDefinition.php | 42 + .../src/Definition/OperatorDefinition.php | 58 + generator/src/Definition/PhpObject.php | 15 + generator/src/Definition/VariadicType.php | 11 + generator/src/Definition/YamlReader.php | 30 + generator/src/ExpressionClassGenerator.php | 84 + generator/src/ExpressionFactoryGenerator.php | 49 + generator/src/OperatorClassGenerator.php | 177 ++ generator/src/OperatorFactoryGenerator.php | 87 + generator/src/OperatorGenerator.php | 154 ++ phpcs.xml.dist | 89 + phpunit.xml.dist | 26 + psalm.xml.dist | 20 + src/Builder/Accumulator.php | 32 + .../Accumulator/AccumulatorAccumulator.php | 93 + .../Accumulator/AddToSetAccumulator.php | 44 + src/Builder/Accumulator/AvgAccumulator.php | 44 + src/Builder/Accumulator/BottomAccumulator.php | 53 + .../Accumulator/BottomNAccumulator.php | 61 + src/Builder/Accumulator/CountAccumulator.php | 35 + .../Accumulator/CovariancePopAccumulator.php | 50 + .../Accumulator/CovarianceSampAccumulator.php | 50 + .../Accumulator/DenseRankAccumulator.php | 33 + .../Accumulator/DerivativeAccumulator.php | 57 + .../Accumulator/DocumentNumberAccumulator.php | 33 + .../Accumulator/ExpMovingAvgAccumulator.php | 67 + src/Builder/Accumulator/FactoryTrait.php | 512 ++++ src/Builder/Accumulator/FirstAccumulator.php | 44 + src/Builder/Accumulator/FirstNAccumulator.php | 57 + src/Builder/Accumulator/LastAccumulator.php | 44 + src/Builder/Accumulator/LastNAccumulator.php | 56 + src/Builder/Accumulator/MaxAccumulator.php | 44 + src/Builder/Accumulator/MaxNAccumulator.php | 57 + src/Builder/Accumulator/MedianAccumulator.php | 53 + .../Accumulator/MergeObjectsAccumulator.php | 53 + src/Builder/Accumulator/MinAccumulator.php | 44 + src/Builder/Accumulator/MinNAccumulator.php | 57 + .../Accumulator/PercentileAccumulator.php | 79 + src/Builder/Accumulator/PushAccumulator.php | 44 + src/Builder/Accumulator/ShiftAccumulator.php | 72 + .../Accumulator/StdDevPopAccumulator.php | 44 + .../Accumulator/StdDevSampAccumulator.php | 44 + src/Builder/Accumulator/SumAccumulator.php | 44 + src/Builder/Accumulator/TopAccumulator.php | 54 + src/Builder/Accumulator/TopNAccumulator.php | 61 + src/Builder/BuilderEncoder.php | 293 +++ src/Builder/Expression.php | 16 + src/Builder/Expression/AbsOperator.php | 40 + src/Builder/Expression/AcosOperator.php | 46 + src/Builder/Expression/AcoshOperator.php | 46 + src/Builder/Expression/AddOperator.php | 51 + .../Expression/AllElementsTrueOperator.php | 50 + src/Builder/Expression/AndOperator.php | 54 + .../Expression/AnyElementTrueOperator.php | 48 + .../Expression/ArrayElemAtOperator.php | 53 + src/Builder/Expression/ArrayFieldPath.php | 21 + .../Expression/ArrayToObjectOperator.php | 48 + src/Builder/Expression/AsinOperator.php | 46 + src/Builder/Expression/AsinhOperator.php | 46 + src/Builder/Expression/Atan2Operator.php | 53 + src/Builder/Expression/AtanOperator.php | 46 + src/Builder/Expression/AtanhOperator.php | 46 + src/Builder/Expression/AvgOperator.php | 51 + src/Builder/Expression/BinDataFieldPath.php | 21 + src/Builder/Expression/BinarySizeOperator.php | 39 + src/Builder/Expression/BitAndOperator.php | 50 + src/Builder/Expression/BitNotOperator.php | 40 + src/Builder/Expression/BitOrOperator.php | 50 + src/Builder/Expression/BitXorOperator.php | 50 + src/Builder/Expression/BoolFieldPath.php | 21 + src/Builder/Expression/BsonSizeOperator.php | 41 + src/Builder/Expression/CeilOperator.php | 40 + src/Builder/Expression/CmpOperator.php | 48 + .../Expression/ConcatArraysOperator.php | 50 + src/Builder/Expression/ConcatOperator.php | 48 + src/Builder/Expression/CondOperator.php | 54 + src/Builder/Expression/ConvertOperator.php | 70 + src/Builder/Expression/CosOperator.php | 44 + src/Builder/Expression/CoshOperator.php | 44 + src/Builder/Expression/DateAddOperator.php | 62 + src/Builder/Expression/DateDiffOperator.php | 67 + src/Builder/Expression/DateFieldPath.php | 21 + .../Expression/DateFromPartsOperator.php | 102 + .../Expression/DateFromStringOperator.php | 79 + .../Expression/DateSubtractOperator.php | 62 + .../Expression/DateToPartsOperator.php | 55 + .../Expression/DateToStringOperator.php | 72 + src/Builder/Expression/DateTruncOperator.php | 81 + src/Builder/Expression/DayOfMonthOperator.php | 49 + src/Builder/Expression/DayOfWeekOperator.php | 49 + src/Builder/Expression/DayOfYearOperator.php | 49 + src/Builder/Expression/DecimalFieldPath.php | 21 + .../Expression/DegreesToRadiansOperator.php | 44 + src/Builder/Expression/DivideOperator.php | 47 + src/Builder/Expression/DoubleFieldPath.php | 21 + src/Builder/Expression/EqOperator.php | 48 + src/Builder/Expression/ExpOperator.php | 40 + .../Expression/ExpressionFactoryTrait.php | 105 + src/Builder/Expression/FactoryTrait.php | 2268 +++++++++++++++++ src/Builder/Expression/FieldPath.php | 21 + src/Builder/Expression/FilterOperator.php | 72 + src/Builder/Expression/FloorOperator.php | 40 + src/Builder/Expression/FunctionOperator.php | 59 + src/Builder/Expression/GetFieldOperator.php | 58 + src/Builder/Expression/GtOperator.php | 48 + src/Builder/Expression/GteOperator.php | 48 + src/Builder/Expression/HourOperator.php | 49 + src/Builder/Expression/IfNullOperator.php | 51 + src/Builder/Expression/InOperator.php | 58 + .../Expression/IndexOfArrayOperator.php | 85 + .../Expression/IndexOfBytesOperator.php | 72 + src/Builder/Expression/IndexOfCPOperator.php | 72 + src/Builder/Expression/IntFieldPath.php | 21 + src/Builder/Expression/IntegralOperator.php | 54 + src/Builder/Expression/IsArrayOperator.php | 51 + src/Builder/Expression/IsNumberOperator.php | 53 + .../Expression/IsoDayOfWeekOperator.php | 49 + src/Builder/Expression/IsoWeekOperator.php | 49 + .../Expression/IsoWeekYearOperator.php | 49 + .../Expression/JavascriptFieldPath.php | 21 + src/Builder/Expression/LetOperator.php | 55 + src/Builder/Expression/LinearFillOperator.php | 42 + src/Builder/Expression/LiteralOperator.php | 40 + src/Builder/Expression/LnOperator.php | 41 + src/Builder/Expression/LocfOperator.php | 43 + src/Builder/Expression/Log10Operator.php | 40 + src/Builder/Expression/LogOperator.php | 47 + src/Builder/Expression/LongFieldPath.php | 21 + src/Builder/Expression/LtOperator.php | 48 + src/Builder/Expression/LteOperator.php | 48 + src/Builder/Expression/LtrimOperator.php | 53 + src/Builder/Expression/MapOperator.php | 65 + src/Builder/Expression/MaxNOperator.php | 53 + src/Builder/Expression/MaxOperator.php | 52 + src/Builder/Expression/MedianOperator.php | 50 + src/Builder/Expression/MetaOperator.php | 38 + .../Expression/MillisecondOperator.php | 49 + src/Builder/Expression/MinNOperator.php | 53 + src/Builder/Expression/MinOperator.php | 52 + src/Builder/Expression/MinuteOperator.php | 49 + src/Builder/Expression/ModOperator.php | 47 + src/Builder/Expression/MonthOperator.php | 49 + src/Builder/Expression/MultiplyOperator.php | 54 + src/Builder/Expression/NeOperator.php | 48 + src/Builder/Expression/NotOperator.php | 42 + src/Builder/Expression/NullFieldPath.php | 21 + src/Builder/Expression/NumberFieldPath.php | 21 + src/Builder/Expression/ObjectFieldPath.php | 21 + src/Builder/Expression/ObjectIdFieldPath.php | 21 + .../Expression/ObjectToArrayOperator.php | 41 + src/Builder/Expression/OrOperator.php | 52 + src/Builder/Expression/PercentileOperator.php | 75 + src/Builder/Expression/PowOperator.php | 47 + .../Expression/RadiansToDegreesOperator.php | 40 + src/Builder/Expression/RandOperator.php | 31 + src/Builder/Expression/RangeOperator.php | 52 + src/Builder/Expression/RankOperator.php | 32 + src/Builder/Expression/ReduceOperator.php | 78 + src/Builder/Expression/RegexFieldPath.php | 21 + .../Expression/RegexFindAllOperator.php | 54 + src/Builder/Expression/RegexFindOperator.php | 54 + src/Builder/Expression/RegexMatchOperator.php | 54 + src/Builder/Expression/ReplaceAllOperator.php | 53 + src/Builder/Expression/ReplaceOneOperator.php | 52 + src/Builder/Expression/ResolvesToAny.php | 13 + src/Builder/Expression/ResolvesToArray.php | 15 + src/Builder/Expression/ResolvesToBinData.php | 15 + src/Builder/Expression/ResolvesToBool.php | 15 + src/Builder/Expression/ResolvesToDate.php | 15 + src/Builder/Expression/ResolvesToDecimal.php | 13 + src/Builder/Expression/ResolvesToDouble.php | 13 + src/Builder/Expression/ResolvesToInt.php | 13 + .../Expression/ResolvesToJavascript.php | 15 + src/Builder/Expression/ResolvesToLong.php | 13 + src/Builder/Expression/ResolvesToNull.php | 15 + src/Builder/Expression/ResolvesToNumber.php | 15 + src/Builder/Expression/ResolvesToObject.php | 15 + src/Builder/Expression/ResolvesToObjectId.php | 15 + src/Builder/Expression/ResolvesToRegex.php | 15 + src/Builder/Expression/ResolvesToString.php | 15 + .../Expression/ResolvesToTimestamp.php | 15 + .../Expression/ReverseArrayOperator.php | 48 + src/Builder/Expression/RoundOperator.php | 52 + src/Builder/Expression/RtrimOperator.php | 52 + src/Builder/Expression/SampleRateOperator.php | 43 + src/Builder/Expression/SecondOperator.php | 49 + .../Expression/SetDifferenceOperator.php | 59 + src/Builder/Expression/SetEqualsOperator.php | 50 + src/Builder/Expression/SetFieldOperator.php | 61 + .../Expression/SetIntersectionOperator.php | 50 + .../Expression/SetIsSubsetOperator.php | 59 + src/Builder/Expression/SetUnionOperator.php | 50 + src/Builder/Expression/SinOperator.php | 44 + src/Builder/Expression/SinhOperator.php | 44 + src/Builder/Expression/SizeOperator.php | 48 + src/Builder/Expression/SliceOperator.php | 75 + src/Builder/Expression/SortArrayOperator.php | 64 + src/Builder/Expression/SplitOperator.php | 43 + src/Builder/Expression/SqrtOperator.php | 40 + src/Builder/Expression/StdDevPopOperator.php | 52 + src/Builder/Expression/StdDevSampOperator.php | 51 + .../Expression/StrLenBytesOperator.php | 38 + src/Builder/Expression/StrLenCPOperator.php | 38 + src/Builder/Expression/StrcasecmpOperator.php | 43 + src/Builder/Expression/StringFieldPath.php | 21 + .../Expression/SubstrBytesOperator.php | 48 + src/Builder/Expression/SubstrCPOperator.php | 48 + src/Builder/Expression/SubstrOperator.php | 48 + src/Builder/Expression/SubtractOperator.php | 48 + src/Builder/Expression/SumOperator.php | 51 + src/Builder/Expression/SwitchOperator.php | 71 + src/Builder/Expression/TanOperator.php | 44 + src/Builder/Expression/TanhOperator.php | 44 + src/Builder/Expression/TimestampFieldPath.php | 21 + src/Builder/Expression/ToBoolOperator.php | 42 + src/Builder/Expression/ToDateOperator.php | 42 + src/Builder/Expression/ToDecimalOperator.php | 42 + src/Builder/Expression/ToDoubleOperator.php | 42 + src/Builder/Expression/ToIntOperator.php | 42 + src/Builder/Expression/ToLongOperator.php | 42 + src/Builder/Expression/ToLowerOperator.php | 38 + src/Builder/Expression/ToObjectIdOperator.php | 42 + src/Builder/Expression/ToStringOperator.php | 42 + src/Builder/Expression/ToUpperOperator.php | 38 + src/Builder/Expression/TrimOperator.php | 53 + src/Builder/Expression/TruncOperator.php | 52 + .../Expression/TsIncrementOperator.php | 40 + src/Builder/Expression/TsSecondOperator.php | 40 + src/Builder/Expression/TypeOperator.php | 41 + src/Builder/Expression/UnsetFieldOperator.php | 49 + src/Builder/Expression/Variable.php | 19 + src/Builder/Expression/WeekOperator.php | 49 + src/Builder/Expression/YearOperator.php | 49 + src/Builder/Expression/ZipOperator.php | 81 + src/Builder/Pipeline.php | 54 + src/Builder/Projection.php | 17 + src/Builder/Projection/ElemMatchOperator.php | 51 + src/Builder/Projection/FactoryTrait.php | 69 + src/Builder/Projection/FilterOperator.php | 76 + src/Builder/Projection/SliceOperator.php | 44 + src/Builder/Query.php | 47 + src/Builder/Query/AllOperator.php | 51 + src/Builder/Query/AndOperator.php | 52 + src/Builder/Query/BitsAllClearOperator.php | 50 + src/Builder/Query/BitsAllSetOperator.php | 50 + src/Builder/Query/BitsAnyClearOperator.php | 50 + src/Builder/Query/BitsAnySetOperator.php | 50 + src/Builder/Query/BoxOperator.php | 49 + src/Builder/Query/CenterOperator.php | 49 + src/Builder/Query/CenterSphereOperator.php | 49 + src/Builder/Query/CommentOperator.php | 39 + src/Builder/Query/ElemMatchOperator.php | 51 + src/Builder/Query/EqOperator.php | 41 + src/Builder/Query/ExistsOperator.php | 39 + src/Builder/Query/ExprOperator.php | 42 + src/Builder/Query/FactoryTrait.php | 514 ++++ src/Builder/Query/GeoIntersectsOperator.php | 43 + src/Builder/Query/GeoWithinOperator.php | 43 + src/Builder/Query/GeometryOperator.php | 65 + src/Builder/Query/GtOperator.php | 41 + src/Builder/Query/GteOperator.php | 41 + src/Builder/Query/InOperator.php | 49 + src/Builder/Query/JsonSchemaOperator.php | 42 + src/Builder/Query/LtOperator.php | 41 + src/Builder/Query/LteOperator.php | 41 + src/Builder/Query/MaxDistanceOperator.php | 41 + src/Builder/Query/MinDistanceOperator.php | 40 + src/Builder/Query/ModOperator.php | 44 + src/Builder/Query/NaturalOperator.php | 32 + src/Builder/Query/NeOperator.php | 41 + src/Builder/Query/NearOperator.php | 57 + src/Builder/Query/NearSphereOperator.php | 57 + src/Builder/Query/NinOperator.php | 49 + src/Builder/Query/NorOperator.php | 52 + src/Builder/Query/NotOperator.php | 52 + src/Builder/Query/OrOperator.php | 52 + src/Builder/Query/PolygonOperator.php | 49 + src/Builder/Query/RandOperator.php | 32 + src/Builder/Query/RegexOperator.php | 40 + src/Builder/Query/SizeOperator.php | 39 + src/Builder/Query/TextOperator.php | 67 + src/Builder/Query/TypeOperator.php | 49 + src/Builder/Query/WhereOperator.php | 39 + src/Builder/Stage.php | 32 + src/Builder/Stage/AddFieldsStage.php | 54 + src/Builder/Stage/BucketAutoStage.php | 72 + src/Builder/Stage/BucketStage.php | 96 + .../ChangeStreamSplitLargeEventStage.php | 33 + src/Builder/Stage/ChangeStreamStage.php | 85 + src/Builder/Stage/CollStatsStage.php | 42 + src/Builder/Stage/CountStage.php | 40 + src/Builder/Stage/CurrentOpStage.php | 32 + src/Builder/Stage/DensifyStage.php | 72 + src/Builder/Stage/DocumentsStage.php | 60 + src/Builder/Stage/FacetStage.php | 55 + src/Builder/Stage/FactoryTrait.php | 694 +++++ src/Builder/Stage/FillStage.php | 88 + src/Builder/Stage/GeoNearStage.php | 123 + src/Builder/Stage/GraphLookupStage.php | 109 + src/Builder/Stage/GroupStage.php | 64 + src/Builder/Stage/IndexStatsStage.php | 32 + src/Builder/Stage/LimitStage.php | 39 + src/Builder/Stage/ListLocalSessionsStage.php | 57 + src/Builder/Stage/ListSampledQueriesStage.php | 40 + src/Builder/Stage/ListSearchIndexesStage.php | 47 + src/Builder/Stage/ListSessionsStage.php | 57 + src/Builder/Stage/LookupStage.php | 97 + src/Builder/Stage/MatchStage.php | 51 + src/Builder/Stage/MergeStage.php | 79 + src/Builder/Stage/OutStage.php | 56 + src/Builder/Stage/PlanCacheStatsStage.php | 32 + src/Builder/Stage/ProjectStage.php | 57 + src/Builder/Stage/RedactStage.php | 42 + src/Builder/Stage/ReplaceRootStage.php | 43 + src/Builder/Stage/ReplaceWithStage.php | 44 + src/Builder/Stage/SampleStage.php | 39 + src/Builder/Stage/SearchMetaStage.php | 43 + src/Builder/Stage/SearchStage.php | 43 + src/Builder/Stage/SetStage.php | 55 + src/Builder/Stage/SetWindowFieldsStage.php | 69 + .../Stage/ShardedDataDistributionStage.php | 33 + src/Builder/Stage/SkipStage.php | 39 + src/Builder/Stage/SortByCountStage.php | 42 + src/Builder/Stage/SortStage.php | 42 + src/Builder/Stage/UnionWithStage.php | 63 + src/Builder/Stage/UnsetStage.php | 51 + src/Builder/Stage/UnwindStage.php | 60 + src/Builder/Type/AccumulatorInterface.php | 14 + src/Builder/Type/CombinedFieldQuery.php | 31 + src/Builder/Type/Encode.php | 19 + src/Builder/Type/ExpressionInterface.php | 15 + src/Builder/Type/FieldPathInterface.php | 14 + src/Builder/Type/FieldQueryInterface.php | 14 + src/Builder/Type/GeometryInterface.php | 10 + .../Type/OperatorExpressionInterface.php | 10 + src/Builder/Type/OperatorInterface.php | 13 + src/Builder/Type/Optional.php | 17 + src/Builder/Type/OutputWindow.php | 70 + src/Builder/Type/ProjectionInterface.php | 14 + src/Builder/Type/QueryInterface.php | 14 + src/Builder/Type/QueryObject.php | 86 + src/Builder/Type/StageInterface.php | 14 + src/Builder/Type/WindowInterface.php | 14 + src/functions.php | 39 + tests/Builder/BuilderEncoderTest.php | 303 +++ tests/Builder/PipelineTest.php | 53 + tests/Builder/Type/CombinedFieldQueryTest.php | 48 + tests/Builder/Type/QueryObjectTest.php | 76 + 633 files changed, 27191 insertions(+), 10 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/coding-standards.yml create mode 100644 .github/workflows/static-analysis.yml create mode 100644 .github/workflows/tests.yml create mode 100644 CONTRIBUTING.md create mode 100644 generator/README.md create mode 100644 generator/composer.json create mode 100644 generator/config/accumulator/accumulator.yaml create mode 100644 generator/config/accumulator/addToSet.yaml create mode 100644 generator/config/accumulator/avg.yaml create mode 100644 generator/config/accumulator/bottom.yaml create mode 100644 generator/config/accumulator/bottomN.yaml create mode 100644 generator/config/accumulator/count.yaml create mode 100644 generator/config/accumulator/covariancePop.yaml create mode 100644 generator/config/accumulator/covarianceSamp.yaml create mode 100644 generator/config/accumulator/denseRank.yaml create mode 100644 generator/config/accumulator/derivative.yaml create mode 100644 generator/config/accumulator/documentNumber.yaml create mode 100644 generator/config/accumulator/expMovingAvg.yaml create mode 100644 generator/config/accumulator/first.yaml create mode 100644 generator/config/accumulator/firstN.yaml create mode 100644 generator/config/accumulator/last.yaml create mode 100644 generator/config/accumulator/lastN.yaml create mode 100644 generator/config/accumulator/max.yaml create mode 100644 generator/config/accumulator/maxN.yaml create mode 100644 generator/config/accumulator/median.yaml create mode 100644 generator/config/accumulator/mergeObjects.yaml create mode 100644 generator/config/accumulator/min.yaml create mode 100644 generator/config/accumulator/minN.yaml create mode 100644 generator/config/accumulator/percentile.yaml create mode 100644 generator/config/accumulator/push.yaml create mode 100644 generator/config/accumulator/shift.yaml create mode 100644 generator/config/accumulator/stdDevPop.yaml create mode 100644 generator/config/accumulator/stdDevSamp.yaml create mode 100644 generator/config/accumulator/sum.yaml create mode 100644 generator/config/accumulator/top.yaml create mode 100644 generator/config/accumulator/topN.yaml create mode 100644 generator/config/definitions.php create mode 100644 generator/config/expression/abs.yaml create mode 100644 generator/config/expression/acos.yaml create mode 100644 generator/config/expression/acosh.yaml create mode 100644 generator/config/expression/add.yaml create mode 100644 generator/config/expression/allElementsTrue.yaml create mode 100644 generator/config/expression/and.yaml create mode 100644 generator/config/expression/anyElementTrue.yaml create mode 100644 generator/config/expression/arrayElemAt.yaml create mode 100644 generator/config/expression/arrayToObject.yaml create mode 100644 generator/config/expression/asin.yaml create mode 100644 generator/config/expression/asinh.yaml create mode 100644 generator/config/expression/atan.yaml create mode 100644 generator/config/expression/atan2.yaml create mode 100644 generator/config/expression/atanh.yaml create mode 100644 generator/config/expression/avg.yaml create mode 100644 generator/config/expression/binarySize.yaml create mode 100644 generator/config/expression/bitAnd.yaml create mode 100644 generator/config/expression/bitNot.yaml create mode 100644 generator/config/expression/bitOr.yaml create mode 100644 generator/config/expression/bitXor.yaml create mode 100644 generator/config/expression/bsonSize.yaml create mode 100644 generator/config/expression/ceil.yaml create mode 100644 generator/config/expression/cmp.yaml create mode 100644 generator/config/expression/concat.yaml create mode 100644 generator/config/expression/concatArrays.yaml create mode 100644 generator/config/expression/cond.yaml create mode 100644 generator/config/expression/convert.yaml create mode 100644 generator/config/expression/cos.yaml create mode 100644 generator/config/expression/cosh.yaml create mode 100644 generator/config/expression/dateAdd.yaml create mode 100644 generator/config/expression/dateDiff.yaml create mode 100644 generator/config/expression/dateFromParts.yaml create mode 100644 generator/config/expression/dateFromString.yaml create mode 100644 generator/config/expression/dateSubtract.yaml create mode 100644 generator/config/expression/dateToParts.yaml create mode 100644 generator/config/expression/dateToString.yaml create mode 100644 generator/config/expression/dateTrunc.yaml create mode 100644 generator/config/expression/dayOfMonth.yaml create mode 100644 generator/config/expression/dayOfWeek.yaml create mode 100644 generator/config/expression/dayOfYear.yaml create mode 100644 generator/config/expression/degreesToRadians.yaml create mode 100644 generator/config/expression/divide.yaml create mode 100644 generator/config/expression/eq.yaml create mode 100644 generator/config/expression/exp.yaml create mode 100644 generator/config/expression/filter.yaml create mode 100644 generator/config/expression/floor.yaml create mode 100644 generator/config/expression/function.yaml create mode 100644 generator/config/expression/getField.yaml create mode 100644 generator/config/expression/gt.yaml create mode 100644 generator/config/expression/gte.yaml create mode 100644 generator/config/expression/hour.yaml create mode 100644 generator/config/expression/ifNull.yaml create mode 100644 generator/config/expression/in.yaml create mode 100644 generator/config/expression/indexOfArray.yaml create mode 100644 generator/config/expression/indexOfBytes.yaml create mode 100644 generator/config/expression/indexOfCP.yaml create mode 100644 generator/config/expression/integral.yaml create mode 100644 generator/config/expression/isArray.yaml create mode 100644 generator/config/expression/isNumber.yaml create mode 100644 generator/config/expression/isoDayOfWeek.yaml create mode 100644 generator/config/expression/isoWeek.yaml create mode 100644 generator/config/expression/isoWeekYear.yaml create mode 100644 generator/config/expression/let.yaml create mode 100644 generator/config/expression/linearFill.yaml create mode 100644 generator/config/expression/literal.yaml create mode 100644 generator/config/expression/ln.yaml create mode 100644 generator/config/expression/locf.yaml create mode 100644 generator/config/expression/log.yaml create mode 100644 generator/config/expression/log10.yaml create mode 100644 generator/config/expression/lt.yaml create mode 100644 generator/config/expression/lte.yaml create mode 100644 generator/config/expression/ltrim.yaml create mode 100644 generator/config/expression/map.yaml create mode 100644 generator/config/expression/max.yaml create mode 100644 generator/config/expression/maxN.yaml create mode 100644 generator/config/expression/median.yaml create mode 100644 generator/config/expression/meta.yaml create mode 100644 generator/config/expression/millisecond.yaml create mode 100644 generator/config/expression/min.yaml create mode 100644 generator/config/expression/minN.yaml create mode 100644 generator/config/expression/minute.yaml create mode 100644 generator/config/expression/mod.yaml create mode 100644 generator/config/expression/month.yaml create mode 100644 generator/config/expression/multiply.yaml create mode 100644 generator/config/expression/ne.yaml create mode 100644 generator/config/expression/not.yaml create mode 100644 generator/config/expression/objectToArray.yaml create mode 100644 generator/config/expression/or.yaml create mode 100644 generator/config/expression/percentile.yaml create mode 100644 generator/config/expression/pow.yaml create mode 100644 generator/config/expression/radiansToDegrees.yaml create mode 100644 generator/config/expression/rand.yaml create mode 100644 generator/config/expression/range.yaml create mode 100644 generator/config/expression/rank.yaml create mode 100644 generator/config/expression/reduce.yaml create mode 100644 generator/config/expression/regexFind.yaml create mode 100644 generator/config/expression/regexFindAll.yaml create mode 100644 generator/config/expression/regexMatch.yaml create mode 100644 generator/config/expression/replaceAll.yaml create mode 100644 generator/config/expression/replaceOne.yaml create mode 100644 generator/config/expression/reverseArray.yaml create mode 100644 generator/config/expression/round.yaml create mode 100644 generator/config/expression/rtrim.yaml create mode 100644 generator/config/expression/sampleRate.yaml create mode 100644 generator/config/expression/second.yaml create mode 100644 generator/config/expression/setDifference.yaml create mode 100644 generator/config/expression/setEquals.yaml create mode 100644 generator/config/expression/setField.yaml create mode 100644 generator/config/expression/setIntersection.yaml create mode 100644 generator/config/expression/setIsSubset.yaml create mode 100644 generator/config/expression/setUnion.yaml create mode 100644 generator/config/expression/sin.yaml create mode 100644 generator/config/expression/sinh.yaml create mode 100644 generator/config/expression/size.yaml create mode 100644 generator/config/expression/slice.yaml create mode 100644 generator/config/expression/sortArray.yaml create mode 100644 generator/config/expression/split.yaml create mode 100644 generator/config/expression/sqrt.yaml create mode 100644 generator/config/expression/stdDevPop.yaml create mode 100644 generator/config/expression/stdDevSamp.yaml create mode 100644 generator/config/expression/strLenBytes.yaml create mode 100644 generator/config/expression/strLenCP.yaml create mode 100644 generator/config/expression/strcasecmp.yaml create mode 100644 generator/config/expression/substr.yaml create mode 100644 generator/config/expression/substrBytes.yaml create mode 100644 generator/config/expression/substrCP.yaml create mode 100644 generator/config/expression/subtract.yaml create mode 100644 generator/config/expression/sum.yaml create mode 100644 generator/config/expression/switch.yaml create mode 100644 generator/config/expression/tan.yaml create mode 100644 generator/config/expression/tanh.yaml create mode 100644 generator/config/expression/toBool.yaml create mode 100644 generator/config/expression/toDate.yaml create mode 100644 generator/config/expression/toDecimal.yaml create mode 100644 generator/config/expression/toDouble.yaml create mode 100644 generator/config/expression/toInt.yaml create mode 100644 generator/config/expression/toLong.yaml create mode 100644 generator/config/expression/toLower.yaml create mode 100644 generator/config/expression/toObjectId.yaml create mode 100644 generator/config/expression/toString.yaml create mode 100644 generator/config/expression/toUpper.yaml create mode 100644 generator/config/expression/trim.yaml create mode 100644 generator/config/expression/trunc.yaml create mode 100644 generator/config/expression/tsIncrement.yaml create mode 100644 generator/config/expression/tsSecond.yaml create mode 100644 generator/config/expression/type.yaml create mode 100644 generator/config/expression/unsetField.yaml create mode 100644 generator/config/expression/week.yaml create mode 100644 generator/config/expression/year.yaml create mode 100644 generator/config/expression/zip.yaml create mode 100644 generator/config/expressions.php create mode 100644 generator/config/projection/elemMatch.yaml create mode 100644 generator/config/projection/filter.yaml create mode 100644 generator/config/projection/slice.yaml create mode 100644 generator/config/query/all.yaml create mode 100644 generator/config/query/and.yaml create mode 100644 generator/config/query/bitsAllClear.yaml create mode 100644 generator/config/query/bitsAllSet.yaml create mode 100644 generator/config/query/bitsAnyClear.yaml create mode 100644 generator/config/query/bitsAnySet.yaml create mode 100644 generator/config/query/box.yaml create mode 100644 generator/config/query/center.yaml create mode 100644 generator/config/query/centerSphere.yaml create mode 100644 generator/config/query/comment.yaml create mode 100644 generator/config/query/elemMatch.yaml create mode 100644 generator/config/query/eq.yaml create mode 100644 generator/config/query/exists.yaml create mode 100644 generator/config/query/expr.yaml create mode 100644 generator/config/query/geoIntersects.yaml create mode 100644 generator/config/query/geoWithin.yaml create mode 100644 generator/config/query/geometry.yaml create mode 100644 generator/config/query/gt.yaml create mode 100644 generator/config/query/gte.yaml create mode 100644 generator/config/query/in.yaml create mode 100644 generator/config/query/jsonSchema.yaml create mode 100644 generator/config/query/lt.yaml create mode 100644 generator/config/query/lte.yaml create mode 100644 generator/config/query/maxDistance.yaml create mode 100644 generator/config/query/minDistance.yaml create mode 100644 generator/config/query/mod.yaml create mode 100644 generator/config/query/natural.yaml create mode 100644 generator/config/query/ne.yaml create mode 100644 generator/config/query/near.yaml create mode 100644 generator/config/query/nearSphere.yaml create mode 100644 generator/config/query/nin.yaml create mode 100644 generator/config/query/nor.yaml create mode 100644 generator/config/query/not.yaml create mode 100644 generator/config/query/or.yaml create mode 100644 generator/config/query/polygon.yaml create mode 100644 generator/config/query/rand.yaml create mode 100644 generator/config/query/regex.yaml create mode 100644 generator/config/query/size.yaml create mode 100644 generator/config/query/text.yaml create mode 100644 generator/config/query/type.yaml create mode 100644 generator/config/query/where.yaml create mode 100644 generator/config/schema.json create mode 100644 generator/config/stage/addFields.yaml create mode 100644 generator/config/stage/bucket.yaml create mode 100644 generator/config/stage/bucketAuto.yaml create mode 100644 generator/config/stage/changeStream.yaml create mode 100644 generator/config/stage/changeStreamSplitLargeEvent.yaml create mode 100644 generator/config/stage/collStats.yaml create mode 100644 generator/config/stage/count.yaml create mode 100644 generator/config/stage/currentOp.yaml create mode 100644 generator/config/stage/densify.yaml create mode 100644 generator/config/stage/documents.yaml create mode 100644 generator/config/stage/facet.yaml create mode 100644 generator/config/stage/fill.yaml create mode 100644 generator/config/stage/geoNear.yaml create mode 100644 generator/config/stage/graphLookup.yaml create mode 100644 generator/config/stage/group.yaml create mode 100644 generator/config/stage/indexStats.yaml create mode 100644 generator/config/stage/limit.yaml create mode 100644 generator/config/stage/listLocalSessions.yaml create mode 100644 generator/config/stage/listSampledQueries.yaml create mode 100644 generator/config/stage/listSearchIndexes.yaml create mode 100644 generator/config/stage/listSessions.yaml create mode 100644 generator/config/stage/lookup.yaml create mode 100644 generator/config/stage/match.yaml create mode 100644 generator/config/stage/merge.yaml create mode 100644 generator/config/stage/out.yaml create mode 100644 generator/config/stage/planCacheStats.yaml create mode 100644 generator/config/stage/project.yaml create mode 100644 generator/config/stage/redact.yaml create mode 100644 generator/config/stage/replaceRoot.yaml create mode 100644 generator/config/stage/replaceWith.yaml create mode 100644 generator/config/stage/sample.yaml create mode 100644 generator/config/stage/search.yaml create mode 100644 generator/config/stage/searchMeta.yaml create mode 100644 generator/config/stage/set.yaml create mode 100644 generator/config/stage/setWindowFields.yaml create mode 100644 generator/config/stage/shardedDataDistribution.yaml create mode 100644 generator/config/stage/skip.yaml create mode 100644 generator/config/stage/sort.yaml create mode 100644 generator/config/stage/sortByCount.yaml create mode 100644 generator/config/stage/unionWith.yaml create mode 100644 generator/config/stage/unset.yaml create mode 100644 generator/config/stage/unwind.yaml create mode 100755 generator/generate create mode 100644 generator/src/AbstractGenerator.php create mode 100644 generator/src/Command/GenerateCommand.php create mode 100644 generator/src/Definition/ArgumentDefinition.php create mode 100644 generator/src/Definition/ExpressionDefinition.php create mode 100644 generator/src/Definition/GeneratorDefinition.php create mode 100644 generator/src/Definition/OperatorDefinition.php create mode 100644 generator/src/Definition/PhpObject.php create mode 100644 generator/src/Definition/VariadicType.php create mode 100644 generator/src/Definition/YamlReader.php create mode 100644 generator/src/ExpressionClassGenerator.php create mode 100644 generator/src/ExpressionFactoryGenerator.php create mode 100644 generator/src/OperatorClassGenerator.php create mode 100644 generator/src/OperatorFactoryGenerator.php create mode 100644 generator/src/OperatorGenerator.php create mode 100644 phpcs.xml.dist create mode 100644 phpunit.xml.dist create mode 100644 psalm.xml.dist create mode 100644 src/Builder/Accumulator.php create mode 100644 src/Builder/Accumulator/AccumulatorAccumulator.php create mode 100644 src/Builder/Accumulator/AddToSetAccumulator.php create mode 100644 src/Builder/Accumulator/AvgAccumulator.php create mode 100644 src/Builder/Accumulator/BottomAccumulator.php create mode 100644 src/Builder/Accumulator/BottomNAccumulator.php create mode 100644 src/Builder/Accumulator/CountAccumulator.php create mode 100644 src/Builder/Accumulator/CovariancePopAccumulator.php create mode 100644 src/Builder/Accumulator/CovarianceSampAccumulator.php create mode 100644 src/Builder/Accumulator/DenseRankAccumulator.php create mode 100644 src/Builder/Accumulator/DerivativeAccumulator.php create mode 100644 src/Builder/Accumulator/DocumentNumberAccumulator.php create mode 100644 src/Builder/Accumulator/ExpMovingAvgAccumulator.php create mode 100644 src/Builder/Accumulator/FactoryTrait.php create mode 100644 src/Builder/Accumulator/FirstAccumulator.php create mode 100644 src/Builder/Accumulator/FirstNAccumulator.php create mode 100644 src/Builder/Accumulator/LastAccumulator.php create mode 100644 src/Builder/Accumulator/LastNAccumulator.php create mode 100644 src/Builder/Accumulator/MaxAccumulator.php create mode 100644 src/Builder/Accumulator/MaxNAccumulator.php create mode 100644 src/Builder/Accumulator/MedianAccumulator.php create mode 100644 src/Builder/Accumulator/MergeObjectsAccumulator.php create mode 100644 src/Builder/Accumulator/MinAccumulator.php create mode 100644 src/Builder/Accumulator/MinNAccumulator.php create mode 100644 src/Builder/Accumulator/PercentileAccumulator.php create mode 100644 src/Builder/Accumulator/PushAccumulator.php create mode 100644 src/Builder/Accumulator/ShiftAccumulator.php create mode 100644 src/Builder/Accumulator/StdDevPopAccumulator.php create mode 100644 src/Builder/Accumulator/StdDevSampAccumulator.php create mode 100644 src/Builder/Accumulator/SumAccumulator.php create mode 100644 src/Builder/Accumulator/TopAccumulator.php create mode 100644 src/Builder/Accumulator/TopNAccumulator.php create mode 100644 src/Builder/BuilderEncoder.php create mode 100644 src/Builder/Expression.php create mode 100644 src/Builder/Expression/AbsOperator.php create mode 100644 src/Builder/Expression/AcosOperator.php create mode 100644 src/Builder/Expression/AcoshOperator.php create mode 100644 src/Builder/Expression/AddOperator.php create mode 100644 src/Builder/Expression/AllElementsTrueOperator.php create mode 100644 src/Builder/Expression/AndOperator.php create mode 100644 src/Builder/Expression/AnyElementTrueOperator.php create mode 100644 src/Builder/Expression/ArrayElemAtOperator.php create mode 100644 src/Builder/Expression/ArrayFieldPath.php create mode 100644 src/Builder/Expression/ArrayToObjectOperator.php create mode 100644 src/Builder/Expression/AsinOperator.php create mode 100644 src/Builder/Expression/AsinhOperator.php create mode 100644 src/Builder/Expression/Atan2Operator.php create mode 100644 src/Builder/Expression/AtanOperator.php create mode 100644 src/Builder/Expression/AtanhOperator.php create mode 100644 src/Builder/Expression/AvgOperator.php create mode 100644 src/Builder/Expression/BinDataFieldPath.php create mode 100644 src/Builder/Expression/BinarySizeOperator.php create mode 100644 src/Builder/Expression/BitAndOperator.php create mode 100644 src/Builder/Expression/BitNotOperator.php create mode 100644 src/Builder/Expression/BitOrOperator.php create mode 100644 src/Builder/Expression/BitXorOperator.php create mode 100644 src/Builder/Expression/BoolFieldPath.php create mode 100644 src/Builder/Expression/BsonSizeOperator.php create mode 100644 src/Builder/Expression/CeilOperator.php create mode 100644 src/Builder/Expression/CmpOperator.php create mode 100644 src/Builder/Expression/ConcatArraysOperator.php create mode 100644 src/Builder/Expression/ConcatOperator.php create mode 100644 src/Builder/Expression/CondOperator.php create mode 100644 src/Builder/Expression/ConvertOperator.php create mode 100644 src/Builder/Expression/CosOperator.php create mode 100644 src/Builder/Expression/CoshOperator.php create mode 100644 src/Builder/Expression/DateAddOperator.php create mode 100644 src/Builder/Expression/DateDiffOperator.php create mode 100644 src/Builder/Expression/DateFieldPath.php create mode 100644 src/Builder/Expression/DateFromPartsOperator.php create mode 100644 src/Builder/Expression/DateFromStringOperator.php create mode 100644 src/Builder/Expression/DateSubtractOperator.php create mode 100644 src/Builder/Expression/DateToPartsOperator.php create mode 100644 src/Builder/Expression/DateToStringOperator.php create mode 100644 src/Builder/Expression/DateTruncOperator.php create mode 100644 src/Builder/Expression/DayOfMonthOperator.php create mode 100644 src/Builder/Expression/DayOfWeekOperator.php create mode 100644 src/Builder/Expression/DayOfYearOperator.php create mode 100644 src/Builder/Expression/DecimalFieldPath.php create mode 100644 src/Builder/Expression/DegreesToRadiansOperator.php create mode 100644 src/Builder/Expression/DivideOperator.php create mode 100644 src/Builder/Expression/DoubleFieldPath.php create mode 100644 src/Builder/Expression/EqOperator.php create mode 100644 src/Builder/Expression/ExpOperator.php create mode 100644 src/Builder/Expression/ExpressionFactoryTrait.php create mode 100644 src/Builder/Expression/FactoryTrait.php create mode 100644 src/Builder/Expression/FieldPath.php create mode 100644 src/Builder/Expression/FilterOperator.php create mode 100644 src/Builder/Expression/FloorOperator.php create mode 100644 src/Builder/Expression/FunctionOperator.php create mode 100644 src/Builder/Expression/GetFieldOperator.php create mode 100644 src/Builder/Expression/GtOperator.php create mode 100644 src/Builder/Expression/GteOperator.php create mode 100644 src/Builder/Expression/HourOperator.php create mode 100644 src/Builder/Expression/IfNullOperator.php create mode 100644 src/Builder/Expression/InOperator.php create mode 100644 src/Builder/Expression/IndexOfArrayOperator.php create mode 100644 src/Builder/Expression/IndexOfBytesOperator.php create mode 100644 src/Builder/Expression/IndexOfCPOperator.php create mode 100644 src/Builder/Expression/IntFieldPath.php create mode 100644 src/Builder/Expression/IntegralOperator.php create mode 100644 src/Builder/Expression/IsArrayOperator.php create mode 100644 src/Builder/Expression/IsNumberOperator.php create mode 100644 src/Builder/Expression/IsoDayOfWeekOperator.php create mode 100644 src/Builder/Expression/IsoWeekOperator.php create mode 100644 src/Builder/Expression/IsoWeekYearOperator.php create mode 100644 src/Builder/Expression/JavascriptFieldPath.php create mode 100644 src/Builder/Expression/LetOperator.php create mode 100644 src/Builder/Expression/LinearFillOperator.php create mode 100644 src/Builder/Expression/LiteralOperator.php create mode 100644 src/Builder/Expression/LnOperator.php create mode 100644 src/Builder/Expression/LocfOperator.php create mode 100644 src/Builder/Expression/Log10Operator.php create mode 100644 src/Builder/Expression/LogOperator.php create mode 100644 src/Builder/Expression/LongFieldPath.php create mode 100644 src/Builder/Expression/LtOperator.php create mode 100644 src/Builder/Expression/LteOperator.php create mode 100644 src/Builder/Expression/LtrimOperator.php create mode 100644 src/Builder/Expression/MapOperator.php create mode 100644 src/Builder/Expression/MaxNOperator.php create mode 100644 src/Builder/Expression/MaxOperator.php create mode 100644 src/Builder/Expression/MedianOperator.php create mode 100644 src/Builder/Expression/MetaOperator.php create mode 100644 src/Builder/Expression/MillisecondOperator.php create mode 100644 src/Builder/Expression/MinNOperator.php create mode 100644 src/Builder/Expression/MinOperator.php create mode 100644 src/Builder/Expression/MinuteOperator.php create mode 100644 src/Builder/Expression/ModOperator.php create mode 100644 src/Builder/Expression/MonthOperator.php create mode 100644 src/Builder/Expression/MultiplyOperator.php create mode 100644 src/Builder/Expression/NeOperator.php create mode 100644 src/Builder/Expression/NotOperator.php create mode 100644 src/Builder/Expression/NullFieldPath.php create mode 100644 src/Builder/Expression/NumberFieldPath.php create mode 100644 src/Builder/Expression/ObjectFieldPath.php create mode 100644 src/Builder/Expression/ObjectIdFieldPath.php create mode 100644 src/Builder/Expression/ObjectToArrayOperator.php create mode 100644 src/Builder/Expression/OrOperator.php create mode 100644 src/Builder/Expression/PercentileOperator.php create mode 100644 src/Builder/Expression/PowOperator.php create mode 100644 src/Builder/Expression/RadiansToDegreesOperator.php create mode 100644 src/Builder/Expression/RandOperator.php create mode 100644 src/Builder/Expression/RangeOperator.php create mode 100644 src/Builder/Expression/RankOperator.php create mode 100644 src/Builder/Expression/ReduceOperator.php create mode 100644 src/Builder/Expression/RegexFieldPath.php create mode 100644 src/Builder/Expression/RegexFindAllOperator.php create mode 100644 src/Builder/Expression/RegexFindOperator.php create mode 100644 src/Builder/Expression/RegexMatchOperator.php create mode 100644 src/Builder/Expression/ReplaceAllOperator.php create mode 100644 src/Builder/Expression/ReplaceOneOperator.php create mode 100644 src/Builder/Expression/ResolvesToAny.php create mode 100644 src/Builder/Expression/ResolvesToArray.php create mode 100644 src/Builder/Expression/ResolvesToBinData.php create mode 100644 src/Builder/Expression/ResolvesToBool.php create mode 100644 src/Builder/Expression/ResolvesToDate.php create mode 100644 src/Builder/Expression/ResolvesToDecimal.php create mode 100644 src/Builder/Expression/ResolvesToDouble.php create mode 100644 src/Builder/Expression/ResolvesToInt.php create mode 100644 src/Builder/Expression/ResolvesToJavascript.php create mode 100644 src/Builder/Expression/ResolvesToLong.php create mode 100644 src/Builder/Expression/ResolvesToNull.php create mode 100644 src/Builder/Expression/ResolvesToNumber.php create mode 100644 src/Builder/Expression/ResolvesToObject.php create mode 100644 src/Builder/Expression/ResolvesToObjectId.php create mode 100644 src/Builder/Expression/ResolvesToRegex.php create mode 100644 src/Builder/Expression/ResolvesToString.php create mode 100644 src/Builder/Expression/ResolvesToTimestamp.php create mode 100644 src/Builder/Expression/ReverseArrayOperator.php create mode 100644 src/Builder/Expression/RoundOperator.php create mode 100644 src/Builder/Expression/RtrimOperator.php create mode 100644 src/Builder/Expression/SampleRateOperator.php create mode 100644 src/Builder/Expression/SecondOperator.php create mode 100644 src/Builder/Expression/SetDifferenceOperator.php create mode 100644 src/Builder/Expression/SetEqualsOperator.php create mode 100644 src/Builder/Expression/SetFieldOperator.php create mode 100644 src/Builder/Expression/SetIntersectionOperator.php create mode 100644 src/Builder/Expression/SetIsSubsetOperator.php create mode 100644 src/Builder/Expression/SetUnionOperator.php create mode 100644 src/Builder/Expression/SinOperator.php create mode 100644 src/Builder/Expression/SinhOperator.php create mode 100644 src/Builder/Expression/SizeOperator.php create mode 100644 src/Builder/Expression/SliceOperator.php create mode 100644 src/Builder/Expression/SortArrayOperator.php create mode 100644 src/Builder/Expression/SplitOperator.php create mode 100644 src/Builder/Expression/SqrtOperator.php create mode 100644 src/Builder/Expression/StdDevPopOperator.php create mode 100644 src/Builder/Expression/StdDevSampOperator.php create mode 100644 src/Builder/Expression/StrLenBytesOperator.php create mode 100644 src/Builder/Expression/StrLenCPOperator.php create mode 100644 src/Builder/Expression/StrcasecmpOperator.php create mode 100644 src/Builder/Expression/StringFieldPath.php create mode 100644 src/Builder/Expression/SubstrBytesOperator.php create mode 100644 src/Builder/Expression/SubstrCPOperator.php create mode 100644 src/Builder/Expression/SubstrOperator.php create mode 100644 src/Builder/Expression/SubtractOperator.php create mode 100644 src/Builder/Expression/SumOperator.php create mode 100644 src/Builder/Expression/SwitchOperator.php create mode 100644 src/Builder/Expression/TanOperator.php create mode 100644 src/Builder/Expression/TanhOperator.php create mode 100644 src/Builder/Expression/TimestampFieldPath.php create mode 100644 src/Builder/Expression/ToBoolOperator.php create mode 100644 src/Builder/Expression/ToDateOperator.php create mode 100644 src/Builder/Expression/ToDecimalOperator.php create mode 100644 src/Builder/Expression/ToDoubleOperator.php create mode 100644 src/Builder/Expression/ToIntOperator.php create mode 100644 src/Builder/Expression/ToLongOperator.php create mode 100644 src/Builder/Expression/ToLowerOperator.php create mode 100644 src/Builder/Expression/ToObjectIdOperator.php create mode 100644 src/Builder/Expression/ToStringOperator.php create mode 100644 src/Builder/Expression/ToUpperOperator.php create mode 100644 src/Builder/Expression/TrimOperator.php create mode 100644 src/Builder/Expression/TruncOperator.php create mode 100644 src/Builder/Expression/TsIncrementOperator.php create mode 100644 src/Builder/Expression/TsSecondOperator.php create mode 100644 src/Builder/Expression/TypeOperator.php create mode 100644 src/Builder/Expression/UnsetFieldOperator.php create mode 100644 src/Builder/Expression/Variable.php create mode 100644 src/Builder/Expression/WeekOperator.php create mode 100644 src/Builder/Expression/YearOperator.php create mode 100644 src/Builder/Expression/ZipOperator.php create mode 100644 src/Builder/Pipeline.php create mode 100644 src/Builder/Projection.php create mode 100644 src/Builder/Projection/ElemMatchOperator.php create mode 100644 src/Builder/Projection/FactoryTrait.php create mode 100644 src/Builder/Projection/FilterOperator.php create mode 100644 src/Builder/Projection/SliceOperator.php create mode 100644 src/Builder/Query.php create mode 100644 src/Builder/Query/AllOperator.php create mode 100644 src/Builder/Query/AndOperator.php create mode 100644 src/Builder/Query/BitsAllClearOperator.php create mode 100644 src/Builder/Query/BitsAllSetOperator.php create mode 100644 src/Builder/Query/BitsAnyClearOperator.php create mode 100644 src/Builder/Query/BitsAnySetOperator.php create mode 100644 src/Builder/Query/BoxOperator.php create mode 100644 src/Builder/Query/CenterOperator.php create mode 100644 src/Builder/Query/CenterSphereOperator.php create mode 100644 src/Builder/Query/CommentOperator.php create mode 100644 src/Builder/Query/ElemMatchOperator.php create mode 100644 src/Builder/Query/EqOperator.php create mode 100644 src/Builder/Query/ExistsOperator.php create mode 100644 src/Builder/Query/ExprOperator.php create mode 100644 src/Builder/Query/FactoryTrait.php create mode 100644 src/Builder/Query/GeoIntersectsOperator.php create mode 100644 src/Builder/Query/GeoWithinOperator.php create mode 100644 src/Builder/Query/GeometryOperator.php create mode 100644 src/Builder/Query/GtOperator.php create mode 100644 src/Builder/Query/GteOperator.php create mode 100644 src/Builder/Query/InOperator.php create mode 100644 src/Builder/Query/JsonSchemaOperator.php create mode 100644 src/Builder/Query/LtOperator.php create mode 100644 src/Builder/Query/LteOperator.php create mode 100644 src/Builder/Query/MaxDistanceOperator.php create mode 100644 src/Builder/Query/MinDistanceOperator.php create mode 100644 src/Builder/Query/ModOperator.php create mode 100644 src/Builder/Query/NaturalOperator.php create mode 100644 src/Builder/Query/NeOperator.php create mode 100644 src/Builder/Query/NearOperator.php create mode 100644 src/Builder/Query/NearSphereOperator.php create mode 100644 src/Builder/Query/NinOperator.php create mode 100644 src/Builder/Query/NorOperator.php create mode 100644 src/Builder/Query/NotOperator.php create mode 100644 src/Builder/Query/OrOperator.php create mode 100644 src/Builder/Query/PolygonOperator.php create mode 100644 src/Builder/Query/RandOperator.php create mode 100644 src/Builder/Query/RegexOperator.php create mode 100644 src/Builder/Query/SizeOperator.php create mode 100644 src/Builder/Query/TextOperator.php create mode 100644 src/Builder/Query/TypeOperator.php create mode 100644 src/Builder/Query/WhereOperator.php create mode 100644 src/Builder/Stage.php create mode 100644 src/Builder/Stage/AddFieldsStage.php create mode 100644 src/Builder/Stage/BucketAutoStage.php create mode 100644 src/Builder/Stage/BucketStage.php create mode 100644 src/Builder/Stage/ChangeStreamSplitLargeEventStage.php create mode 100644 src/Builder/Stage/ChangeStreamStage.php create mode 100644 src/Builder/Stage/CollStatsStage.php create mode 100644 src/Builder/Stage/CountStage.php create mode 100644 src/Builder/Stage/CurrentOpStage.php create mode 100644 src/Builder/Stage/DensifyStage.php create mode 100644 src/Builder/Stage/DocumentsStage.php create mode 100644 src/Builder/Stage/FacetStage.php create mode 100644 src/Builder/Stage/FactoryTrait.php create mode 100644 src/Builder/Stage/FillStage.php create mode 100644 src/Builder/Stage/GeoNearStage.php create mode 100644 src/Builder/Stage/GraphLookupStage.php create mode 100644 src/Builder/Stage/GroupStage.php create mode 100644 src/Builder/Stage/IndexStatsStage.php create mode 100644 src/Builder/Stage/LimitStage.php create mode 100644 src/Builder/Stage/ListLocalSessionsStage.php create mode 100644 src/Builder/Stage/ListSampledQueriesStage.php create mode 100644 src/Builder/Stage/ListSearchIndexesStage.php create mode 100644 src/Builder/Stage/ListSessionsStage.php create mode 100644 src/Builder/Stage/LookupStage.php create mode 100644 src/Builder/Stage/MatchStage.php create mode 100644 src/Builder/Stage/MergeStage.php create mode 100644 src/Builder/Stage/OutStage.php create mode 100644 src/Builder/Stage/PlanCacheStatsStage.php create mode 100644 src/Builder/Stage/ProjectStage.php create mode 100644 src/Builder/Stage/RedactStage.php create mode 100644 src/Builder/Stage/ReplaceRootStage.php create mode 100644 src/Builder/Stage/ReplaceWithStage.php create mode 100644 src/Builder/Stage/SampleStage.php create mode 100644 src/Builder/Stage/SearchMetaStage.php create mode 100644 src/Builder/Stage/SearchStage.php create mode 100644 src/Builder/Stage/SetStage.php create mode 100644 src/Builder/Stage/SetWindowFieldsStage.php create mode 100644 src/Builder/Stage/ShardedDataDistributionStage.php create mode 100644 src/Builder/Stage/SkipStage.php create mode 100644 src/Builder/Stage/SortByCountStage.php create mode 100644 src/Builder/Stage/SortStage.php create mode 100644 src/Builder/Stage/UnionWithStage.php create mode 100644 src/Builder/Stage/UnsetStage.php create mode 100644 src/Builder/Stage/UnwindStage.php create mode 100644 src/Builder/Type/AccumulatorInterface.php create mode 100644 src/Builder/Type/CombinedFieldQuery.php create mode 100644 src/Builder/Type/Encode.php create mode 100644 src/Builder/Type/ExpressionInterface.php create mode 100644 src/Builder/Type/FieldPathInterface.php create mode 100644 src/Builder/Type/FieldQueryInterface.php create mode 100644 src/Builder/Type/GeometryInterface.php create mode 100644 src/Builder/Type/OperatorExpressionInterface.php create mode 100644 src/Builder/Type/OperatorInterface.php create mode 100644 src/Builder/Type/Optional.php create mode 100644 src/Builder/Type/OutputWindow.php create mode 100644 src/Builder/Type/ProjectionInterface.php create mode 100644 src/Builder/Type/QueryInterface.php create mode 100644 src/Builder/Type/QueryObject.php create mode 100644 src/Builder/Type/StageInterface.php create mode 100644 src/Builder/Type/WindowInterface.php create mode 100644 src/functions.php create mode 100644 tests/Builder/BuilderEncoderTest.php create mode 100644 tests/Builder/PipelineTest.php create mode 100644 tests/Builder/Type/CombinedFieldQueryTest.php create mode 100644 tests/Builder/Type/QueryObjectTest.php diff --git a/.gitattributes b/.gitattributes index ac0e88093..9071b99c6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,13 +1,16 @@ .* export-ignore *.md export-ignore +generator export-ignore tests export-ignore -docs export-ignore -examples export-ignore -mongo-orchestration export-ignore -stubs export-ignore -tools export-ignore phpcs.xml.dist export-ignore -phpunit.evergreen.xml export-ignore phpunit.xml.dist export-ignore psalm.xml.dist export-ignore psalm-baseline.xml export-ignore + +# Keep generated files from displaying in diffs by default +# https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github +/src/Builder/Accumulator/*.php linguist-generated=true +/src/Builder/Expression/*.php linguist-generated=true +/src/Builder/Query/*.php linguist-generated=true +/src/Builder/Projection/*.php linguist-generated=true +/src/Builder/Stage/*.php linguist-generated=true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 586553497..58e6cd7a6 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,12 @@ blank_issues_enabled: false + contact_links: - name: MongoDB PHP library url: https://github.com/mongodb/mongo-php-library/issues/new/choose about: Experiencing a problem with mongo-php-builder? Please report it through the mongo-php-library repository. + - name: MongoDB Developer Community Forums + url: https://developer.mongodb.com/community/forums/ + about: For questions, discussions, or general technical support, visit the MongoDB Community Forums. The MongoDB Community Forums are a centralized place to connect with other MongoDB users, ask questions, and get answers. + - name: Report a Security Vulnerability + url: https://mongodb.com/docs/manual/tutorial/create-a-vulnerability-report + about: If you believe you have discovered a vulnerability in MongoDB products or have experienced a security incident related to MongoDB products, please report the issue to aid in its resolution. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..5ace4600a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml new file mode 100644 index 000000000..281d63d92 --- /dev/null +++ b/.github/workflows/coding-standards.yml @@ -0,0 +1,63 @@ +name: "Coding Standards" + +on: + pull_request: + branches: + - "[0-9]+.[0-9]+" + - "feature/*" + paths-ignore: + - "docs/**" + push: + branches: + - "[0-9]+.[0-9]+" + - "feature/*" + paths-ignore: + - "docs/**" + +env: + PHP_VERSION: "8.2" + DRIVER_VERSION: "mongodb/mongo-php-driver@master" + +jobs: + phpcs: + name: "phpcs" + runs-on: "ubuntu-22.04" + + steps: + - name: "Checkout" + uses: "actions/checkout@v4" + + - name: Setup cache environment + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ env.PHP_VERSION }} + extensions: "mongodb-${{ env.DRIVER_VERSION }}" + key: "extcache-v1" + + - name: Cache extensions + uses: actions/cache@v3 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ steps.extcache.outputs.key }} + restore-keys: ${{ steps.extcache.outputs.key }} + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + extensions: "mongodb-${{ env.DRIVER_VERSION }}" + php-version: "${{ env.PHP_VERSION }}" + tools: "cs2pr" + + - name: "Show driver information" + run: "php --ri mongodb" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@2.2.0" + with: + composer-options: "--no-suggest" + + # The -q option is required until phpcs v4 is released + - name: "Run PHP_CodeSniffer" + run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 000000000..6c9bdd7a6 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,62 @@ +name: "Static Analysis" + +on: + pull_request: + branches: + - "[0-9]+.[0-9]+" + - "feature/*" + paths-ignore: + - "docs/**" + push: + branches: + - "[0-9]+.[0-9]+" + - "feature/*" + paths-ignore: + - "docs/**" + +env: + PHP_VERSION: "8.2" + DRIVER_VERSION: "mongodb/mongo-php-driver@master" + +jobs: + psalm: + name: "Psalm" + runs-on: "ubuntu-22.04" + + steps: + - name: "Checkout" + uses: "actions/checkout@v4" + + - name: Setup cache environment + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ env.PHP_VERSION }} + extensions: "mongodb-${{ ENV.DRIVER_VERSION }}" + key: "extcache-v1" + + - name: Cache extensions + uses: actions/cache@v3 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ steps.extcache.outputs.key }} + restore-keys: ${{ steps.extcache.outputs.key }} + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + extensions: "mongodb-${{ ENV.DRIVER_VERSION }}" + php-version: "${{ env.PHP_VERSION }}" + tools: "cs2pr" + + - name: "Show driver information" + run: "php --ri mongodb" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@2.2.0" + with: + composer-options: "--no-suggest" + + - name: "Run Psalm" + run: "vendor/bin/psalm --show-info=false --stats --output-format=github --threads=$(nproc)" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..c1d42b34b --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,112 @@ +name: "Tests" + +on: + pull_request: + branches: + - "[0-9]+.[0-9]+" + - "feature/*" + paths-ignore: + - "docs/**" + push: + branches: + - "[0-9]+.[0-9]+" + - "feature/*" + paths-ignore: + - "docs/**" + +jobs: + phpunit: + name: "PHPUnit tests" + runs-on: "${{ matrix.os }}" + + strategy: + fail-fast: true + matrix: + os: + - "ubuntu-20.04" + php-version: + - "8.1" + - "8.2" + - "8.3" + mongodb-version: + - "4.4" + driver-version: + - "mongodb/mongo-php-driver@master" + topology: + - "server" + include: + - os: "ubuntu-20.04" + php-version: "8.1" + mongodb-version: "6.0" + driver-version: "mongodb/mongo-php-driver@master" + topology: "replica_set" + - os: "ubuntu-20.04" + php-version: "8.1" + mongodb-version: "6.0" + driver-version: "mongodb/mongo-php-driver@master" + topology: "sharded_cluster" + - os: "ubuntu-20.04" + php-version: "8.1" + mongodb-version: "5.0" + driver-version: "mongodb/mongo-php-driver@master" + topology: "server" + - os: "ubuntu-20.04" + php-version: "8.1" + mongodb-version: "4.4" + driver-version: "mongodb/mongo-php-driver@master" + topology: "replica_set" + - os: "ubuntu-20.04" + php-version: "8.1" + mongodb-version: "4.4" + driver-version: "mongodb/mongo-php-driver@master" + topology: "sharded_cluster" + + steps: + - name: "Checkout" + uses: "actions/checkout@v4" + with: + fetch-depth: 2 + + - id: setup-mongodb + uses: mongodb-labs/drivers-evergreen-tools@master + with: + version: ${{ matrix.mongodb-version }} + topology: ${{ matrix.topology }} + + - name: Setup cache environment + id: extcache + uses: shivammathur/cache-extensions@v1 + with: + php-version: ${{ matrix.php-version }} + extensions: "mongodb-${{ matrix.driver-version }}" + key: "extcache-v1" + + - name: Cache extensions + uses: actions/cache@v3 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ steps.extcache.outputs.key }} + restore-keys: ${{ steps.extcache.outputs.key }} + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + php-version: "${{ matrix.php-version }}" + tools: "pecl" + extensions: "mongodb-${{ matrix.driver-version }}" + coverage: "none" + ini-values: "zend.assertions=1" + + - name: "Show driver information" + run: "php --ri mongodb" + + - name: "Install dependencies with Composer" + uses: "ramsey/composer-install@2.2.0" + with: + composer-options: "--no-suggest" + + - name: "Run PHPUnit" + run: "vendor/bin/simple-phpunit -v" + env: + SYMFONY_DEPRECATIONS_HELPER: 999999 + MONGODB_URI: ${{ steps.setup-mongodb.outputs.cluster-uri }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..3062d6901 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,116 @@ +# Contributing to the PHP Builder for MongoDB + +## Initializing the Repository + +Developers who would like to contribute to the library will need to clone it and +initialize the project dependencies with [Composer](https://getcomposer.org/): + +``` +$ git clone https://github.com/mongodb/mongo-php-builder.git +$ cd mongo-php-builder +$ composer update +``` + +In addition to installing project dependencies, Composer will check that the +required extension version is installed. Directions for installing the extension +may be found [here](https://php.net/manual/en/mongodb.installation.php). + +Installation directions for Composer may be found in its +[Getting Started](https://getcomposer.org/doc/00-intro.md) guide. + +## Testing + +The library's test suite uses [PHPUnit](https://phpunit.de/), which is installed +through the [PHPUnit Bridge](https://symfony.com/phpunit-bridge) dependency by +Composer. + +The test suite may be executed with: + +```console +$ composer run test +``` + +The `phpunit.xml.dist` file is used as the default configuration file for the +test suite. In addition to various PHPUnit options, it defines environment +variables such as `MONGODB_URI` and `MONGODB_DATABASE`. You may customize +this configuration by creating your own `phpunit.xml` file based on the +`phpunit.xml.dist` file we provide. + +By default, the `simple-phpunit` binary chooses the correct PHPUnit version for +the PHP version you are running. To run tests against a specific PHPUnit +version, use the `SYMFONY_PHPUNIT_VERSION` environment variable: + +```console +$ SYMFONY_PHPUNIT_VERSION=8.5 vendor/bin/simple-phpunit +``` + +### Environment Variables + +The test suite references the following environment variables: + + * `MONGODB_DATABASE`: Default database to use in tests. Defaults to + `phplib_test`. + * `MONGODB_PASSWORD`: If specified, this value will be appended as the + `password` URI option for clients constructed by the test suite, which will + override any credentials in the connection string itself. + * `MONGODB_URI`: Connection string. Defaults to `mongodb://127.0.0.1/`, which + assumes a MongoDB server is listening on localhost port 27017. + * `MONGODB_USERNAME`: If specified, this value will be appended as the + `username` URI option for clients constructed by the test suite, which will + override any credentials in the connection string itself. + +## Code quality + +Before submitting a pull request, please ensure that your code adheres to the +coding standards and passes static analysis checks. + +```console +$ composer run checks +``` + +### Coding standards + +The library's code is checked using [PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer), +which is installed as a development dependency by Composer. To check the code +for style errors, run the `phpcs` binary: + +```console +$ vendor/bin/phpcs +``` + +To automatically fix all fixable errors, use the `phpcbf` binary: + +```console +$ vendor/bin/phpcbf +``` + +### Static analysis + +The library uses [psalm](https://psalm.dev) to run static analysis on the code +and ensure an additional level of type safety. New code is expected to adhere +to level 1, with a baseline covering existing issues. To run static analysis +checks, run the `psalm` binary: + +```console +$ vendor/bin/psalm +``` + +To remove fixed errors from the baseline, you can use the `update-baseline` +command-line argument: + +```console +$ vendor/bin/psalm --update-baseline +``` + +Note that this will not add new errors to the baseline. New errors should be +fixed instead of being added to the technical debt, but in case this isn't +possible it can be added to the baseline using `set-baseline`: + +```console +$ vendor/bin/psalm --set-baseline=psalm-baseline.xml +``` + +## Releasing + +The releases are created by the maintainers of the library. The process is documented in +the [RELEASING.md](RELEASING.md) file. diff --git a/README.md b/README.md index fe3a44227..22db72073 100644 --- a/README.md +++ b/README.md @@ -1 +1,21 @@ -# mongo-php-builder +# MongoDB PHP Builder + +Query and Aggregation Builder for MongoDB PHP Library. + +This package is experimental and requires PHP 8.1 or higher. Once it is stable, it will be merged into `mongodb/mongodb`. + +## Installation + +```console +$ composer require mongodb/builder +``` + +## Usage + + +## Development + +Development is tracked in the +[PHPLIB](https://jira.mongodb.org/projects/PHPLIB/summary) project in MongoDB's +JIRA. Documentation for contributing to this project may be found in +[CONTRIBUTING.md](CONTRIBUTING.md). diff --git a/composer.json b/composer.json index c39dd374e..a2229618f 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "mongodb/builder", - "description": "MongoDB driver query and aggregation builders", + "description": "Query and Aggregation Builder for MongoDB", "keywords": ["database", "driver", "mongodb", "persistence"], "homepage": "https://jira.mongodb.org/browse/PHPLIB", "license": "Apache-2.0", @@ -10,7 +10,9 @@ { "name": "Jérôme Tamarelle", "email": "jerome.tamarelle@mongodb.com" } ], "require": { - "php": "^8.1" + "php": ">=8.1", + "ext-mongodb": "^1.17.0", + "mongodb/mongodb": "^1.17.0@dev" }, "require-dev": { "doctrine/coding-standard": "^11.1", @@ -20,7 +22,8 @@ "vimeo/psalm": "^5.13" }, "autoload": { - "psr-4": { "MongoDB\\": "src/" } + "psr-4": { "MongoDB\\": "src/" }, + "files": [ "src/functions.php" ] }, "autoload-dev": { "psr-4": { diff --git a/generator/README.md b/generator/README.md new file mode 100644 index 000000000..5f05180d9 --- /dev/null +++ b/generator/README.md @@ -0,0 +1,18 @@ +# Code Generator for MongoDB PHP Library + +This subproject is used to generate the code that is committed to the repository. +The `generator` directory is not included in `mongodb/builder` package and is not installed by Composer. + +## Contributing + +Updating the generated code can be done only by modifying the code generator, or its configuration. + +To run the generator, you need to have PHP 8.1+ installed and Composer. + +1. Move to the `generator` directory: `cd generator` +1. Install dependencies: `composer install` +1. Run the generator: `./generate` + +## Configuration + +The `generator/config/*.yaml` files contains the list of operators and stages that are supported by the library. diff --git a/generator/composer.json b/generator/composer.json new file mode 100644 index 000000000..929523725 --- /dev/null +++ b/generator/composer.json @@ -0,0 +1,34 @@ +{ + "name": "mongodb/code-generator", + "type": "project", + "repositories": [ + { + "type": "path", + "url": "../", + "symlink": true + } + ], + "replace": { + "symfony/polyfill-php80": "*", + "symfony/polyfill-php81": "*" + }, + "require": { + "php": ">=8.1", + "ext-mongodb": "^1.16.0", + "mongodb/mongodb": "^1.17.0@dev", + "mongodb/builder": "@dev", + "nette/php-generator": "^4", + "symfony/console": "^6.3|^7.0", + "symfony/finder": "^6.3|^7.0", + "symfony/yaml": "^6.3|^7.0" + }, + "license": "Apache-2.0", + "autoload": { + "psr-4": { + "MongoDB\\CodeGenerator\\": "src/" + } + }, + "config": { + "sort-packages": true + } +} diff --git a/generator/config/accumulator/accumulator.yaml b/generator/config/accumulator/accumulator.yaml new file mode 100644 index 000000000..ed201787b --- /dev/null +++ b/generator/config/accumulator/accumulator.yaml @@ -0,0 +1,54 @@ +# $schema: ../schema.json +name: $accumulator +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/accumulator/' +type: + - accumulator +encode: object +description: | + Defines a custom accumulator function. + New in MongoDB 4.4. +arguments: + - + name: init + type: + - string + description: | + Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. + - + name: initArgs + type: + - resolvesToArray + optional: true + description: | + Arguments passed to the init function. + - + name: accumulate + type: + - string + description: | + Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. + - + name: accumulateArgs + type: + - resolvesToArray + description: | + Arguments passed to the accumulate function. You can use accumulateArgs to specify what field value(s) to pass to the accumulate function. + - + name: merge + type: + - string + description: | + Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. + - + name: finalize + type: + - string + optional: true + description: | + Function used to update the result of the accumulation. + - + name: lang + type: + - string + description: | + The language used in the $accumulator code. diff --git a/generator/config/accumulator/addToSet.yaml b/generator/config/accumulator/addToSet.yaml new file mode 100644 index 000000000..4ea79e704 --- /dev/null +++ b/generator/config/accumulator/addToSet.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $addToSet +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/addToSet/' +type: + - accumulator + - window +encode: single +description: | + Returns an array of unique expression values for each group. Order of the array elements is undefined. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/accumulator/avg.yaml b/generator/config/accumulator/avg.yaml new file mode 100644 index 000000000..7854f3d99 --- /dev/null +++ b/generator/config/accumulator/avg.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $avg +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/avg/' +type: + - accumulator + - window +encode: single +description: | + Returns an average of numerical values. Ignores non-numeric values. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - resolvesToNumber diff --git a/generator/config/accumulator/bottom.yaml b/generator/config/accumulator/bottom.yaml new file mode 100644 index 000000000..bddfa2274 --- /dev/null +++ b/generator/config/accumulator/bottom.yaml @@ -0,0 +1,23 @@ +# $schema: ../schema.json +name: $bottom +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottom/' +type: + - accumulator + - window +encode: object +description: | + Returns the bottom element within a group according to the specified sort order. + New in MongoDB 5.2: Available in the $group and $setWindowFields stages. +arguments: + - + name: sortBy + type: + - object # SortSpec + description: | + Specifies the order of results, with syntax similar to $sort. + - + name: output + type: + - expression + description: | + Represents the output for each element in the group and can be any expression. diff --git a/generator/config/accumulator/bottomN.yaml b/generator/config/accumulator/bottomN.yaml new file mode 100644 index 000000000..84e48cc18 --- /dev/null +++ b/generator/config/accumulator/bottomN.yaml @@ -0,0 +1,30 @@ +# $schema: ../schema.json +name: $bottomN +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottomN/' +type: + - accumulator + - window +encode: object +description: | + Returns an aggregation of the bottom n elements within a group, according to the specified sort order. If the group contains fewer than n elements, $bottomN returns all elements in the group. + New in MongoDB 5.2. + Available in the $group and $setWindowFields stages. +arguments: + - + name: 'n' + type: + - resolvesToInt + description: | + Limits the number of results per group and has to be a positive integral expression that is either a constant or depends on the _id value for $group. + - + name: sortBy + type: + - object # SortSpec + description: | + Specifies the order of results, with syntax similar to $sort. + - + name: output + type: + - expression + description: | + Represents the output for each element in the group and can be any expression. diff --git a/generator/config/accumulator/count.yaml b/generator/config/accumulator/count.yaml new file mode 100644 index 000000000..158d0a9d8 --- /dev/null +++ b/generator/config/accumulator/count.yaml @@ -0,0 +1,11 @@ +# $schema: ../schema.json +name: $count +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/count-accumulator/' +type: + - accumulator + - window +encode: object +description: | + Returns the number of documents in the group or window. + Distinct from the $count pipeline stage. + New in MongoDB 5.0. diff --git a/generator/config/accumulator/covariancePop.yaml b/generator/config/accumulator/covariancePop.yaml new file mode 100644 index 000000000..758b90bb7 --- /dev/null +++ b/generator/config/accumulator/covariancePop.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $covariancePop +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/covariancePop/' +type: + - window +encode: array +description: | + Returns the population covariance of two numeric expressions. + New in MongoDB 5.0. +arguments: + - + name: expression1 + type: + - resolvesToNumber + - + name: expression2 + type: + - resolvesToNumber diff --git a/generator/config/accumulator/covarianceSamp.yaml b/generator/config/accumulator/covarianceSamp.yaml new file mode 100644 index 000000000..072c5a7de --- /dev/null +++ b/generator/config/accumulator/covarianceSamp.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $covarianceSamp +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/covarianceSamp/' +type: + - window +encode: array +description: | + Returns the sample covariance of two numeric expressions. + New in MongoDB 5.0. +arguments: + - + name: expression1 + type: + - resolvesToNumber + - + name: expression2 + type: + - resolvesToNumber diff --git a/generator/config/accumulator/denseRank.yaml b/generator/config/accumulator/denseRank.yaml new file mode 100644 index 000000000..2a587ccea --- /dev/null +++ b/generator/config/accumulator/denseRank.yaml @@ -0,0 +1,9 @@ +# $schema: ../schema.json +name: $denseRank +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/denseRank/' +type: + - window +encode: object +description: | + Returns the document position (known as the rank) relative to other documents in the $setWindowFields stage partition. There are no gaps in the ranks. Ties receive the same rank. + New in MongoDB 5.0. diff --git a/generator/config/accumulator/derivative.yaml b/generator/config/accumulator/derivative.yaml new file mode 100644 index 000000000..974b4d669 --- /dev/null +++ b/generator/config/accumulator/derivative.yaml @@ -0,0 +1,23 @@ +# $schema: ../schema.json +name: $derivative +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/derivative/' +type: + - window +encode: object +description: | + Returns the average rate of change within the specified window. + New in MongoDB 5.0. +arguments: + - + name: input + type: + - resolvesToNumber + - resolvesToDate + - + name: unit + type: + - string + optional: true + description: | + A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. diff --git a/generator/config/accumulator/documentNumber.yaml b/generator/config/accumulator/documentNumber.yaml new file mode 100644 index 000000000..3ae8c8c01 --- /dev/null +++ b/generator/config/accumulator/documentNumber.yaml @@ -0,0 +1,9 @@ +# $schema: ../schema.json +name: $documentNumber +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/documentNumber/' +type: + - window +encode: object +description: | + Returns the position of a document (known as the document number) in the $setWindowFields stage partition. Ties result in different adjacent document numbers. + New in MongoDB 5.0. diff --git a/generator/config/accumulator/expMovingAvg.yaml b/generator/config/accumulator/expMovingAvg.yaml new file mode 100644 index 000000000..981cde465 --- /dev/null +++ b/generator/config/accumulator/expMovingAvg.yaml @@ -0,0 +1,31 @@ +# $schema: ../schema.json +name: $expMovingAvg +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/expMovingAvg/' +type: + - window +encode: object +description: | + Returns the exponential moving average for the numeric expression. + New in MongoDB 5.0. +arguments: + - + name: input + type: + - resolvesToNumber + - + name: 'N' + type: + - int + optional: true + description: | + An integer that specifies the number of historical documents that have a significant mathematical weight in the exponential moving average calculation, with the most recent documents contributing the most weight. + You must specify either N or alpha. You cannot specify both. + The N value is used in this formula to calculate the current result based on the expression value from the current document being read and the previous result of the calculation: + - + name: alpha + type: + - double + optional: true + description: | + A double that specifies the exponential decay value to use in the exponential moving average calculation. A higher alpha value assigns a lower mathematical significance to previous results from the calculation. + You must specify either N or alpha. You cannot specify both. diff --git a/generator/config/accumulator/first.yaml b/generator/config/accumulator/first.yaml new file mode 100644 index 000000000..2de21e535 --- /dev/null +++ b/generator/config/accumulator/first.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $first +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/' +type: + - accumulator + - window +encode: single +description: | + Returns the result of an expression for the first document in a group or window. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/accumulator/firstN.yaml b/generator/config/accumulator/firstN.yaml new file mode 100644 index 000000000..06ea49bcc --- /dev/null +++ b/generator/config/accumulator/firstN.yaml @@ -0,0 +1,22 @@ +# $schema: ../schema.json +name: $firstN +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/' +type: + - accumulator + - window +encode: object +description: | + Returns a specified number of elements from the beginning of an array. Distinct from the $firstN accumulator. +arguments: + - + name: input + type: + - resolvesToArray + description: | + An expression that resolves to the array from which to return n elements. + - + name: 'n' + type: + - resolvesToInt + description: | + An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. diff --git a/generator/config/accumulator/last.yaml b/generator/config/accumulator/last.yaml new file mode 100644 index 000000000..175846ef4 --- /dev/null +++ b/generator/config/accumulator/last.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $last +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/' +type: + - accumulator + - window +encode: single +description: | + Returns the result of an expression for the last document in a group or window. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/accumulator/lastN.yaml b/generator/config/accumulator/lastN.yaml new file mode 100644 index 000000000..224c05c94 --- /dev/null +++ b/generator/config/accumulator/lastN.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $lastN +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/' +type: + - window +encode: object +description: | + Returns a specified number of elements from the end of an array. Distinct from the $lastN accumulator. +arguments: + - + name: input + type: + - resolvesToArray + description: | + An expression that resolves to the array from which to return n elements. + - + name: 'n' + type: + - resolvesToInt + description: | + An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. diff --git a/generator/config/accumulator/max.yaml b/generator/config/accumulator/max.yaml new file mode 100644 index 000000000..e183d86a8 --- /dev/null +++ b/generator/config/accumulator/max.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $max +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/' +type: + - accumulator + - window +encode: single +description: | + Returns the maximum value that results from applying an expression to each document. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/accumulator/maxN.yaml b/generator/config/accumulator/maxN.yaml new file mode 100644 index 000000000..ba0941cf8 --- /dev/null +++ b/generator/config/accumulator/maxN.yaml @@ -0,0 +1,22 @@ +# $schema: ../schema.json +name: $maxN +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN/' +type: + - accumulator + - window +encode: object +description: | + Returns the n largest values in an array. Distinct from the $maxN accumulator. +arguments: + - + name: input + type: + - resolvesToArray + description: | + An expression that resolves to the array from which to return the maximal n elements. + - + name: 'n' + type: + - resolvesToInt + description: | + An expression that resolves to a positive integer. The integer specifies the number of array elements that $maxN returns. diff --git a/generator/config/accumulator/median.yaml b/generator/config/accumulator/median.yaml new file mode 100644 index 000000000..0cef61fef --- /dev/null +++ b/generator/config/accumulator/median.yaml @@ -0,0 +1,27 @@ +# $schema: ../schema.json +name: $median +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/' +type: + - accumulator + - window +encode: object +description: | + Returns an approximation of the median, the 50th percentile, as a scalar value. + New in MongoDB 7.0. + This operator is available as an accumulator in these stages: + $group + $setWindowFields + It is also available as an aggregation expression. +arguments: + - + name: input + type: + - resolvesToNumber + description: | + $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. + - + name: method + type: + - string # AccumulatorPercentile + description: | + The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. diff --git a/generator/config/accumulator/mergeObjects.yaml b/generator/config/accumulator/mergeObjects.yaml new file mode 100644 index 000000000..bddb1db6f --- /dev/null +++ b/generator/config/accumulator/mergeObjects.yaml @@ -0,0 +1,16 @@ +# $schema: ../schema.json +name: $mergeObjects +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/' +type: + - accumulator +encode: single +description: | + Combines multiple documents into a single document. +arguments: + - + name: document + type: + - resolvesToObject + variadic: array + description: | + Any valid expression that resolves to a document. diff --git a/generator/config/accumulator/min.yaml b/generator/config/accumulator/min.yaml new file mode 100644 index 000000000..346cf3ff9 --- /dev/null +++ b/generator/config/accumulator/min.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $min +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/' +type: + - accumulator + - window +encode: single +description: | + Returns the minimum value that results from applying an expression to each document. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/accumulator/minN.yaml b/generator/config/accumulator/minN.yaml new file mode 100644 index 000000000..50737b084 --- /dev/null +++ b/generator/config/accumulator/minN.yaml @@ -0,0 +1,22 @@ +# $schema: ../schema.json +name: $minN +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN/' +type: + - accumulator + - window +encode: object +description: | + Returns the n smallest values in an array. Distinct from the $minN accumulator. +arguments: + - + name: input + type: + - resolvesToArray + description: | + An expression that resolves to the array from which to return the maximal n elements. + - + name: 'n' + type: + - resolvesToInt + description: | + An expression that resolves to a positive integer. The integer specifies the number of array elements that $maxN returns. diff --git a/generator/config/accumulator/percentile.yaml b/generator/config/accumulator/percentile.yaml new file mode 100644 index 000000000..c3195347c --- /dev/null +++ b/generator/config/accumulator/percentile.yaml @@ -0,0 +1,37 @@ +# $schema: ../schema.json +name: $percentile +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/' +type: + - accumulator + - window +encode: object +description: | + Returns an array of scalar values that correspond to specified percentile values. + New in MongoDB 7.0. + + This operator is available as an accumulator in these stages: + $group + + $setWindowFields + + It is also available as an aggregation expression. +arguments: + - + name: input + type: + - resolvesToNumber + description: | + $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. + - + name: p + type: + - resolvesToArray # of resolvesToNumber + description: | + $percentile calculates a percentile value for each element in p. The elements represent percentages and must evaluate to numeric values in the range 0.0 to 1.0, inclusive. + $percentile returns results in the same order as the elements in p. + - + name: method + type: + - string # AccumulatorPercentile + description: | + The method that mongod uses to calculate the percentile value. The method must be 'approximate'. diff --git a/generator/config/accumulator/push.yaml b/generator/config/accumulator/push.yaml new file mode 100644 index 000000000..c026d4b4c --- /dev/null +++ b/generator/config/accumulator/push.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $push +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/push/' +type: + - accumulator + - window +encode: single +description: | + Returns an array of values that result from applying an expression to each document. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/accumulator/shift.yaml b/generator/config/accumulator/shift.yaml new file mode 100644 index 000000000..38bc36308 --- /dev/null +++ b/generator/config/accumulator/shift.yaml @@ -0,0 +1,34 @@ +# $schema: ../schema.json +name: $shift +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/shift/' +type: + - window +encode: object +description: | + Returns the value from an expression applied to a document in a specified position relative to the current document in the $setWindowFields stage partition. + New in MongoDB 5.0. +arguments: + - + name: output + type: + - expression + description: | + Specifies an expression to evaluate and return in the output. + - + name: by + type: + - int + description: | + Specifies an integer with a numeric document position relative to the current document in the output. + For example: + 1 specifies the document position after the current document. + -1 specifies the document position before the current document. + -2 specifies the document position that is two positions before the current document. + - + name: default + type: + - expression + description: | + Specifies an optional default expression to evaluate if the document position is outside of the implicit $setWindowFields stage window. The implicit window contains all the documents in the partition. + The default expression must evaluate to a constant value. + If you do not specify a default expression, $shift returns null for documents whose positions are outside of the implicit $setWindowFields stage window. diff --git a/generator/config/accumulator/stdDevPop.yaml b/generator/config/accumulator/stdDevPop.yaml new file mode 100644 index 000000000..836c4310b --- /dev/null +++ b/generator/config/accumulator/stdDevPop.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $stdDevPop +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/' +type: + - accumulator +encode: single +description: | + Calculates the population standard deviation of the input values. Use if the values encompass the entire population of data you want to represent and do not wish to generalize about a larger population. $stdDevPop ignores non-numeric values. + If the values represent only a sample of a population of data from which to generalize about the population, use $stdDevSamp instead. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - resolvesToNumber diff --git a/generator/config/accumulator/stdDevSamp.yaml b/generator/config/accumulator/stdDevSamp.yaml new file mode 100644 index 000000000..64f6799f2 --- /dev/null +++ b/generator/config/accumulator/stdDevSamp.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $stdDevSamp +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevSamp/' +type: + - accumulator +encode: single +description: | + Calculates the sample standard deviation of the input values. Use if the values encompass a sample of a population of data from which to generalize about the population. $stdDevSamp ignores non-numeric values. + If the values represent the entire population of data or you do not wish to generalize about a larger population, use $stdDevPop instead. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - resolvesToNumber diff --git a/generator/config/accumulator/sum.yaml b/generator/config/accumulator/sum.yaml new file mode 100644 index 000000000..8dc498358 --- /dev/null +++ b/generator/config/accumulator/sum.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $sum +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/' +type: + - accumulator + - window +encode: single +description: | + Returns a sum of numerical values. Ignores non-numeric values. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - resolvesToNumber diff --git a/generator/config/accumulator/top.yaml b/generator/config/accumulator/top.yaml new file mode 100644 index 000000000..3f298fe55 --- /dev/null +++ b/generator/config/accumulator/top.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $top +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/top/' +type: + - accumulator +encode: object +description: | + Returns the top element within a group according to the specified sort order. + New in MongoDB 5.2. + + Available in the $group and $setWindowFields stages. +arguments: + - + name: sortBy + type: + - object # SortSpec + description: | + Specifies the order of results, with syntax similar to $sort. + - + name: output + type: + - expression + description: | + Represents the output for each element in the group and can be any expression. diff --git a/generator/config/accumulator/topN.yaml b/generator/config/accumulator/topN.yaml new file mode 100644 index 000000000..a3fa45d6f --- /dev/null +++ b/generator/config/accumulator/topN.yaml @@ -0,0 +1,30 @@ +# $schema: ../schema.json +name: $topN +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/topN/' +type: + - accumulator +encode: object +description: | + Returns an aggregation of the top n fields within a group, according to the specified sort order. + New in MongoDB 5.2. + + Available in the $group and $setWindowFields stages. +arguments: + - + name: 'n' + type: + - resolvesToInt + description: | + limits the number of results per group and has to be a positive integral expression that is either a constant or depends on the _id value for $group. + - + name: sortBy + type: + - object # SortSpec + description: | + Specifies the order of results, with syntax similar to $sort. + - + name: output + type: + - expression + description: | + Represents the output for each element in the group and can be any expression. diff --git a/generator/config/definitions.php b/generator/config/definitions.php new file mode 100644 index 000000000..3581acb50 --- /dev/null +++ b/generator/config/definitions.php @@ -0,0 +1,65 @@ + __DIR__ . '/stage', + 'namespace' => 'MongoDB\\Builder\\Stage', + 'classNameSuffix' => 'Stage', + 'generators' => [ + OperatorClassGenerator::class, + OperatorFactoryGenerator::class, + ], + ], + + // Aggregation Pipeline Accumulator and Window Operators + [ + 'configFiles' => __DIR__ . '/accumulator', + 'namespace' => 'MongoDB\\Builder\\Accumulator', + 'classNameSuffix' => 'Accumulator', + 'generators' => [ + OperatorClassGenerator::class, + OperatorFactoryGenerator::class, + ], + ], + + // Aggregation Pipeline Expression + [ + 'configFiles' => __DIR__ . '/expression', + 'namespace' => 'MongoDB\\Builder\\Expression', + 'classNameSuffix' => 'Operator', + 'generators' => [ + OperatorClassGenerator::class, + OperatorFactoryGenerator::class, + ], + ], + + // Query Operators + [ + 'configFiles' => __DIR__ . '/query', + 'namespace' => 'MongoDB\\Builder\\Query', + 'classNameSuffix' => 'Operator', + 'generators' => [ + OperatorClassGenerator::class, + OperatorFactoryGenerator::class, + ], + ], + + // Projection Operators + [ + 'configFiles' => __DIR__ . '/projection', + 'namespace' => 'MongoDB\\Builder\\Projection', + 'classNameSuffix' => 'Operator', + 'generators' => [ + OperatorClassGenerator::class, + OperatorFactoryGenerator::class, + ], + ], +]; diff --git a/generator/config/expression/abs.yaml b/generator/config/expression/abs.yaml new file mode 100644 index 000000000..029b33d4c --- /dev/null +++ b/generator/config/expression/abs.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $abs +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/abs/' +type: + - resolvesToNumber +encode: single +description: | + Returns the absolute value of a number. +arguments: + - + name: value + type: + - resolvesToNumber diff --git a/generator/config/expression/acos.yaml b/generator/config/expression/acos.yaml new file mode 100644 index 000000000..07d0ea2d3 --- /dev/null +++ b/generator/config/expression/acos.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $acos +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/acos/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the inverse cosine (arc cosine) of a value in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $acos takes any valid expression that resolves to a number between -1 and 1, e.g. -1 <= value <= 1. + $acos returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. + By default $acos returns values as a double. $acos can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. diff --git a/generator/config/expression/acosh.yaml b/generator/config/expression/acosh.yaml new file mode 100644 index 000000000..8e516df0b --- /dev/null +++ b/generator/config/expression/acosh.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $acosh +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/acosh/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the inverse hyperbolic cosine (hyperbolic arc cosine) of a value in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $acosh takes any valid expression that resolves to a number between 1 and +Infinity, e.g. 1 <= value <= +Infinity. + $acosh returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. + By default $acosh returns values as a double. $acosh can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. diff --git a/generator/config/expression/add.yaml b/generator/config/expression/add.yaml new file mode 100644 index 000000000..cd3c70ae2 --- /dev/null +++ b/generator/config/expression/add.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $add +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/add/' +type: + - resolvesToNumber + - resolvesToDate +encode: array +description: | + Adds numbers to return the sum, or adds numbers and a date to return a new date. If adding numbers and a date, treats the numbers as milliseconds. Accepts any number of argument expressions, but at most, one expression can resolve to a date. +arguments: + - + name: expression + type: + - resolvesToNumber + - resolvesToDate + variadic: array + description: | + The arguments can be any valid expression as long as they resolve to either all numbers or to numbers and a date. diff --git a/generator/config/expression/allElementsTrue.yaml b/generator/config/expression/allElementsTrue.yaml new file mode 100644 index 000000000..1a9751a00 --- /dev/null +++ b/generator/config/expression/allElementsTrue.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $allElementsTrue +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/allElementsTrue/' +type: + - resolvesToBool +encode: array +description: | + Returns true if no element of a set evaluates to false, otherwise, returns false. Accepts a single argument expression. +arguments: + - + name: expression + type: + - resolvesToArray + variadic: array diff --git a/generator/config/expression/and.yaml b/generator/config/expression/and.yaml new file mode 100644 index 000000000..511ef776d --- /dev/null +++ b/generator/config/expression/and.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $and +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/and/' +type: + - resolvesToBool +encode: single +description: | + Returns true only when all its expressions evaluate to true. Accepts any number of argument expressions. +arguments: + - + name: expression + type: + - expression + - resolvesToBool + - resolvesToNumber + - resolvesToString + - resolvesToNull + variadic: array diff --git a/generator/config/expression/anyElementTrue.yaml b/generator/config/expression/anyElementTrue.yaml new file mode 100644 index 000000000..14c96506d --- /dev/null +++ b/generator/config/expression/anyElementTrue.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $anyElementTrue +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/anyElementTrue/' +type: + - resolvesToBool +encode: array +description: | + Returns true if any elements of a set evaluate to true; otherwise, returns false. Accepts a single argument expression. +arguments: + - + name: expression + type: + - resolvesToArray diff --git a/generator/config/expression/arrayElemAt.yaml b/generator/config/expression/arrayElemAt.yaml new file mode 100644 index 000000000..6baa62c9c --- /dev/null +++ b/generator/config/expression/arrayElemAt.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $arrayElemAt +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayElemAt/' +type: + - resolvesToAny +encode: array +description: | + Returns the element at the specified array index. +arguments: + - + name: array + type: + - resolvesToArray + - + name: idx + type: + - resolvesToInt diff --git a/generator/config/expression/arrayToObject.yaml b/generator/config/expression/arrayToObject.yaml new file mode 100644 index 000000000..78e5055c8 --- /dev/null +++ b/generator/config/expression/arrayToObject.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $arrayToObject +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayToObject/' +type: + - resolvesToObject +encode: array +description: | + Converts an array of key value pairs to a document. +arguments: + - + name: array + type: + - resolvesToArray diff --git a/generator/config/expression/asin.yaml b/generator/config/expression/asin.yaml new file mode 100644 index 000000000..169ed50f6 --- /dev/null +++ b/generator/config/expression/asin.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $asin +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/asin/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the inverse sin (arc sine) of a value in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $asin takes any valid expression that resolves to a number between -1 and 1, e.g. -1 <= value <= 1. + $asin returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. + By default $asin returns values as a double. $asin can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. diff --git a/generator/config/expression/asinh.yaml b/generator/config/expression/asinh.yaml new file mode 100644 index 000000000..f85049f9b --- /dev/null +++ b/generator/config/expression/asinh.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $asinh +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/asinh/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the inverse hyperbolic sine (hyperbolic arc sine) of a value in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $asinh takes any valid expression that resolves to a number. + $asinh returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. + By default $asinh returns values as a double. $asinh can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. diff --git a/generator/config/expression/atan.yaml b/generator/config/expression/atan.yaml new file mode 100644 index 000000000..9f48641db --- /dev/null +++ b/generator/config/expression/atan.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $atan +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/atan/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the inverse tangent (arc tangent) of a value in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $atan takes any valid expression that resolves to a number. + $atan returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. + By default $atan returns values as a double. $atan can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. diff --git a/generator/config/expression/atan2.yaml b/generator/config/expression/atan2.yaml new file mode 100644 index 000000000..f4312f8de --- /dev/null +++ b/generator/config/expression/atan2.yaml @@ -0,0 +1,22 @@ +# $schema: ../schema.json +name: $atan2 +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/atan2/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: array +description: | + Returns the inverse tangent (arc tangent) of y / x in radians, where y and x are the first and second values passed to the expression respectively. +arguments: + - + name: 'y' + type: + - resolvesToNumber + description: | + $atan2 takes any valid expression that resolves to a number. + $atan2 returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. + By default $atan returns values as a double. $atan2 can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. + - + name: x + type: + - resolvesToNumber diff --git a/generator/config/expression/atanh.yaml b/generator/config/expression/atanh.yaml new file mode 100644 index 000000000..41a4901d5 --- /dev/null +++ b/generator/config/expression/atanh.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $atanh +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/atanh/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the inverse hyperbolic tangent (hyperbolic arc tangent) of a value in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $atanh takes any valid expression that resolves to a number between -1 and 1, e.g. -1 <= value <= 1. + $atanh returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. + By default $atanh returns values as a double. $atanh can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. diff --git a/generator/config/expression/avg.yaml b/generator/config/expression/avg.yaml new file mode 100644 index 000000000..554e8037a --- /dev/null +++ b/generator/config/expression/avg.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $avg +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/avg/' +type: + - resolvesToNumber +encode: single +description: | + Returns an average of numerical values. Ignores non-numeric values. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - resolvesToNumber + variadic: array diff --git a/generator/config/expression/binarySize.yaml b/generator/config/expression/binarySize.yaml new file mode 100644 index 000000000..a4f15e6bd --- /dev/null +++ b/generator/config/expression/binarySize.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $binarySize +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/binarySize/' +type: + - resolvesToInt +encode: single +description: | + Returns the size of a given string or binary data value's content in bytes. +arguments: + - + name: expression + type: + - resolvesToString + - resolvesToBinData + - resolvesToNull diff --git a/generator/config/expression/bitAnd.yaml b/generator/config/expression/bitAnd.yaml new file mode 100644 index 000000000..7a412145c --- /dev/null +++ b/generator/config/expression/bitAnd.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $bitAnd +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitAnd/' +type: + - resolvesToInt + - resolvesToLong +encode: single +description: | + Returns the result of a bitwise and operation on an array of int or long values. + New in MongoDB 6.3. +arguments: + - + name: expression + type: + - resolvesToInt + - resolvesToLong + variadic: array diff --git a/generator/config/expression/bitNot.yaml b/generator/config/expression/bitNot.yaml new file mode 100644 index 000000000..ebd937899 --- /dev/null +++ b/generator/config/expression/bitNot.yaml @@ -0,0 +1,16 @@ +# $schema: ../schema.json +name: $bitNot +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitNot/' +type: + - resolvesToInt + - resolvesToLong +encode: single +description: | + Returns the result of a bitwise not operation on a single argument or an array that contains a single int or long value. + New in MongoDB 6.3. +arguments: + - + name: expression + type: + - resolvesToInt + - resolvesToLong diff --git a/generator/config/expression/bitOr.yaml b/generator/config/expression/bitOr.yaml new file mode 100644 index 000000000..afd187fff --- /dev/null +++ b/generator/config/expression/bitOr.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $bitOr +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitOr/' +type: + - resolvesToInt + - resolvesToLong +encode: single +description: | + Returns the result of a bitwise or operation on an array of int or long values. + New in MongoDB 6.3. +arguments: + - + name: expression + type: + - resolvesToInt + - resolvesToLong + variadic: array diff --git a/generator/config/expression/bitXor.yaml b/generator/config/expression/bitXor.yaml new file mode 100644 index 000000000..eafd0bd27 --- /dev/null +++ b/generator/config/expression/bitXor.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $bitXor +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitXor/' +type: + - resolvesToInt + - resolvesToLong +encode: single +description: | + Returns the result of a bitwise xor (exclusive or) operation on an array of int and long values. + New in MongoDB 6.3. +arguments: + - + name: expression + type: + - resolvesToInt + - resolvesToLong + variadic: array diff --git a/generator/config/expression/bsonSize.yaml b/generator/config/expression/bsonSize.yaml new file mode 100644 index 000000000..69fbd4122 --- /dev/null +++ b/generator/config/expression/bsonSize.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $bsonSize +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bsonSize/' +type: + - resolvesToInt +encode: single +description: | + Returns the size in bytes of a given document (i.e. bsontype Object) when encoded as BSON. +arguments: + - + name: object + type: + - resolvesToObject + - resolvesToNull diff --git a/generator/config/expression/ceil.yaml b/generator/config/expression/ceil.yaml new file mode 100644 index 000000000..b33567720 --- /dev/null +++ b/generator/config/expression/ceil.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $ceil +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/ceil/' +type: + - resolvesToInt +encode: single +description: | + Returns the smallest integer greater than or equal to the specified number. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + If the argument resolves to a value of null or refers to a field that is missing, $ceil returns null. If the argument resolves to NaN, $ceil returns NaN. diff --git a/generator/config/expression/cmp.yaml b/generator/config/expression/cmp.yaml new file mode 100644 index 000000000..5c70e13f9 --- /dev/null +++ b/generator/config/expression/cmp.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $cmp +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/cmp/' +type: + - resolvesToInt +encode: array +description: | + Returns 0 if the two values are equivalent, 1 if the first value is greater than the second, and -1 if the first value is less than the second. +arguments: + - + name: expression1 + type: + - expression + - + name: expression2 + type: + - expression diff --git a/generator/config/expression/concat.yaml b/generator/config/expression/concat.yaml new file mode 100644 index 000000000..531a8c201 --- /dev/null +++ b/generator/config/expression/concat.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $concat +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/concat/' +type: + - resolvesToString +encode: single +description: | + Concatenates any number of strings. +arguments: + - + name: expression + type: + - resolvesToString + variadic: array diff --git a/generator/config/expression/concatArrays.yaml b/generator/config/expression/concatArrays.yaml new file mode 100644 index 000000000..a6e90df32 --- /dev/null +++ b/generator/config/expression/concatArrays.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $concatArrays +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/concatArrays/' +type: + - resolvesToArray +encode: single +description: | + Concatenates arrays to return the concatenated array. +arguments: + - + name: array + type: + - resolvesToArray + variadic: array diff --git a/generator/config/expression/cond.yaml b/generator/config/expression/cond.yaml new file mode 100644 index 000000000..386705fcb --- /dev/null +++ b/generator/config/expression/cond.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $cond +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/' +type: + - resolvesToAny +encode: object +description: | + A ternary operator that evaluates one expression, and depending on the result, returns the value of one of the other two expressions. Accepts either three expressions in an ordered list or three named parameters. +arguments: + - + name: if + type: + - resolvesToBool + - + name: then + type: + - expression + - + name: else + type: + - expression diff --git a/generator/config/expression/convert.yaml b/generator/config/expression/convert.yaml new file mode 100644 index 000000000..36475be8c --- /dev/null +++ b/generator/config/expression/convert.yaml @@ -0,0 +1,35 @@ +# $schema: ../schema.json +name: $convert +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/convert/' +type: + - resolvesToAny +encode: object +description: | + Converts a value to a specified type. + New in MongoDB 4.0. +arguments: + - + name: input + type: + - expression + - + name: to + type: + - resolvesToString + - resolvesToInt + - + name: onError + type: + - expression + optional: true + description: | + The value to return on encountering an error during conversion, including unsupported type conversions. The arguments can be any valid expression. + If unspecified, the operation throws an error upon encountering an error and stops. + - + name: onNull + type: + - expression + optional: true + description: | + The value to return if the input is null or missing. The arguments can be any valid expression. + If unspecified, $convert returns null if the input is null or missing. diff --git a/generator/config/expression/cos.yaml b/generator/config/expression/cos.yaml new file mode 100644 index 000000000..e9aa9bdd6 --- /dev/null +++ b/generator/config/expression/cos.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $cos +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/cos/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the cosine of a value that is measured in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $cos takes any valid expression that resolves to a number. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the result to radians. + By default $cos returns values as a double. $cos can also return values as a 128-bit decimal as long as the resolves to a 128-bit decimal value. diff --git a/generator/config/expression/cosh.yaml b/generator/config/expression/cosh.yaml new file mode 100644 index 000000000..a7963ab29 --- /dev/null +++ b/generator/config/expression/cosh.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $cosh +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/cosh/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the hyperbolic cosine of a value that is measured in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $cosh takes any valid expression that resolves to a number, measured in radians. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the value to radians. + By default $cosh returns values as a double. $cosh can also return values as a 128-bit decimal if the resolves to a 128-bit decimal value. diff --git a/generator/config/expression/dateAdd.yaml b/generator/config/expression/dateAdd.yaml new file mode 100644 index 000000000..c3f167f59 --- /dev/null +++ b/generator/config/expression/dateAdd.yaml @@ -0,0 +1,35 @@ +# $schema: ../schema.json +name: $dateAdd +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateAdd/' +type: + - resolvesToDate +encode: object +description: | + Adds a number of time units to a date object. +arguments: + - + name: startDate + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: unit + type: + - resolvesToString + description: | + The unit used to measure the amount of time added to the startDate. + - + name: amount + type: + - resolvesToInt + - resolvesToLong + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/dateDiff.yaml b/generator/config/expression/dateDiff.yaml new file mode 100644 index 000000000..6ff998154 --- /dev/null +++ b/generator/config/expression/dateDiff.yaml @@ -0,0 +1,45 @@ +# $schema: ../schema.json +name: $dateDiff +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateDiff/' +type: + - resolvesToInt +encode: object +description: | + Returns the difference between two dates. +arguments: + - + name: startDate + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The start of the time period. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: endDate + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The end of the time period. The endDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: unit + type: + - resolvesToString + description: | + The time measurement unit between the startDate and endDate + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + - + name: startOfWeek + type: + - resolvesToString + optional: true + description: | + Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string diff --git a/generator/config/expression/dateFromParts.yaml b/generator/config/expression/dateFromParts.yaml new file mode 100644 index 000000000..7759f7b96 --- /dev/null +++ b/generator/config/expression/dateFromParts.yaml @@ -0,0 +1,84 @@ +# $schema: ../schema.json +name: $dateFromParts +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateFromParts/' +type: + - resolvesToDate +encode: object +description: | + Constructs a BSON Date object given the date's constituent parts. +arguments: + - + name: year + type: + - resolvesToNumber + description: | + Calendar year. Can be any expression that evaluates to a number. + - + name: isoWeekYear + type: + - resolvesToNumber + description: | + ISO Week Date Year. Can be any expression that evaluates to a number. + - + name: month + type: + - resolvesToNumber + optional: true + description: | + Month. Defaults to 1. + - + name: isoWeek + type: + - resolvesToNumber + optional: true + description: | + Week of year. Defaults to 1. + - + name: day + type: + - resolvesToNumber + optional: true + description: | + Day of month. Defaults to 1. + - + name: isoDayOfWeek + type: + - resolvesToNumber + optional: true + description: | + Day of week (Monday 1 - Sunday 7). Defaults to 1. + - + name: hour + type: + - resolvesToNumber + optional: true + description: | + Hour. Defaults to 0. + - + name: minute + type: + - resolvesToNumber + optional: true + description: | + Minute. Defaults to 0. + - + name: second + type: + - resolvesToNumber + optional: true + description: | + Second. Defaults to 0. + - + name: millisecond + type: + - resolvesToNumber + optional: true + description: | + Millisecond. Defaults to 0. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/dateFromString.yaml b/generator/config/expression/dateFromString.yaml new file mode 100644 index 000000000..69c761f9a --- /dev/null +++ b/generator/config/expression/dateFromString.yaml @@ -0,0 +1,46 @@ +# $schema: ../schema.json +name: $dateFromString +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateFromString/' +type: + - resolvesToDate +encode: object +description: | + Converts a date/time string to a date object. +arguments: + - + name: dateString + type: + - resolvesToString + description: | + The date/time string to convert to a date object. + - + name: format + type: + - resolvesToString + optional: true + description: | + The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The time zone to use to format the date. + - + name: onError + type: + - expression + optional: true + description: | + If $dateFromString encounters an error while parsing the given dateString, it outputs the result value of the provided onError expression. This result value can be of any type. + If you do not specify onError, $dateFromString throws an error if it cannot parse dateString. + - + name: onNull + type: + - expression + optional: true + description: | + If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. This result value can be of any type. + If you do not specify onNull and dateString is null or missing, then $dateFromString outputs null. diff --git a/generator/config/expression/dateSubtract.yaml b/generator/config/expression/dateSubtract.yaml new file mode 100644 index 000000000..fc01ca0b4 --- /dev/null +++ b/generator/config/expression/dateSubtract.yaml @@ -0,0 +1,35 @@ +# $schema: ../schema.json +name: $dateSubtract +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateSubtract/' +type: + - resolvesToDate +encode: object +description: | + Subtracts a number of time units from a date object. +arguments: + - + name: startDate + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: unit + type: + - resolvesToString + description: | + The unit used to measure the amount of time added to the startDate. + - + name: amount + type: + - resolvesToInt + - resolvesToLong + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/dateToParts.yaml b/generator/config/expression/dateToParts.yaml new file mode 100644 index 000000000..21bace3ad --- /dev/null +++ b/generator/config/expression/dateToParts.yaml @@ -0,0 +1,31 @@ +# $schema: ../schema.json +name: $dateToParts +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToParts/' +type: + - resolvesToObject +encode: object +description: | + Returns a document containing the constituent parts of a date. +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The input date for which to return parts. date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + - + name: iso8601 + type: + - bool + optional: true + description: | + If set to true, modifies the output document to use ISO week date fields. Defaults to false. diff --git a/generator/config/expression/dateToString.yaml b/generator/config/expression/dateToString.yaml new file mode 100644 index 000000000..248cb9ffb --- /dev/null +++ b/generator/config/expression/dateToString.yaml @@ -0,0 +1,40 @@ +# $schema: ../schema.json +name: $dateToString +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/' +type: + - resolvesToString +encode: object +description: | + Returns the date as a formatted string. +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to convert to string. Must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: format + type: + - resolvesToString + optional: true + description: | + The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The time zone to use to format the date. + - + name: onNull + type: + - expression + optional: true + description: | + The value to return if the date is null or missing. + If unspecified, $dateToString returns null if the date is null or missing. diff --git a/generator/config/expression/dateTrunc.yaml b/generator/config/expression/dateTrunc.yaml new file mode 100644 index 000000000..2c3042b28 --- /dev/null +++ b/generator/config/expression/dateTrunc.yaml @@ -0,0 +1,47 @@ +# $schema: ../schema.json +name: $dateTrunc +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateTrunc/' +type: + - resolvesToDate +encode: object +description: | + Truncates a date. +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to truncate, specified in UTC. The date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: unit + type: + - resolvesToString + description: | + The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. + Together, binSize and unit specify the time period used in the $dateTrunc calculation. + - + name: binSize + type: + - resolvesToNumber + optional: true + description: | + The numeric time value, specified as an expression that must resolve to a positive non-zero number. Defaults to 1. + Together, binSize and unit specify the time period used in the $dateTrunc calculation. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + - + name: startOfWeek + type: + - string + optional: true + description: | + The start of the week. Used when + unit is week. Defaults to Sunday. diff --git a/generator/config/expression/dayOfMonth.yaml b/generator/config/expression/dayOfMonth.yaml new file mode 100644 index 000000000..b0be6eadd --- /dev/null +++ b/generator/config/expression/dayOfMonth.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $dayOfMonth +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfMonth/' +type: + - resolvesToInt +encode: object +description: | + Returns the day of the month for a date as a number between 1 and 31. +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/dayOfWeek.yaml b/generator/config/expression/dayOfWeek.yaml new file mode 100644 index 000000000..df93ef41a --- /dev/null +++ b/generator/config/expression/dayOfWeek.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $dayOfWeek +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfWeek/' +type: + - resolvesToInt +encode: object +description: | + Returns the day of the week for a date as a number between 1 (Sunday) and 7 (Saturday). +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/dayOfYear.yaml b/generator/config/expression/dayOfYear.yaml new file mode 100644 index 000000000..889ff7fee --- /dev/null +++ b/generator/config/expression/dayOfYear.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $dayOfYear +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfYear/' +type: + - resolvesToInt +encode: object +description: | + Returns the day of the year for a date as a number between 1 and 366 (leap year). +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/degreesToRadians.yaml b/generator/config/expression/degreesToRadians.yaml new file mode 100644 index 000000000..50e8a5586 --- /dev/null +++ b/generator/config/expression/degreesToRadians.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $degreesToRadians +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/degreesToRadians/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Converts a value from degrees to radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $degreesToRadians takes any valid expression that resolves to a number. + By default $degreesToRadians returns values as a double. $degreesToRadians can also return values as a 128-bit decimal as long as the resolves to a 128-bit decimal value. diff --git a/generator/config/expression/divide.yaml b/generator/config/expression/divide.yaml new file mode 100644 index 000000000..66b4eb067 --- /dev/null +++ b/generator/config/expression/divide.yaml @@ -0,0 +1,19 @@ +# $schema: ../schema.json +name: $divide +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/divide/' +type: + - resolvesToDouble +encode: array +description: | + Returns the result of dividing the first number by the second. Accepts two argument expressions. +arguments: + - + name: dividend + type: + - resolvesToNumber + description: | + The first argument is the dividend, and the second argument is the divisor; i.e. the first argument is divided by the second argument. + - + name: divisor + type: + - resolvesToNumber diff --git a/generator/config/expression/eq.yaml b/generator/config/expression/eq.yaml new file mode 100644 index 000000000..83260d4f6 --- /dev/null +++ b/generator/config/expression/eq.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $eq +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/eq/' +type: + - resolvesToBool +encode: array +description: | + Returns true if the values are equivalent. +arguments: + - + name: expression1 + type: + - expression + - + name: expression2 + type: + - expression diff --git a/generator/config/expression/exp.yaml b/generator/config/expression/exp.yaml new file mode 100644 index 000000000..9df1cc391 --- /dev/null +++ b/generator/config/expression/exp.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $exp +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/exp/' +type: + - resolvesToDouble +encode: single +description: | + Raises e to the specified exponent. +arguments: + - + name: exponent + type: + - resolvesToNumber diff --git a/generator/config/expression/filter.yaml b/generator/config/expression/filter.yaml new file mode 100644 index 000000000..cacd31fd4 --- /dev/null +++ b/generator/config/expression/filter.yaml @@ -0,0 +1,34 @@ +# $schema: ../schema.json +name: $filter +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/' +type: + - resolvesToArray +encode: object +description: | + Selects a subset of the array to return an array with only the elements that match the filter condition. +arguments: + - + name: input + type: + - resolvesToArray + - + name: cond + type: + - resolvesToBool + description: | + An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. + - + name: as + type: + - string + optional: true + description: | + A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + - + name: limit + type: + - resolvesToInt + optional: true + description: | + A number expression that restricts the number of matching array elements that $filter returns. You cannot specify a limit less than 1. The matching array elements are returned in the order they appear in the input array. + If the specified limit is greater than the number of matching array elements, $filter returns all matching array elements. If the limit is null, $filter returns all matching array elements. diff --git a/generator/config/expression/floor.yaml b/generator/config/expression/floor.yaml new file mode 100644 index 000000000..f497e2694 --- /dev/null +++ b/generator/config/expression/floor.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $floor +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/floor/' +type: + - resolvesToInt +encode: single +description: | + Returns the largest integer less than or equal to the specified number. +arguments: + - + name: expression + type: + - resolvesToNumber diff --git a/generator/config/expression/function.yaml b/generator/config/expression/function.yaml new file mode 100644 index 000000000..2e8fd411b --- /dev/null +++ b/generator/config/expression/function.yaml @@ -0,0 +1,26 @@ +# $schema: ../schema.json +name: $function +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/' +type: + - resolvesToAny +encode: object +description: | + Defines a custom function. + New in MongoDB 4.4. +arguments: + - + name: body + type: + - string + description: | + The function definition. You can specify the function definition as either BSON type Code or String. + - + name: args + type: + - array + description: | + Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ]. + - + name: lang + type: + - string diff --git a/generator/config/expression/getField.yaml b/generator/config/expression/getField.yaml new file mode 100644 index 000000000..ae81b1b57 --- /dev/null +++ b/generator/config/expression/getField.yaml @@ -0,0 +1,25 @@ +# $schema: ../schema.json +name: $getField +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/' +type: + - resolvesToAny +encode: object +description: | + Returns the value of a specified field from a document. You can use $getField to retrieve the value of fields with names that contain periods (.) or start with dollar signs ($). + New in MongoDB 5.0. +arguments: + - + name: field + type: + - string + description: | + Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. + If field begins with a dollar sign ($), place the field name inside of a $literal expression to return its value. + - + name: input + type: + - expression + optional: true + description: | + Default: $$CURRENT + A valid expression that contains the field for which you want to return a value. input must resolve to an object, missing, null, or undefined. If omitted, defaults to the document currently being processed in the pipeline ($$CURRENT). diff --git a/generator/config/expression/gt.yaml b/generator/config/expression/gt.yaml new file mode 100644 index 000000000..070deba36 --- /dev/null +++ b/generator/config/expression/gt.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $gt +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/gt/' +type: + - resolvesToBool +encode: array +description: | + Returns true if the first value is greater than the second. +arguments: + - + name: expression1 + type: + - expression + - + name: expression2 + type: + - expression diff --git a/generator/config/expression/gte.yaml b/generator/config/expression/gte.yaml new file mode 100644 index 000000000..a62838c82 --- /dev/null +++ b/generator/config/expression/gte.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $gte +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/gte/' +type: + - resolvesToBool +encode: array +description: | + Returns true if the first value is greater than or equal to the second. +arguments: + - + name: expression1 + type: + - expression + - + name: expression2 + type: + - expression diff --git a/generator/config/expression/hour.yaml b/generator/config/expression/hour.yaml new file mode 100644 index 000000000..f620f8917 --- /dev/null +++ b/generator/config/expression/hour.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $hour +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/hour/' +type: + - resolvesToInt +encode: object +description: | + Returns the hour for a date as a number between 0 and 23. +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/ifNull.yaml b/generator/config/expression/ifNull.yaml new file mode 100644 index 000000000..3cbbb72ae --- /dev/null +++ b/generator/config/expression/ifNull.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $ifNull +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/ifNull/' +type: + - resolvesToAny +encode: single +description: | + Returns either the non-null result of the first expression or the result of the second expression if the first expression results in a null result. Null result encompasses instances of undefined values or missing fields. Accepts two expressions as arguments. The result of the second expression can be null. +arguments: + - + name: expression + type: + - expression + variadic: array diff --git a/generator/config/expression/in.yaml b/generator/config/expression/in.yaml new file mode 100644 index 000000000..2ec0c1f8f --- /dev/null +++ b/generator/config/expression/in.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $in +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/in/' +type: + - resolvesToBool +encode: array +description: | + Returns a boolean indicating whether a specified value is in an array. +arguments: + - + name: expression + type: + - expression + description: | + Any valid expression expression. + - + name: array + type: + - resolvesToArray + description: | + Any valid expression that resolves to an array. diff --git a/generator/config/expression/indexOfArray.yaml b/generator/config/expression/indexOfArray.yaml new file mode 100644 index 000000000..061ca1ee7 --- /dev/null +++ b/generator/config/expression/indexOfArray.yaml @@ -0,0 +1,37 @@ +# $schema: ../schema.json +name: $indexOfArray +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfArray/' +type: + - resolvesToInt +encode: array +description: | + Searches an array for an occurrence of a specified value and returns the array index of the first occurrence. Array indexes start at zero. +arguments: + - + name: array + type: + - resolvesToArray + description: | + Can be any valid expression as long as it resolves to an array. + If the array expression resolves to a value of null or refers to a field that is missing, $indexOfArray returns null. + If the array expression does not resolve to an array or null nor refers to a missing field, $indexOfArray returns an error. + - + name: search + type: + - expression + - + name: start + type: + - resolvesToInt + optional: true + description: | + An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. + If unspecified, the starting index position for the search is the beginning of the string. + - + name: end + type: + - resolvesToInt + optional: true + description: | + An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + If unspecified, the ending index position for the search is the end of the string. diff --git a/generator/config/expression/indexOfBytes.yaml b/generator/config/expression/indexOfBytes.yaml new file mode 100644 index 000000000..ab235d24f --- /dev/null +++ b/generator/config/expression/indexOfBytes.yaml @@ -0,0 +1,39 @@ +# $schema: ../schema.json +name: $indexOfBytes +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfBytes/' +type: + - resolvesToInt +encode: array +description: | + Searches a string for an occurrence of a substring and returns the UTF-8 byte index of the first occurrence. If the substring is not found, returns -1. +arguments: + - + name: string + type: + - resolvesToString + description: | + Can be any valid expression as long as it resolves to a string. + If the string expression resolves to a value of null or refers to a field that is missing, $indexOfBytes returns null. + If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfBytes returns an error. + - + name: substring + type: + - resolvesToString + description: | + Can be any valid expression as long as it resolves to a string. + - + name: start + type: + - resolvesToInt + optional: true + description: | + An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. + If unspecified, the starting index position for the search is the beginning of the string. + - + name: end + type: + - resolvesToInt + optional: true + description: | + An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + If unspecified, the ending index position for the search is the end of the string. diff --git a/generator/config/expression/indexOfCP.yaml b/generator/config/expression/indexOfCP.yaml new file mode 100644 index 000000000..8eb4b3bd9 --- /dev/null +++ b/generator/config/expression/indexOfCP.yaml @@ -0,0 +1,39 @@ +# $schema: ../schema.json +name: $indexOfCP +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfCP/' +type: + - resolvesToInt +encode: array +description: | + Searches a string for an occurrence of a substring and returns the UTF-8 code point index of the first occurrence. If the substring is not found, returns -1 +arguments: + - + name: string + type: + - resolvesToString + description: | + Can be any valid expression as long as it resolves to a string. + If the string expression resolves to a value of null or refers to a field that is missing, $indexOfCP returns null. + If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfCP returns an error. + - + name: substring + type: + - resolvesToString + description: | + Can be any valid expression as long as it resolves to a string. + - + name: start + type: + - resolvesToInt + optional: true + description: | + An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. + If unspecified, the starting index position for the search is the beginning of the string. + - + name: end + type: + - resolvesToInt + optional: true + description: | + An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + If unspecified, the ending index position for the search is the end of the string. diff --git a/generator/config/expression/integral.yaml b/generator/config/expression/integral.yaml new file mode 100644 index 000000000..55228ea72 --- /dev/null +++ b/generator/config/expression/integral.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $integral +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: object +description: | + Returns the approximation of the area under a curve. + New in MongoDB 5.0. +arguments: + - + name: input + type: + - resolvesToNumber + - resolvesToDate + - + name: unit + type: + - resolvesToString + optional: true + description: | + A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. diff --git a/generator/config/expression/isArray.yaml b/generator/config/expression/isArray.yaml new file mode 100644 index 000000000..1bdbb675f --- /dev/null +++ b/generator/config/expression/isArray.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $isArray +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isArray/' +type: + - resolvesToBool +encode: array +description: | + Determines if the operand is an array. Returns a boolean. +arguments: + - + name: expression + type: + - expression + variadic: array diff --git a/generator/config/expression/isNumber.yaml b/generator/config/expression/isNumber.yaml new file mode 100644 index 000000000..4bce308a9 --- /dev/null +++ b/generator/config/expression/isNumber.yaml @@ -0,0 +1,16 @@ +# $schema: ../schema.json +name: $isNumber +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/' +type: + - resolvesToBool +encode: single +description: | + Returns boolean true if the specified expression resolves to an integer, decimal, double, or long. + Returns boolean false if the expression resolves to any other BSON type, null, or a missing field. + New in MongoDB 4.4. +arguments: + - + name: expression + type: + - expression + variadic: array diff --git a/generator/config/expression/isoDayOfWeek.yaml b/generator/config/expression/isoDayOfWeek.yaml new file mode 100644 index 000000000..7f6245c20 --- /dev/null +++ b/generator/config/expression/isoDayOfWeek.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $isoDayOfWeek +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoDayOfWeek/' +type: + - resolvesToInt +encode: object +description: | + Returns the weekday number in ISO 8601 format, ranging from 1 (for Monday) to 7 (for Sunday). +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/isoWeek.yaml b/generator/config/expression/isoWeek.yaml new file mode 100644 index 000000000..b395fe99f --- /dev/null +++ b/generator/config/expression/isoWeek.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $isoWeek +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoWeek/' +type: + - resolvesToInt +encode: object +description: | + Returns the week number in ISO 8601 format, ranging from 1 to 53. Week numbers start at 1 with the week (Monday through Sunday) that contains the year's first Thursday. +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/isoWeekYear.yaml b/generator/config/expression/isoWeekYear.yaml new file mode 100644 index 000000000..fb5ee284d --- /dev/null +++ b/generator/config/expression/isoWeekYear.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $isoWeekYear +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoWeekYear/' +type: + - resolvesToInt +encode: object +description: | + Returns the year number in ISO 8601 format. The year starts with the Monday of week 1 (ISO 8601) and ends with the Sunday of the last week (ISO 8601). +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/let.yaml b/generator/config/expression/let.yaml new file mode 100644 index 000000000..7b2228a62 --- /dev/null +++ b/generator/config/expression/let.yaml @@ -0,0 +1,23 @@ +# $schema: ../schema.json +name: $let +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/let/' +type: + - resolvesToAny +encode: object +description: | + Defines variables for use within the scope of a subexpression and returns the result of the subexpression. Accepts named parameters. + Accepts any number of argument expressions. +arguments: + - + name: vars + type: + - object # of expression + description: | + Assignment block for the variables accessible in the in expression. To assign a variable, specify a string for the variable name and assign a valid expression for the value. + The variable assignments have no meaning outside the in expression, not even within the vars block itself. + - + name: in + type: + - expression + description: | + The expression to evaluate. diff --git a/generator/config/expression/linearFill.yaml b/generator/config/expression/linearFill.yaml new file mode 100644 index 000000000..a53ebe572 --- /dev/null +++ b/generator/config/expression/linearFill.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $linearFill +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/' +type: + - resolvesToNumber +encode: single +description: | + Fills null and missing fields in a window using linear interpolation based on surrounding field values. + Available in the $setWindowFields stage. + New in MongoDB 5.3. +arguments: + - + name: expression + type: + - resolvesToNumber diff --git a/generator/config/expression/literal.yaml b/generator/config/expression/literal.yaml new file mode 100644 index 000000000..dfae7bbb3 --- /dev/null +++ b/generator/config/expression/literal.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $literal +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/literal/' +type: + - resolvesToAny +encode: single +description: | + Return a value without parsing. Use for values that the aggregation pipeline may interpret as an expression. For example, use a $literal expression to a string that starts with a dollar sign ($) to avoid parsing as a field path. +arguments: + - + name: value + type: + - any + description: | + If the value is an expression, $literal does not evaluate the expression but instead returns the unparsed expression. diff --git a/generator/config/expression/ln.yaml b/generator/config/expression/ln.yaml new file mode 100644 index 000000000..f4660df8a --- /dev/null +++ b/generator/config/expression/ln.yaml @@ -0,0 +1,16 @@ +# $schema: ../schema.json +name: $ln +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/ln/' +type: + - resolvesToDouble +encode: single +description: | + Calculates the natural log of a number. + $ln is equivalent to $log: [ , Math.E ] expression, where Math.E is a JavaScript representation for Euler's number e. +arguments: + - + name: number + type: + - resolvesToNumber + description: | + Any valid expression as long as it resolves to a non-negative number. For more information on expressions, see Expressions. diff --git a/generator/config/expression/locf.yaml b/generator/config/expression/locf.yaml new file mode 100644 index 000000000..5e079620c --- /dev/null +++ b/generator/config/expression/locf.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $locf +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf/' +type: + - resolvesToAny +encode: single +description: | + Last observation carried forward. Sets values for null and missing fields in a window to the last non-null value for the field. + Available in the $setWindowFields stage. + New in MongoDB 5.2. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/expression/log.yaml b/generator/config/expression/log.yaml new file mode 100644 index 000000000..becc32437 --- /dev/null +++ b/generator/config/expression/log.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $log +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/log/' +type: + - resolvesToDouble +encode: array +description: | + Calculates the log of a number in the specified base. +arguments: + - + name: number + type: + - resolvesToNumber + description: | + Any valid expression as long as it resolves to a non-negative number. + - + name: base + type: + - resolvesToNumber + description: | + Any valid expression as long as it resolves to a positive number greater than 1. diff --git a/generator/config/expression/log10.yaml b/generator/config/expression/log10.yaml new file mode 100644 index 000000000..2ca26e535 --- /dev/null +++ b/generator/config/expression/log10.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $log10 +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/log10/' +type: + - resolvesToDouble +encode: single +description: | + Calculates the log base 10 of a number. +arguments: + - + name: number + type: + - resolvesToNumber + description: | + Any valid expression as long as it resolves to a non-negative number. diff --git a/generator/config/expression/lt.yaml b/generator/config/expression/lt.yaml new file mode 100644 index 000000000..90e12d2ac --- /dev/null +++ b/generator/config/expression/lt.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $lt +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lt/' +type: + - resolvesToBool +encode: array +description: | + Returns true if the first value is less than the second. +arguments: + - + name: expression1 + type: + - expression + - + name: expression2 + type: + - expression diff --git a/generator/config/expression/lte.yaml b/generator/config/expression/lte.yaml new file mode 100644 index 000000000..b71d04617 --- /dev/null +++ b/generator/config/expression/lte.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $lte +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lte/' +type: + - resolvesToBool +encode: array +description: | + Returns true if the first value is less than or equal to the second. +arguments: + - + name: expression1 + type: + - expression + - + name: expression2 + type: + - expression diff --git a/generator/config/expression/ltrim.yaml b/generator/config/expression/ltrim.yaml new file mode 100644 index 000000000..1491ed768 --- /dev/null +++ b/generator/config/expression/ltrim.yaml @@ -0,0 +1,25 @@ +# $schema: ../schema.json +name: $ltrim +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/ltrim/' +type: + - resolvesToString +encode: object +description: | + Removes whitespace or the specified characters from the beginning of a string. + New in MongoDB 4.0. +arguments: + - + name: input + type: + - resolvesToString + description: | + The string to trim. The argument can be any valid expression that resolves to a string. + - + name: chars + type: + - resolvesToString + optional: true + description: | + The character(s) to trim from the beginning of the input. + The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. + If unspecified, $ltrim removes whitespace characters, including the null character. diff --git a/generator/config/expression/map.yaml b/generator/config/expression/map.yaml new file mode 100644 index 000000000..335716783 --- /dev/null +++ b/generator/config/expression/map.yaml @@ -0,0 +1,28 @@ +# $schema: ../schema.json +name: $map +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/' +type: + - resolvesToArray +encode: object +description: | + Applies a subexpression to each element of an array and returns the array of resulting values in order. Accepts named parameters. +arguments: + - + name: input + type: + - resolvesToArray + description: | + An expression that resolves to an array. + - + name: as + type: + - resolvesToString + optional: true + description: | + A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + - + name: in + type: + - expression + description: | + An expression that is applied to each element of the input array. The expression references each element individually with the variable name specified in as. diff --git a/generator/config/expression/max.yaml b/generator/config/expression/max.yaml new file mode 100644 index 000000000..dfb7a20fd --- /dev/null +++ b/generator/config/expression/max.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $max +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/' +type: + - resolvesToAny +encode: single +description: | + Returns the maximum value that results from applying an expression to each document. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - expression + variadic: array diff --git a/generator/config/expression/maxN.yaml b/generator/config/expression/maxN.yaml new file mode 100644 index 000000000..af3a9c5c5 --- /dev/null +++ b/generator/config/expression/maxN.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $maxN +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN-array-element/' +type: + - resolvesToArray +encode: object +description: | + Returns the n largest values in an array. Distinct from the $maxN accumulator. +arguments: + - + name: input + type: + - resolvesToArray + description: | + An expression that resolves to the array from which to return the maximal n elements. + - + name: 'n' + type: + - resolvesToInt + description: | + An expression that resolves to a positive integer. The integer specifies the number of array elements that $maxN returns. diff --git a/generator/config/expression/median.yaml b/generator/config/expression/median.yaml new file mode 100644 index 000000000..43512db54 --- /dev/null +++ b/generator/config/expression/median.yaml @@ -0,0 +1,26 @@ +# $schema: ../schema.json +name: $median +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/' +type: + - resolvesToDouble +encode: object +description: | + Returns an approximation of the median, the 50th percentile, as a scalar value. + New in MongoDB 7.0. + This operator is available as an accumulator in these stages: + $group + $setWindowFields + It is also available as an aggregation expression. +arguments: + - + name: input + type: + - resolvesToNumber + description: | + $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. + - + name: method + type: + - string # AccumulatorPercentile + description: | + The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. diff --git a/generator/config/expression/meta.yaml b/generator/config/expression/meta.yaml new file mode 100644 index 000000000..a85fc8a62 --- /dev/null +++ b/generator/config/expression/meta.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $meta +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/' +type: + - resolvesToAny +encode: single +description: | + Access available per-document metadata related to the aggregation operation. +arguments: + - + name: keyword + type: + - string diff --git a/generator/config/expression/millisecond.yaml b/generator/config/expression/millisecond.yaml new file mode 100644 index 000000000..3027cfc82 --- /dev/null +++ b/generator/config/expression/millisecond.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $millisecond +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/millisecond/' +type: + - resolvesToInt +encode: object +description: | + Returns the milliseconds of a date as a number between 0 and 999. +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/min.yaml b/generator/config/expression/min.yaml new file mode 100644 index 000000000..a0265aa50 --- /dev/null +++ b/generator/config/expression/min.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $min +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/' +type: + - resolvesToAny +encode: single +description: | + Returns the minimum value that results from applying an expression to each document. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - expression + variadic: array diff --git a/generator/config/expression/minN.yaml b/generator/config/expression/minN.yaml new file mode 100644 index 000000000..07a3d957c --- /dev/null +++ b/generator/config/expression/minN.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $minN +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN-array-element/' +type: + - resolvesToArray +encode: object +description: | + Returns the n smallest values in an array. Distinct from the $minN accumulator. +arguments: + - + name: input + type: + - resolvesToArray + description: | + An expression that resolves to the array from which to return the maximal n elements. + - + name: 'n' + type: + - resolvesToInt + description: | + An expression that resolves to a positive integer. The integer specifies the number of array elements that $maxN returns. diff --git a/generator/config/expression/minute.yaml b/generator/config/expression/minute.yaml new file mode 100644 index 000000000..e6a39feec --- /dev/null +++ b/generator/config/expression/minute.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $minute +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/minute/' +type: + - resolvesToInt +encode: object +description: | + Returns the minute for a date as a number between 0 and 59. +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/mod.yaml b/generator/config/expression/mod.yaml new file mode 100644 index 000000000..24bfa56db --- /dev/null +++ b/generator/config/expression/mod.yaml @@ -0,0 +1,19 @@ +# $schema: ../schema.json +name: $mod +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/mod/' +type: + - resolvesToInt +encode: array +description: | + Returns the remainder of the first number divided by the second. Accepts two argument expressions. +arguments: + - + name: dividend + type: + - resolvesToNumber + description: | + The first argument is the dividend, and the second argument is the divisor; i.e. first argument is divided by the second argument. + - + name: divisor + type: + - resolvesToNumber diff --git a/generator/config/expression/month.yaml b/generator/config/expression/month.yaml new file mode 100644 index 000000000..e77d08520 --- /dev/null +++ b/generator/config/expression/month.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $month +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/month/' +type: + - resolvesToInt +encode: object +description: | + Returns the month for a date as a number between 1 (January) and 12 (December). +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/multiply.yaml b/generator/config/expression/multiply.yaml new file mode 100644 index 000000000..855616e52 --- /dev/null +++ b/generator/config/expression/multiply.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $multiply +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/multiply/' +type: + - resolvesToDecimal +encode: single +description: | + Multiplies numbers to return the product. Accepts any number of argument expressions. +arguments: + - + name: expression + type: + - resolvesToNumber + variadic: array + description: | + The arguments can be any valid expression as long as they resolve to numbers. + Starting in MongoDB 6.1 you can optimize the $multiply operation. To improve performance, group references at the end of the argument list. diff --git a/generator/config/expression/ne.yaml b/generator/config/expression/ne.yaml new file mode 100644 index 000000000..f4e7ab02c --- /dev/null +++ b/generator/config/expression/ne.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $ne +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/ne/' +type: + - resolvesToBool +encode: array +description: | + Returns true if the values are not equivalent. +arguments: + - + name: expression1 + type: + - expression + - + name: expression2 + type: + - expression diff --git a/generator/config/expression/not.yaml b/generator/config/expression/not.yaml new file mode 100644 index 000000000..9c20a5fbc --- /dev/null +++ b/generator/config/expression/not.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $not +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/not/' +type: + - resolvesToBool +encode: single +description: | + Returns the boolean value that is the opposite of its argument expression. Accepts a single argument expression. +arguments: + - + name: expression + type: + - expression + - resolvesToBool diff --git a/generator/config/expression/objectToArray.yaml b/generator/config/expression/objectToArray.yaml new file mode 100644 index 000000000..40c1cba77 --- /dev/null +++ b/generator/config/expression/objectToArray.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $objectToArray +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/' +type: + - resolvesToArray +encode: single +description: | + Converts a document to an array of documents representing key-value pairs. +arguments: + - + name: object + type: + - resolvesToObject + description: | + Any valid expression as long as it resolves to a document object. $objectToArray applies to the top-level fields of its argument. If the argument is a document that itself contains embedded document fields, the $objectToArray does not recursively apply to the embedded document fields. diff --git a/generator/config/expression/or.yaml b/generator/config/expression/or.yaml new file mode 100644 index 000000000..6525041f3 --- /dev/null +++ b/generator/config/expression/or.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $or +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/or/' +type: + - resolvesToBool +encode: single +description: | + Returns true when any of its expressions evaluates to true. Accepts any number of argument expressions. +arguments: + - + name: expression + type: + - expression + - resolvesToBool + variadic: array diff --git a/generator/config/expression/percentile.yaml b/generator/config/expression/percentile.yaml new file mode 100644 index 000000000..9847ffd59 --- /dev/null +++ b/generator/config/expression/percentile.yaml @@ -0,0 +1,36 @@ +# $schema: ../schema.json +name: $percentile +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/' +type: + - resolvesToArray # of scalar +encode: object +description: | + Returns an array of scalar values that correspond to specified percentile values. + New in MongoDB 7.0. + + This operator is available as an accumulator in these stages: + $group + + $setWindowFields + + It is also available as an aggregation expression. +arguments: + - + name: input + type: + - resolvesToNumber + description: | + $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. + - + name: p + type: + - resolvesToArray # of resolvesToNumber + description: | + $percentile calculates a percentile value for each element in p. The elements represent percentages and must evaluate to numeric values in the range 0.0 to 1.0, inclusive. + $percentile returns results in the same order as the elements in p. + - + name: method + type: + - string # AccumulatorPercentile + description: | + The method that mongod uses to calculate the percentile value. The method must be 'approximate'. diff --git a/generator/config/expression/pow.yaml b/generator/config/expression/pow.yaml new file mode 100644 index 000000000..4d9d2b489 --- /dev/null +++ b/generator/config/expression/pow.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $pow +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/pow/' +type: + - resolvesToNumber +encode: array +description: | + Raises a number to the specified exponent. +arguments: + - + name: number + type: + - resolvesToNumber + - + name: exponent + type: + - resolvesToNumber diff --git a/generator/config/expression/radiansToDegrees.yaml b/generator/config/expression/radiansToDegrees.yaml new file mode 100644 index 000000000..e8f8d3794 --- /dev/null +++ b/generator/config/expression/radiansToDegrees.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $radiansToDegrees +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/radiansToDegrees/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Converts a value from radians to degrees. +arguments: + - + name: expression + type: + - resolvesToNumber diff --git a/generator/config/expression/rand.yaml b/generator/config/expression/rand.yaml new file mode 100644 index 000000000..f0e731dc7 --- /dev/null +++ b/generator/config/expression/rand.yaml @@ -0,0 +1,8 @@ +# $schema: ../schema.json +name: $rand +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/rand/' +type: + - resolvesToDouble +encode: object +description: | + Returns a random float between 0 and 1 diff --git a/generator/config/expression/range.yaml b/generator/config/expression/range.yaml new file mode 100644 index 000000000..ceea6be9f --- /dev/null +++ b/generator/config/expression/range.yaml @@ -0,0 +1,28 @@ +# $schema: ../schema.json +name: $range +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/range/' +type: + - resolvesToArray # of int +encode: array +description: | + Outputs an array containing a sequence of integers according to user-defined inputs. +arguments: + - + name: start + type: + - resolvesToInt + description: | + An integer that specifies the start of the sequence. Can be any valid expression that resolves to an integer. + - + name: end + type: + - resolvesToInt + description: | + An integer that specifies the exclusive upper limit of the sequence. Can be any valid expression that resolves to an integer. + - + name: step + type: + - resolvesToInt + optional: true + description: | + An integer that specifies the increment value. Can be any valid expression that resolves to a non-zero integer. Defaults to 1. diff --git a/generator/config/expression/rank.yaml b/generator/config/expression/rank.yaml new file mode 100644 index 000000000..dde42047c --- /dev/null +++ b/generator/config/expression/rank.yaml @@ -0,0 +1,9 @@ +# $schema: ../schema.json +name: $rank +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/' +type: + - resolvesToInt +encode: object +description: | + Returns the document position (known as the rank) relative to other documents in the $setWindowFields stage partition. + New in MongoDB 5.0. diff --git a/generator/config/expression/reduce.yaml b/generator/config/expression/reduce.yaml new file mode 100644 index 000000000..a92eef3bd --- /dev/null +++ b/generator/config/expression/reduce.yaml @@ -0,0 +1,32 @@ +# $schema: ../schema.json +name: $reduce +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/' +type: + - resolvesToAny +encode: object +description: | + Applies an expression to each element in an array and combines them into a single value. +arguments: + - + name: input + type: + - resolvesToArray + description: | + Can be any valid expression that resolves to an array. + If the argument resolves to a value of null or refers to a missing field, $reduce returns null. + If the argument does not resolve to an array or null nor refers to a missing field, $reduce returns an error. + - + name: initialValue + type: + - expression + description: | + The initial cumulative value set before in is applied to the first element of the input array. + - + name: in + type: + - expression + description: | + A valid expression that $reduce applies to each element in the input array in left-to-right order. Wrap the input value with $reverseArray to yield the equivalent of applying the combining expression from right-to-left. + During evaluation of the in expression, two variables will be available: + - value is the variable that represents the cumulative value of the expression. + - this is the variable that refers to the element being processed. diff --git a/generator/config/expression/regexFind.yaml b/generator/config/expression/regexFind.yaml new file mode 100644 index 000000000..f4a5c9979 --- /dev/null +++ b/generator/config/expression/regexFind.yaml @@ -0,0 +1,28 @@ +# $schema: ../schema.json +name: $regexFind +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFind/' +type: + - resolvesToObject +encode: object +description: | + Applies a regular expression (regex) to a string and returns information on the first matched substring. + New in MongoDB 4.2. +arguments: + - + name: input + type: + - resolvesToString + description: | + The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + - + name: regex + type: + - resolvesToString + - regex + description: | + The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + - + name: options + type: + - string + optional: true diff --git a/generator/config/expression/regexFindAll.yaml b/generator/config/expression/regexFindAll.yaml new file mode 100644 index 000000000..1347f061d --- /dev/null +++ b/generator/config/expression/regexFindAll.yaml @@ -0,0 +1,28 @@ +# $schema: ../schema.json +name: $regexFindAll +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFindAll/' +type: + - resolvesToArray # of object +encode: object +description: | + Applies a regular expression (regex) to a string and returns information on the all matched substrings. + New in MongoDB 4.2. +arguments: + - + name: input + type: + - resolvesToString + description: | + The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + - + name: regex + type: + - resolvesToString + - regex + description: | + The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + - + name: options + type: + - string + optional: true diff --git a/generator/config/expression/regexMatch.yaml b/generator/config/expression/regexMatch.yaml new file mode 100644 index 000000000..5656ad81e --- /dev/null +++ b/generator/config/expression/regexMatch.yaml @@ -0,0 +1,28 @@ +# $schema: ../schema.json +name: $regexMatch +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexMatch/' +type: + - resolvesToBool +encode: object +description: | + Applies a regular expression (regex) to a string and returns a boolean that indicates if a match is found or not. + New in MongoDB 4.2. +arguments: + - + name: input + type: + - resolvesToString + description: | + The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + - + name: regex + type: + - resolvesToString + - regex + description: | + The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + - + name: options + type: + - string + optional: true diff --git a/generator/config/expression/replaceAll.yaml b/generator/config/expression/replaceAll.yaml new file mode 100644 index 000000000..ab2ef3e99 --- /dev/null +++ b/generator/config/expression/replaceAll.yaml @@ -0,0 +1,32 @@ +# $schema: ../schema.json +name: $replaceAll +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceAll/' +type: + - resolvesToString +encode: object +description: | + Replaces all instances of a search string in an input string with a replacement string. + $replaceAll is both case-sensitive and diacritic-sensitive, and ignores any collation present on a collection. + New in MongoDB 4.4. +arguments: + - + name: input + type: + - resolvesToString + - resolvesToNull + description: | + The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. + - + name: find + type: + - resolvesToString + - resolvesToNull + description: | + The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. + - + name: replacement + type: + - resolvesToString + - resolvesToNull + description: | + The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. diff --git a/generator/config/expression/replaceOne.yaml b/generator/config/expression/replaceOne.yaml new file mode 100644 index 000000000..6b002da19 --- /dev/null +++ b/generator/config/expression/replaceOne.yaml @@ -0,0 +1,31 @@ +# $schema: ../schema.json +name: $replaceOne +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceOne/' +type: + - resolvesToString +encode: object +description: | + Replaces the first instance of a matched string in a given input. + New in MongoDB 4.4. +arguments: + - + name: input + type: + - resolvesToString + - resolvesToNull + description: | + The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. + - + name: find + type: + - resolvesToString + - resolvesToNull + description: | + The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. + - + name: replacement + type: + - resolvesToString + - resolvesToNull + description: | + The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. diff --git a/generator/config/expression/reverseArray.yaml b/generator/config/expression/reverseArray.yaml new file mode 100644 index 000000000..c351148fa --- /dev/null +++ b/generator/config/expression/reverseArray.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $reverseArray +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/reverseArray/' +type: + - resolvesToArray +encode: single +description: | + Returns an array with the elements in reverse order. +arguments: + - + name: expression + type: + - resolvesToArray + description: | + The argument can be any valid expression as long as it resolves to an array. diff --git a/generator/config/expression/round.yaml b/generator/config/expression/round.yaml new file mode 100644 index 000000000..8f0fae277 --- /dev/null +++ b/generator/config/expression/round.yaml @@ -0,0 +1,29 @@ +# $schema: ../schema.json +name: $round +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/' +type: + - resolvesToInt + - resolvesToDouble + - resolvesToDecimal + - resolvesToLong +encode: array +description: | + Rounds a number to to a whole integer or to a specified decimal place. +arguments: + - + name: number + type: + - resolvesToInt + - resolvesToDouble + - resolvesToDecimal + - resolvesToLong + description: | + Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. + $round returns an error if the expression resolves to a non-numeric data type. + - + name: place + type: + - resolvesToInt + optional: true + description: | + Can be any valid expression that resolves to an integer between -20 and 100, exclusive. diff --git a/generator/config/expression/rtrim.yaml b/generator/config/expression/rtrim.yaml new file mode 100644 index 000000000..ab6627527 --- /dev/null +++ b/generator/config/expression/rtrim.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $rtrim +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/rtrim/' +type: + - resolvesToString +encode: object +description: | + Removes whitespace characters, including null, or the specified characters from the end of a string. +arguments: + - + name: input + type: + - resolvesToString + description: | + The string to trim. The argument can be any valid expression that resolves to a string. + - + name: chars + type: + - resolvesToString + optional: true + description: | + The character(s) to trim from the beginning of the input. + The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. + If unspecified, $ltrim removes whitespace characters, including the null character. diff --git a/generator/config/expression/sampleRate.yaml b/generator/config/expression/sampleRate.yaml new file mode 100644 index 000000000..18a0e5453 --- /dev/null +++ b/generator/config/expression/sampleRate.yaml @@ -0,0 +1,16 @@ +# $schema: ../schema.json +name: $sampleRate +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sampleRate/' +type: + - resolvesToAny +encode: single +description: | + Randomly select documents at a given rate. Although the exact number of documents selected varies on each run, the quantity chosen approximates the sample rate expressed as a percentage of the total number of documents. +arguments: + - + name: rate + type: + - resolvesToDouble + description: | + The selection process uses a uniform random distribution. The sample rate is a floating point number between 0 and 1, inclusive, which represents the probability that a given document will be selected as it passes through the pipeline. + For example, a sample rate of 0.33 selects roughly one document in three. diff --git a/generator/config/expression/second.yaml b/generator/config/expression/second.yaml new file mode 100644 index 000000000..7e731d24f --- /dev/null +++ b/generator/config/expression/second.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $second +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/second/' +type: + - resolvesToInt +encode: object +description: | + Returns the seconds for a date as a number between 0 and 60 (leap seconds). +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/setDifference.yaml b/generator/config/expression/setDifference.yaml new file mode 100644 index 000000000..fac46be28 --- /dev/null +++ b/generator/config/expression/setDifference.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $setDifference +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setDifference/' +type: + - resolvesToArray +encode: array +description: | + Returns a set with elements that appear in the first set but not in the second set; i.e. performs a relative complement of the second set relative to the first. Accepts exactly two argument expressions. +arguments: + - + name: expression1 + type: + - resolvesToArray + description: | + The arguments can be any valid expression as long as they each resolve to an array. + - + name: expression2 + type: + - resolvesToArray + description: | + The arguments can be any valid expression as long as they each resolve to an array. diff --git a/generator/config/expression/setEquals.yaml b/generator/config/expression/setEquals.yaml new file mode 100644 index 000000000..4bde55dd0 --- /dev/null +++ b/generator/config/expression/setEquals.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $setEquals +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setEquals/' +type: + - resolvesToBool +encode: single +description: | + Returns true if the input sets have the same distinct elements. Accepts two or more argument expressions. +arguments: + - + name: expression + type: + - resolvesToArray + variadic: array diff --git a/generator/config/expression/setField.yaml b/generator/config/expression/setField.yaml new file mode 100644 index 000000000..429e1222a --- /dev/null +++ b/generator/config/expression/setField.yaml @@ -0,0 +1,29 @@ +# $schema: ../schema.json +name: $setField +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/' +type: + - resolvesToObject +encode: object +description: | + Adds, updates, or removes a specified field in a document. You can use $setField to add, update, or remove fields with names that contain periods (.) or start with dollar signs ($). + New in MongoDB 5.0. +arguments: + - + name: field + type: + - resolvesToString + description: | + Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. + - + name: input + type: + - resolvesToObject + description: | + Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. + - + name: value + type: + - expression + description: | + The value that you want to assign to field. value can be any valid expression. + Set to $$REMOVE to remove field from the input document. diff --git a/generator/config/expression/setIntersection.yaml b/generator/config/expression/setIntersection.yaml new file mode 100644 index 000000000..5c6798968 --- /dev/null +++ b/generator/config/expression/setIntersection.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $setIntersection +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/' +type: + - resolvesToArray +encode: single +description: | + Returns a set with elements that appear in all of the input sets. Accepts any number of argument expressions. +arguments: + - + name: expression + type: + - resolvesToArray + variadic: array diff --git a/generator/config/expression/setIsSubset.yaml b/generator/config/expression/setIsSubset.yaml new file mode 100644 index 000000000..ba670b941 --- /dev/null +++ b/generator/config/expression/setIsSubset.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $setIsSubset +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIsSubset/' +type: + - resolvesToBool +encode: array +description: | + Returns true if all elements of the first set appear in the second set, including when the first set equals the second set; i.e. not a strict subset. Accepts exactly two argument expressions. +arguments: + - + name: expression1 + type: + - resolvesToArray + - + name: expression2 + type: + - resolvesToArray diff --git a/generator/config/expression/setUnion.yaml b/generator/config/expression/setUnion.yaml new file mode 100644 index 000000000..f9b7c9ca9 --- /dev/null +++ b/generator/config/expression/setUnion.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $setUnion +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/' +type: + - resolvesToArray +encode: single +description: | + Returns a set with elements that appear in any of the input sets. +arguments: + - + name: expression + type: + - resolvesToArray + variadic: array diff --git a/generator/config/expression/sin.yaml b/generator/config/expression/sin.yaml new file mode 100644 index 000000000..2eb5b3c72 --- /dev/null +++ b/generator/config/expression/sin.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $sin +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sin/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the sine of a value that is measured in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $sin takes any valid expression that resolves to a number. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the result to radians. + By default $sin returns values as a double. $sin can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. diff --git a/generator/config/expression/sinh.yaml b/generator/config/expression/sinh.yaml new file mode 100644 index 000000000..a3e779d58 --- /dev/null +++ b/generator/config/expression/sinh.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $sinh +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sinh/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the hyperbolic sine of a value that is measured in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $sinh takes any valid expression that resolves to a number, measured in radians. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the value to radians. + By default $sinh returns values as a double. $sinh can also return values as a 128-bit decimal if the expression resolves to a 128-bit decimal value. diff --git a/generator/config/expression/size.yaml b/generator/config/expression/size.yaml new file mode 100644 index 000000000..b773e67f2 --- /dev/null +++ b/generator/config/expression/size.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $size +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/size/' +type: + - resolvesToInt +encode: single +description: | + Returns the number of elements in the array. Accepts a single expression as argument. +arguments: + - + name: expression + type: + - resolvesToArray + description: | + The argument for $size can be any expression as long as it resolves to an array. diff --git a/generator/config/expression/slice.yaml b/generator/config/expression/slice.yaml new file mode 100644 index 000000000..a034a00db --- /dev/null +++ b/generator/config/expression/slice.yaml @@ -0,0 +1,33 @@ +# $schema: ../schema.json +name: $slice +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/slice/' +type: + - resolvesToArray + - projection +encode: array +description: | + Returns a subset of an array. +arguments: + - + name: expression + type: + - resolvesToArray + description: | + Any valid expression as long as it resolves to an array. + - + name: position + type: + - resolvesToInt + optional: true + description: | + Any valid expression as long as it resolves to an integer. + If positive, $slice determines the starting position from the start of the array. If position is greater than the number of elements, the $slice returns an empty array. + If negative, $slice determines the starting position from the end of the array. If the absolute value of the is greater than the number of elements, the starting position is the start of the array. + - + name: "n" + type: + - resolvesToInt + description: | + Any valid expression as long as it resolves to an integer. If position is specified, n must resolve to a positive integer. + If positive, $slice returns up to the first n elements in the array. If the position is specified, $slice returns the first n elements starting from the position. + If negative, $slice returns up to the last n elements in the array. n cannot resolve to a negative number if is specified. diff --git a/generator/config/expression/sortArray.yaml b/generator/config/expression/sortArray.yaml new file mode 100644 index 000000000..5b32bae4b --- /dev/null +++ b/generator/config/expression/sortArray.yaml @@ -0,0 +1,23 @@ +# $schema: ../schema.json +name: $sortArray +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/' +type: + - resolvesToArray +encode: object +description: | + Sorts the elements of an array. +arguments: + - + name: input + type: + - resolvesToArray + description: | + The array to be sorted. + The result is null if the expression: is missing, evaluates to null, or evaluates to undefined + If the expression evaluates to any other non-array value, the document returns an error. + - + name: sortBy + type: + - object # SortSpec + description: | + The document specifies a sort ordering. diff --git a/generator/config/expression/split.yaml b/generator/config/expression/split.yaml new file mode 100644 index 000000000..9d4f56d65 --- /dev/null +++ b/generator/config/expression/split.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $split +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/split/' +type: + - resolvesToArray # of string +encode: array +description: | + Splits a string into substrings based on a delimiter. Returns an array of substrings. If the delimiter is not found within the string, returns an array containing the original string. +arguments: + - + name: string + type: + - resolvesToString + description: | + The string to be split. string expression can be any valid expression as long as it resolves to a string. + - + name: delimiter + type: + - resolvesToString + description: | + The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. diff --git a/generator/config/expression/sqrt.yaml b/generator/config/expression/sqrt.yaml new file mode 100644 index 000000000..7b7fb0680 --- /dev/null +++ b/generator/config/expression/sqrt.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $sqrt +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sqrt/' +type: + - resolvesToDouble +encode: single +description: | + Calculates the square root. +arguments: + - + name: number + type: + - resolvesToNumber + description: | + The argument can be any valid expression as long as it resolves to a non-negative number. diff --git a/generator/config/expression/stdDevPop.yaml b/generator/config/expression/stdDevPop.yaml new file mode 100644 index 000000000..e0063e502 --- /dev/null +++ b/generator/config/expression/stdDevPop.yaml @@ -0,0 +1,16 @@ +# $schema: ../schema.json +name: $stdDevPop +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/' +type: + - resolvesToDouble +encode: single +description: | + Calculates the population standard deviation of the input values. Use if the values encompass the entire population of data you want to represent and do not wish to generalize about a larger population. $stdDevPop ignores non-numeric values. + If the values represent only a sample of a population of data from which to generalize about the population, use $stdDevSamp instead. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - resolvesToNumber + variadic: array diff --git a/generator/config/expression/stdDevSamp.yaml b/generator/config/expression/stdDevSamp.yaml new file mode 100644 index 000000000..84b35f52a --- /dev/null +++ b/generator/config/expression/stdDevSamp.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $stdDevSamp +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevSamp/' +type: + - resolvesToDouble +encode: single +description: | + Calculates the sample standard deviation of the input values. Use if the values encompass a sample of a population of data from which to generalize about the population. $stdDevSamp ignores non-numeric values. + If the values represent the entire population of data or you do not wish to generalize about a larger population, use $stdDevPop instead. +arguments: + - + name: expression + type: + - resolvesToNumber + variadic: array diff --git a/generator/config/expression/strLenBytes.yaml b/generator/config/expression/strLenBytes.yaml new file mode 100644 index 000000000..0f806c41f --- /dev/null +++ b/generator/config/expression/strLenBytes.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $strLenBytes +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenBytes/' +type: + - resolvesToInt +encode: single +description: | + Returns the number of UTF-8 encoded bytes in a string. +arguments: + - + name: expression + type: + - resolvesToString diff --git a/generator/config/expression/strLenCP.yaml b/generator/config/expression/strLenCP.yaml new file mode 100644 index 000000000..77e2ff1a8 --- /dev/null +++ b/generator/config/expression/strLenCP.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $strLenCP +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenCP/' +type: + - resolvesToInt +encode: single +description: | + Returns the number of UTF-8 code points in a string. +arguments: + - + name: expression + type: + - resolvesToString diff --git a/generator/config/expression/strcasecmp.yaml b/generator/config/expression/strcasecmp.yaml new file mode 100644 index 000000000..fb9500e39 --- /dev/null +++ b/generator/config/expression/strcasecmp.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $strcasecmp +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/strcasecmp/' +type: + - resolvesToInt +encode: array +description: | + Performs case-insensitive string comparison and returns: 0 if two strings are equivalent, 1 if the first string is greater than the second, and -1 if the first string is less than the second. +arguments: + - + name: expression1 + type: + - resolvesToString + - + name: expression2 + type: + - resolvesToString diff --git a/generator/config/expression/substr.yaml b/generator/config/expression/substr.yaml new file mode 100644 index 000000000..791659841 --- /dev/null +++ b/generator/config/expression/substr.yaml @@ -0,0 +1,25 @@ +# $schema: ../schema.json +name: $substr +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/substr/' +type: + - resolvesToString +encode: array +description: | + Deprecated. Use $substrBytes or $substrCP. +arguments: + - + name: string + type: + - resolvesToString + - + name: start + type: + - resolvesToInt + description: | + If start is a negative number, $substr returns an empty string "". + - + name: length + type: + - resolvesToInt + description: | + If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. diff --git a/generator/config/expression/substrBytes.yaml b/generator/config/expression/substrBytes.yaml new file mode 100644 index 000000000..3bc3fb1af --- /dev/null +++ b/generator/config/expression/substrBytes.yaml @@ -0,0 +1,25 @@ +# $schema: ../schema.json +name: $substrBytes +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrBytes/' +type: + - resolvesToString +encode: array +description: | + Returns the substring of a string. Starts with the character at the specified UTF-8 byte index (zero-based) in the string and continues for the specified number of bytes. +arguments: + - + name: string + type: + - resolvesToString + - + name: start + type: + - resolvesToInt + description: | + If start is a negative number, $substr returns an empty string "". + - + name: length + type: + - resolvesToInt + description: | + If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. diff --git a/generator/config/expression/substrCP.yaml b/generator/config/expression/substrCP.yaml new file mode 100644 index 000000000..979b55cdf --- /dev/null +++ b/generator/config/expression/substrCP.yaml @@ -0,0 +1,25 @@ +# $schema: ../schema.json +name: $substrCP +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrCP/' +type: + - resolvesToString +encode: array +description: | + Returns the substring of a string. Starts with the character at the specified UTF-8 code point (CP) index (zero-based) in the string and continues for the number of code points specified. +arguments: + - + name: string + type: + - resolvesToString + - + name: start + type: + - resolvesToInt + description: | + If start is a negative number, $substr returns an empty string "". + - + name: length + type: + - resolvesToInt + description: | + If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. diff --git a/generator/config/expression/subtract.yaml b/generator/config/expression/subtract.yaml new file mode 100644 index 000000000..66b00e501 --- /dev/null +++ b/generator/config/expression/subtract.yaml @@ -0,0 +1,20 @@ +# $schema: ../schema.json +name: $subtract +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/' +type: + - resolvesToNumber + - resolvesToDate +encode: array +description: | + Returns the result of subtracting the second value from the first. If the two values are numbers, return the difference. If the two values are dates, return the difference in milliseconds. If the two values are a date and a number in milliseconds, return the resulting date. Accepts two argument expressions. If the two values are a date and a number, specify the date argument first as it is not meaningful to subtract a date from a number. +arguments: + - + name: expression1 + type: + - resolvesToNumber + - resolvesToDate + - + name: expression2 + type: + - resolvesToNumber + - resolvesToDate diff --git a/generator/config/expression/sum.yaml b/generator/config/expression/sum.yaml new file mode 100644 index 000000000..f1887f6ba --- /dev/null +++ b/generator/config/expression/sum.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $sum +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/' +type: + - resolvesToNumber +encode: single +description: | + Returns a sum of numerical values. Ignores non-numeric values. + Changed in MongoDB 5.0: Available in the $setWindowFields stage. +arguments: + - + name: expression + type: + - resolvesToNumber + variadic: array diff --git a/generator/config/expression/switch.yaml b/generator/config/expression/switch.yaml new file mode 100644 index 000000000..2ade8d502 --- /dev/null +++ b/generator/config/expression/switch.yaml @@ -0,0 +1,26 @@ +# $schema: ../schema.json +name: $switch +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/switch/' +type: + - resolvesToAny +encode: object +description: | + Evaluates a series of case expressions. When it finds an expression which evaluates to true, $switch executes a specified expression and breaks out of the control flow. +arguments: + - + name: branches + type: + - array # of object{case:resolvesToBool, then:expression} + description: | + An array of control branch documents. Each branch is a document with the following fields: + - case Can be any valid expression that resolves to a boolean. If the result is not a boolean, it is coerced to a boolean value. More information about how MongoDB evaluates expressions as either true or false can be found here. + - then Can be any valid expression. + The branches array must contain at least one branch document. + - + name: default + type: + - expression + optional: true + description: | + The path to take if no branch case expression evaluates to true. + Although optional, if default is unspecified and no branch case evaluates to true, $switch returns an error. diff --git a/generator/config/expression/tan.yaml b/generator/config/expression/tan.yaml new file mode 100644 index 000000000..f82484155 --- /dev/null +++ b/generator/config/expression/tan.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $tan +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/tan/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the tangent of a value that is measured in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $tan takes any valid expression that resolves to a number. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the result to radians. + By default $tan returns values as a double. $tan can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. diff --git a/generator/config/expression/tanh.yaml b/generator/config/expression/tanh.yaml new file mode 100644 index 000000000..1ce471cf4 --- /dev/null +++ b/generator/config/expression/tanh.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $tanh +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/tanh/' +type: + - resolvesToDouble + - resolvesToDecimal +encode: single +description: | + Returns the hyperbolic tangent of a value that is measured in radians. +arguments: + - + name: expression + type: + - resolvesToNumber + description: | + $tanh takes any valid expression that resolves to a number, measured in radians. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the value to radians. + By default $tanh returns values as a double. $tanh can also return values as a 128-bit decimal if the expression resolves to a 128-bit decimal value. diff --git a/generator/config/expression/toBool.yaml b/generator/config/expression/toBool.yaml new file mode 100644 index 000000000..eaf8ca210 --- /dev/null +++ b/generator/config/expression/toBool.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $toBool +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toBool/' +type: + - resolvesToBool +encode: single +description: | + Converts value to a boolean. + New in MongoDB 4.0. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/expression/toDate.yaml b/generator/config/expression/toDate.yaml new file mode 100644 index 000000000..173669cd4 --- /dev/null +++ b/generator/config/expression/toDate.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $toDate +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDate/' +type: + - resolvesToDate +encode: single +description: | + Converts value to a Date. + New in MongoDB 4.0. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/expression/toDecimal.yaml b/generator/config/expression/toDecimal.yaml new file mode 100644 index 000000000..59db31a53 --- /dev/null +++ b/generator/config/expression/toDecimal.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $toDecimal +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDecimal/' +type: + - resolvesToDecimal +encode: single +description: | + Converts value to a Decimal128. + New in MongoDB 4.0. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/expression/toDouble.yaml b/generator/config/expression/toDouble.yaml new file mode 100644 index 000000000..6fc87f6cd --- /dev/null +++ b/generator/config/expression/toDouble.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $toDouble +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDouble/' +type: + - resolvesToDouble +encode: single +description: | + Converts value to a double. + New in MongoDB 4.0. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/expression/toInt.yaml b/generator/config/expression/toInt.yaml new file mode 100644 index 000000000..cf343643a --- /dev/null +++ b/generator/config/expression/toInt.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $toInt +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toInt/' +type: + - resolvesToInt +encode: single +description: | + Converts value to an integer. + New in MongoDB 4.0. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/expression/toLong.yaml b/generator/config/expression/toLong.yaml new file mode 100644 index 000000000..9687e4791 --- /dev/null +++ b/generator/config/expression/toLong.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $toLong +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLong/' +type: + - resolvesToLong +encode: single +description: | + Converts value to a long. + New in MongoDB 4.0. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/expression/toLower.yaml b/generator/config/expression/toLower.yaml new file mode 100644 index 000000000..bb6764add --- /dev/null +++ b/generator/config/expression/toLower.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $toLower +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLower/' +type: + - resolvesToString +encode: single +description: | + Converts a string to lowercase. Accepts a single argument expression. +arguments: + - + name: expression + type: + - resolvesToString diff --git a/generator/config/expression/toObjectId.yaml b/generator/config/expression/toObjectId.yaml new file mode 100644 index 000000000..e78ee28e1 --- /dev/null +++ b/generator/config/expression/toObjectId.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $toObjectId +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toObjectId/' +type: + - resolvesToObjectId +encode: single +description: | + Converts value to an ObjectId. + New in MongoDB 4.0. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/expression/toString.yaml b/generator/config/expression/toString.yaml new file mode 100644 index 000000000..0e0d3fdfd --- /dev/null +++ b/generator/config/expression/toString.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $toString +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toString/' +type: + - resolvesToString +encode: single +description: | + Converts value to a string. + New in MongoDB 4.0. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/expression/toUpper.yaml b/generator/config/expression/toUpper.yaml new file mode 100644 index 000000000..e62936a92 --- /dev/null +++ b/generator/config/expression/toUpper.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $toUpper +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toUpper/' +type: + - resolvesToString +encode: single +description: | + Converts a string to uppercase. Accepts a single argument expression. +arguments: + - + name: expression + type: + - resolvesToString diff --git a/generator/config/expression/trim.yaml b/generator/config/expression/trim.yaml new file mode 100644 index 000000000..ec38bd5f9 --- /dev/null +++ b/generator/config/expression/trim.yaml @@ -0,0 +1,25 @@ +# $schema: ../schema.json +name: $trim +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/trim/' +type: + - resolvesToString +encode: object +description: | + Removes whitespace or the specified characters from the beginning and end of a string. + New in MongoDB 4.0. +arguments: + - + name: input + type: + - resolvesToString + description: | + The string to trim. The argument can be any valid expression that resolves to a string. + - + name: chars + type: + - resolvesToString + optional: true + description: | + The character(s) to trim from the beginning of the input. + The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. + If unspecified, $ltrim removes whitespace characters, including the null character. diff --git a/generator/config/expression/trunc.yaml b/generator/config/expression/trunc.yaml new file mode 100644 index 000000000..3d5871305 --- /dev/null +++ b/generator/config/expression/trunc.yaml @@ -0,0 +1,23 @@ +# $schema: ../schema.json +name: $trunc +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/trunc/' +type: + - resolvesToString +encode: array +description: | + Truncates a number to a whole integer or to a specified decimal place. +arguments: + - + name: number + type: + - resolvesToNumber + description: | + Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. + $trunc returns an error if the expression resolves to a non-numeric data type. + - + name: place + type: + - resolvesToInt + optional: true + description: | + Can be any valid expression that resolves to an integer between -20 and 100, exclusive. e.g. -20 < place < 100. Defaults to 0. diff --git a/generator/config/expression/tsIncrement.yaml b/generator/config/expression/tsIncrement.yaml new file mode 100644 index 000000000..08d1c5b2c --- /dev/null +++ b/generator/config/expression/tsIncrement.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $tsIncrement +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsIncrement/' +type: + - resolvesToLong +encode: single +description: | + Returns the incrementing ordinal from a timestamp as a long. + New in MongoDB 5.1. +arguments: + - + name: expression + type: + - resolvesToTimestamp diff --git a/generator/config/expression/tsSecond.yaml b/generator/config/expression/tsSecond.yaml new file mode 100644 index 000000000..066b8bb27 --- /dev/null +++ b/generator/config/expression/tsSecond.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $tsSecond +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsSecond/' +type: + - resolvesToLong +encode: single +description: | + Returns the seconds from a timestamp as a long. + New in MongoDB 5.1. +arguments: + - + name: expression + type: + - resolvesToTimestamp diff --git a/generator/config/expression/type.yaml b/generator/config/expression/type.yaml new file mode 100644 index 000000000..f9aa73f39 --- /dev/null +++ b/generator/config/expression/type.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $type +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/type/' +type: + - resolvesToString +encode: single +description: | + Return the BSON data type of the field. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/expression/unsetField.yaml b/generator/config/expression/unsetField.yaml new file mode 100644 index 000000000..106b47cac --- /dev/null +++ b/generator/config/expression/unsetField.yaml @@ -0,0 +1,22 @@ +# $schema: ../schema.json +name: $unsetField +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/' +type: + - resolvesToObject +encode: object +description: | + You can use $unsetField to remove fields with names that contain periods (.) or that start with dollar signs ($). + $unsetField is an alias for $setField using $$REMOVE to remove fields. +arguments: + - + name: field + type: + - resolvesToString + description: | + Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. + - + name: input + type: + - resolvesToObject + description: | + Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. diff --git a/generator/config/expression/week.yaml b/generator/config/expression/week.yaml new file mode 100644 index 000000000..4fd7b08f3 --- /dev/null +++ b/generator/config/expression/week.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $week +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/week/' +type: + - resolvesToInt +encode: object +description: | + Returns the week number for a date as a number between 0 (the partial week that precedes the first Sunday of the year) and 53 (leap year). +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/year.yaml b/generator/config/expression/year.yaml new file mode 100644 index 000000000..e2de94fa9 --- /dev/null +++ b/generator/config/expression/year.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $year +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/year/' +type: + - resolvesToInt +encode: object +description: | + Returns the year for a date as a number (e.g. 2014). +arguments: + - + name: date + type: + - resolvesToDate + - resolvesToTimestamp + - resolvesToObjectId + description: | + The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + - + name: timezone + type: + - resolvesToString + optional: true + description: | + The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. diff --git a/generator/config/expression/zip.yaml b/generator/config/expression/zip.yaml new file mode 100644 index 000000000..3b8697037 --- /dev/null +++ b/generator/config/expression/zip.yaml @@ -0,0 +1,32 @@ +# $schema: ../schema.json +name: $zip +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/zip/' +type: + - resolvesToArray # of array +encode: object +description: | + Merge two arrays together. +arguments: + - + name: inputs + type: + - resolvesToArray # of array + description: | + An array of expressions that resolve to arrays. The elements of these input arrays combine to form the arrays of the output array. + If any of the inputs arrays resolves to a value of null or refers to a missing field, $zip returns null. + If any of the inputs arrays does not resolve to an array or null nor refers to a missing field, $zip returns an error. + - + name: useLongestLength + type: + - bool + description: | + A boolean which specifies whether the length of the longest array determines the number of arrays in the output array. + The default value is false: the shortest array length determines the number of arrays in the output array. + - + name: defaults + type: + - array + description: | + An array of default element values to use if the input arrays have different lengths. You must specify useLongestLength: true along with this field, or else $zip will return an error. + If useLongestLength: true but defaults is empty or not specified, $zip uses null as the default value. + If specifying a non-empty defaults, you must specify a default for each input array or else $zip will return an error. diff --git a/generator/config/expressions.php b/generator/config/expressions.php new file mode 100644 index 000000000..fa88274ba --- /dev/null +++ b/generator/config/expressions.php @@ -0,0 +1,163 @@ + ['int', BSON\Int64::class, 'float'], + 'string' => ['string'], + 'object' => ['array', stdClass::class, BSON\Document::class, BSON\Serializable::class], + 'array' => ['array', BSONArray::class, BSON\PackedArray::class], + 'binData' => ['string', BSON\Binary::class], + 'objectId' => [BSON\ObjectId::class], + 'bool' => ['bool'], + 'date' => [BSON\UTCDateTime::class], + 'null' => ['null'], + 'regex' => [BSON\Regex::class], + 'javascript' => ['string', BSON\Javascript::class], + 'int' => ['int'], + 'timestamp' => ['int', BSON\Timestamp::class], + 'long' => ['int', BSON\Int64::class], + 'decimal' => ['int', BSON\Int64::class, 'float', BSON\Decimal128::class], +]; + +// "any" accepts all the BSON types. No generic "object" or "mixed" +$bsonTypes['any'] = ['bool', 'int', 'float', 'string', 'array', 'null', stdClass::class, BSON\Type::class]; + +// "number" accepts all the numeric types +$bsonTypes['number'] = ['int', 'float', BSON\Int64::class, BSON\Decimal128::class]; + +$expressions = []; +$resolvesToInterfaces = []; +foreach ($bsonTypes as $name => $acceptedTypes) { + $expressions[$name] = ['acceptedTypes' => $acceptedTypes]; + + $resolvesTo = 'resolvesTo' . ucfirst($name); + $resolvesToInterface = __NAMESPACE__ . '\\' . ucfirst($resolvesTo); + $expressions[$resolvesTo] = [ + 'generate' => PhpObject::PhpInterface, + 'implements' => [Type\ExpressionInterface::class], + 'returnType' => $resolvesToInterface, + 'acceptedTypes' => $acceptedTypes, + ]; + + $fieldPathName = $name . 'FieldPath'; + if ($name === 'any') { + $fieldPathName = 'fieldPath'; + } else { + $resolvesToInterfaces[] = $resolvesToInterface; + } + + $expressions[$fieldPathName] = [ + 'generate' => PhpObject::PhpClass, + 'implements' => [Type\FieldPathInterface::class, $resolvesToInterface], + 'acceptedTypes' => ['string'], + ]; +} + +$expressions['resolvesToLong']['implements'] = [ResolvesToInt::class]; +$expressions['resolvesToInt']['implements'] = [ResolvesToNumber::class]; +$expressions['resolvesToDecimal']['implements'] = [ResolvesToDouble::class]; +$expressions['resolvesToDouble']['implements'] = [ResolvesToNumber::class]; +$expressions['resolvesToAny']['implements'] = $resolvesToInterfaces; + +return $expressions + [ + 'expression' => [ + 'returnType' => Type\ExpressionInterface::class, + 'acceptedTypes' => [Type\ExpressionInterface::class, ...$bsonTypes['any']], + ], + 'fieldQuery' => [ + 'returnType' => Type\FieldQueryInterface::class, + 'acceptedTypes' => [Type\FieldQueryInterface::class, ...$bsonTypes['any']], + ], + 'query' => [ + 'returnType' => Type\QueryInterface::class, + 'acceptedTypes' => [Type\QueryInterface::class, ...$bsonTypes['object']], + ], + 'projection' => [ + 'returnType' => Type\ProjectionInterface::class, + 'acceptedTypes' => [Type\ProjectionInterface::class, ...$bsonTypes['object']], + ], + 'accumulator' => [ + 'returnType' => Type\AccumulatorInterface::class, + 'acceptedTypes' => [Type\AccumulatorInterface::class, ...$bsonTypes['object']], + ], + 'window' => [ + 'returnType' => Type\WindowInterface::class, + 'acceptedTypes' => [Type\WindowInterface::class, ...$bsonTypes['object']], + ], + 'stage' => [ + 'returnType' => Type\StageInterface::class, + 'acceptedTypes' => [Type\StageInterface::class, ...$bsonTypes['object']], + ], + 'pipeline' => [ + 'acceptedTypes' => [Pipeline::class, ...$bsonTypes['array']], + ], + 'variable' => [ + 'generate' => PhpObject::PhpClass, + 'implements' => [ResolvesToAny::class], + 'acceptedTypes' => ['string'], + ], + 'geometry' => [ + 'returnType' => Type\GeometryInterface::class, + 'acceptedTypes' => [Type\GeometryInterface::class, ...$bsonTypes['object']], + ], + + // @todo add enum values + 'Granularity' => [ + 'acceptedTypes' => [...$bsonTypes['string']], + ], + 'FullDocument' => [ + 'acceptedTypes' => [...$bsonTypes['string']], + ], + 'FullDocumentBeforeChange' => [ + 'acceptedTypes' => [...$bsonTypes['string']], + ], + 'AccumulatorPercentile' => [ + 'acceptedTypes' => [...$bsonTypes['string']], + ], + 'WhenMatched' => [ + 'acceptedTypes' => [...$bsonTypes['string']], + ], + 'WhenNotMatched' => [ + 'acceptedTypes' => [...$bsonTypes['string']], + ], + + // @todo create specific model classes factories + 'OutCollection' => [ + 'acceptedTypes' => [...$bsonTypes['object']], + ], + 'CollStats' => [ + 'acceptedTypes' => [...$bsonTypes['object']], + ], + 'Range' => [ + 'acceptedTypes' => [...$bsonTypes['object']], + ], + 'FillOut' => [ + 'acceptedTypes' => [...$bsonTypes['object']], + ], + 'SortSpec' => [ + 'acceptedTypes' => [...$bsonTypes['object']], + ], + 'Window' => [ + 'acceptedTypes' => [...$bsonTypes['object']], + ], + 'GeoPoint' => [ + 'acceptedTypes' => [...$bsonTypes['object']], + ], +]; diff --git a/generator/config/projection/elemMatch.yaml b/generator/config/projection/elemMatch.yaml new file mode 100644 index 000000000..b309c5a9e --- /dev/null +++ b/generator/config/projection/elemMatch.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $elemMatch +link: 'https://www.mongodb.com/docs/manual/reference/operator/projection/elemMatch/' +type: + - projection +encode: object +description: | + Projects the first element in an array that matches the specified $elemMatch condition. +arguments: + - + name: query + type: + - query diff --git a/generator/config/projection/filter.yaml b/generator/config/projection/filter.yaml new file mode 100644 index 000000000..50cfaaedf --- /dev/null +++ b/generator/config/projection/filter.yaml @@ -0,0 +1,34 @@ +# $schema: ../schema.json +name: $filter +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/' +type: + - projection +encode: object +description: | + Selects a subset of the array to return an array with only the elements that match the filter condition. +arguments: + - + name: input + type: + - resolvesToArray + - + name: cond + type: + - resolvesToBool + description: | + An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. + - + name: as + type: + - string + optional: true + description: | + A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + - + name: limit + type: + - resolvesToInt + optional: true + description: | + A number expression that restricts the number of matching array elements that $filter returns. You cannot specify a limit less than 1. The matching array elements are returned in the order they appear in the input array. + If the specified limit is greater than the number of matching array elements, $filter returns all matching array elements. If the limit is null, $filter returns all matching array elements. diff --git a/generator/config/projection/slice.yaml b/generator/config/projection/slice.yaml new file mode 100644 index 000000000..cfab8f4a7 --- /dev/null +++ b/generator/config/projection/slice.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $slice +link: 'https://www.mongodb.com/docs/manual/reference/operator/projection/slice/' +type: + - projection +encode: array +description: | + Limits the number of elements projected from an array. Supports skip and limit slices. +arguments: + - + name: limit + type: + - int + - + name: skip + type: + - int diff --git a/generator/config/query/all.yaml b/generator/config/query/all.yaml new file mode 100644 index 000000000..0a50e2091 --- /dev/null +++ b/generator/config/query/all.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $all +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/all/' +type: + - fieldQuery +encode: single +description: | + Matches arrays that contain all elements specified in the query. +arguments: + - + name: value + type: + - any + variadic: array diff --git a/generator/config/query/and.yaml b/generator/config/query/and.yaml new file mode 100644 index 000000000..14fde59a2 --- /dev/null +++ b/generator/config/query/and.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $and +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/and/' +type: + - query +encode: single +description: | + Joins query clauses with a logical AND returns all documents that match the conditions of both clauses. +arguments: + - + name: expression + type: + - query + variadic: array diff --git a/generator/config/query/bitsAllClear.yaml b/generator/config/query/bitsAllClear.yaml new file mode 100644 index 000000000..27f7cc6c1 --- /dev/null +++ b/generator/config/query/bitsAllClear.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $bitsAllClear +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/bitsAllClear/' +type: + - fieldQuery +encode: single +description: | + Matches numeric or binary values in which a set of bit positions all have a value of 0. +arguments: + - + name: bitmask + type: + - int + - binData + - array # of int diff --git a/generator/config/query/bitsAllSet.yaml b/generator/config/query/bitsAllSet.yaml new file mode 100644 index 000000000..c27b2ea20 --- /dev/null +++ b/generator/config/query/bitsAllSet.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $bitsAllSet +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/bitsAllSet/' +type: + - fieldQuery +encode: single +description: | + Matches numeric or binary values in which a set of bit positions all have a value of 1. +arguments: + - + name: bitmask + type: + - int + - binData + - array # of int diff --git a/generator/config/query/bitsAnyClear.yaml b/generator/config/query/bitsAnyClear.yaml new file mode 100644 index 000000000..1fa6a3afd --- /dev/null +++ b/generator/config/query/bitsAnyClear.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $bitsAnyClear +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/bitsAnyClear/' +type: + - fieldQuery +encode: single +description: | + Matches numeric or binary values in which any bit from a set of bit positions has a value of 0. +arguments: + - + name: bitmask + type: + - int + - binData + - array # of int diff --git a/generator/config/query/bitsAnySet.yaml b/generator/config/query/bitsAnySet.yaml new file mode 100644 index 000000000..cce76cd26 --- /dev/null +++ b/generator/config/query/bitsAnySet.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $bitsAnySet +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/bitsAnySet/' +type: + - fieldQuery +encode: single +description: | + Matches numeric or binary values in which any bit from a set of bit positions has a value of 1. +arguments: + - + name: bitmask + type: + - int + - binData + - array # of int diff --git a/generator/config/query/box.yaml b/generator/config/query/box.yaml new file mode 100644 index 000000000..14043c4ae --- /dev/null +++ b/generator/config/query/box.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $box +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/box/' +type: + - geometry +encode: single +description: | + Specifies a rectangular box using legacy coordinate pairs for $geoWithin queries. The 2d index supports $box. +arguments: + - + name: value + type: + - array diff --git a/generator/config/query/center.yaml b/generator/config/query/center.yaml new file mode 100644 index 000000000..c86215a52 --- /dev/null +++ b/generator/config/query/center.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $center +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/center/' +type: + - geometry +encode: single +description: | + Specifies a circle using legacy coordinate pairs to $geoWithin queries when using planar geometry. The 2d index supports $center. +arguments: + - + name: value + type: + - array diff --git a/generator/config/query/centerSphere.yaml b/generator/config/query/centerSphere.yaml new file mode 100644 index 000000000..e3677dade --- /dev/null +++ b/generator/config/query/centerSphere.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $centerSphere +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/centerSphere/' +type: + - geometry +encode: single +description: | + Specifies a circle using either legacy coordinate pairs or GeoJSON format for $geoWithin queries when using spherical geometry. The 2dsphere and 2d indexes support $centerSphere. +arguments: + - + name: value + type: + - array diff --git a/generator/config/query/comment.yaml b/generator/config/query/comment.yaml new file mode 100644 index 000000000..b5b0fb25c --- /dev/null +++ b/generator/config/query/comment.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $comment +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/comment/' +type: + - query +encode: single +description: | + Adds a comment to a query predicate. +arguments: + - + name: comment + type: + - string diff --git a/generator/config/query/elemMatch.yaml b/generator/config/query/elemMatch.yaml new file mode 100644 index 000000000..2b9866bce --- /dev/null +++ b/generator/config/query/elemMatch.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $elemMatch +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/' +type: + - fieldQuery +encode: object +description: | + The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria. +arguments: + - + name: query + type: + - query diff --git a/generator/config/query/eq.yaml b/generator/config/query/eq.yaml new file mode 100644 index 000000000..363964f46 --- /dev/null +++ b/generator/config/query/eq.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $eq +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/eq/' +type: + - fieldQuery +encode: single +description: | + Matches values that are equal to a specified value. +arguments: + - + name: value + type: + - any diff --git a/generator/config/query/exists.yaml b/generator/config/query/exists.yaml new file mode 100644 index 000000000..014e31db1 --- /dev/null +++ b/generator/config/query/exists.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $exists +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/exists/' +type: + - fieldQuery +encode: single +description: | + Matches documents that have the specified field. +arguments: + - + name: exists + type: + - bool diff --git a/generator/config/query/expr.yaml b/generator/config/query/expr.yaml new file mode 100644 index 000000000..04f2cf449 --- /dev/null +++ b/generator/config/query/expr.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $expr +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/expr/' +type: + - fieldQuery +encode: single +description: | + Allows use of aggregation expressions within the query language. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/query/geoIntersects.yaml b/generator/config/query/geoIntersects.yaml new file mode 100644 index 000000000..45a4ac53a --- /dev/null +++ b/generator/config/query/geoIntersects.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $geoIntersects +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/geoIntersects/' +type: + - fieldQuery +encode: single +description: | + Selects geometries that intersect with a GeoJSON geometry. The 2dsphere index supports $geoIntersects. +arguments: + - + name: geometry + type: + - geometry diff --git a/generator/config/query/geoWithin.yaml b/generator/config/query/geoWithin.yaml new file mode 100644 index 000000000..12944b42c --- /dev/null +++ b/generator/config/query/geoWithin.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $geoWithin +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/' +type: + - fieldQuery +encode: single +description: | + Selects geometries within a bounding GeoJSON geometry. The 2dsphere and 2d indexes support $geoWithin. +arguments: + - + name: geometry + type: + - geometry diff --git a/generator/config/query/geometry.yaml b/generator/config/query/geometry.yaml new file mode 100644 index 000000000..a08325396 --- /dev/null +++ b/generator/config/query/geometry.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $geometry +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/geometry/' +type: + - geometry +encode: object +description: | + Specifies a geometry in GeoJSON format to geospatial query operators. +arguments: + - + name: type + type: + - string + - + name: coordinates + type: + - array + - + name: crs + type: + - object diff --git a/generator/config/query/gt.yaml b/generator/config/query/gt.yaml new file mode 100644 index 000000000..3d06d02e8 --- /dev/null +++ b/generator/config/query/gt.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $gt +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/gt/' +type: + - fieldQuery +encode: single +description: | + Matches values that are greater than a specified value. +arguments: + - + name: value + type: + - any diff --git a/generator/config/query/gte.yaml b/generator/config/query/gte.yaml new file mode 100644 index 000000000..ba81633a0 --- /dev/null +++ b/generator/config/query/gte.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $gte +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/gte/' +type: + - fieldQuery +encode: single +description: | + Matches values that are greater than or equal to a specified value. +arguments: + - + name: value + type: + - any diff --git a/generator/config/query/in.yaml b/generator/config/query/in.yaml new file mode 100644 index 000000000..dce69929a --- /dev/null +++ b/generator/config/query/in.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $in +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/in/' +type: + - fieldQuery +encode: single +description: | + Matches any of the values specified in an array. +arguments: + - + name: value + type: + - array # of expression diff --git a/generator/config/query/jsonSchema.yaml b/generator/config/query/jsonSchema.yaml new file mode 100644 index 000000000..88af08c0a --- /dev/null +++ b/generator/config/query/jsonSchema.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $jsonSchema +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/jsonSchema/' +type: + - query +encode: single +description: | + Validate documents against the given JSON Schema. +arguments: + - + name: schema + type: + - object diff --git a/generator/config/query/lt.yaml b/generator/config/query/lt.yaml new file mode 100644 index 000000000..d07d26d4c --- /dev/null +++ b/generator/config/query/lt.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $lt +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/lt/' +type: + - fieldQuery +encode: single +description: | + Matches values that are less than a specified value. +arguments: + - + name: value + type: + - any diff --git a/generator/config/query/lte.yaml b/generator/config/query/lte.yaml new file mode 100644 index 000000000..68f1c458d --- /dev/null +++ b/generator/config/query/lte.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $lte +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/lte/' +type: + - fieldQuery +encode: single +description: | + Matches values that are less than or equal to a specified value. +arguments: + - + name: value + type: + - any diff --git a/generator/config/query/maxDistance.yaml b/generator/config/query/maxDistance.yaml new file mode 100644 index 000000000..e95212d23 --- /dev/null +++ b/generator/config/query/maxDistance.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $maxDistance +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/maxDistance/' +type: + - fieldQuery +encode: single +description: | + Specifies a maximum distance to limit the results of $near and $nearSphere queries. The 2dsphere and 2d indexes support $maxDistance. +arguments: + - + name: value + type: + - number diff --git a/generator/config/query/minDistance.yaml b/generator/config/query/minDistance.yaml new file mode 100644 index 000000000..fd467a96f --- /dev/null +++ b/generator/config/query/minDistance.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $minDistance +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/minDistance/' +type: + - fieldQuery +encode: single +description: | + Specifies a minimum distance to limit the results of $near and $nearSphere queries. For use with 2dsphere index only. +arguments: + - + name: value + type: + - int + - double diff --git a/generator/config/query/mod.yaml b/generator/config/query/mod.yaml new file mode 100644 index 000000000..291c6a61f --- /dev/null +++ b/generator/config/query/mod.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $mod +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/mod/' +type: + - fieldQuery +encode: array +description: | + Performs a modulo operation on the value of a field and selects documents with a specified result. +arguments: + - + name: divisor + type: + - int + - + name: remainder + type: + - int diff --git a/generator/config/query/natural.yaml b/generator/config/query/natural.yaml new file mode 100644 index 000000000..f3759972f --- /dev/null +++ b/generator/config/query/natural.yaml @@ -0,0 +1,8 @@ +# $schema: ../schema.json +name: $natural +link: 'https://www.mongodb.com/docs/v7.0/reference/operator/meta/natural/' +type: + - expression +encode: object +description: | + A special hint that can be provided via the sort() or hint() methods that can be used to force either a forward or reverse collection scan. diff --git a/generator/config/query/ne.yaml b/generator/config/query/ne.yaml new file mode 100644 index 000000000..aabe88c40 --- /dev/null +++ b/generator/config/query/ne.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $ne +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/ne/' +type: + - fieldQuery +encode: single +description: | + Matches all values that are not equal to a specified value. +arguments: + - + name: value + type: + - any diff --git a/generator/config/query/near.yaml b/generator/config/query/near.yaml new file mode 100644 index 000000000..36945657b --- /dev/null +++ b/generator/config/query/near.yaml @@ -0,0 +1,27 @@ +# $schema: ../schema.json +name: $near +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/near/' +type: + - fieldQuery +encode: object +description: | + Returns geospatial objects in proximity to a point. Requires a geospatial index. The 2dsphere and 2d indexes support $near. +arguments: + - + name: geometry + type: + - geometry + - + name: maxDistance + type: + - int + optional: true + description: | + Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. + - + name: minDistance + type: + - int + optional: true + description: | + Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. diff --git a/generator/config/query/nearSphere.yaml b/generator/config/query/nearSphere.yaml new file mode 100644 index 000000000..bc9a48e6b --- /dev/null +++ b/generator/config/query/nearSphere.yaml @@ -0,0 +1,27 @@ +# $schema: ../schema.json +name: $nearSphere +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/' +type: + - fieldQuery +encode: object +description: | + Returns geospatial objects in proximity to a point on a sphere. Requires a geospatial index. The 2dsphere and 2d indexes support $nearSphere. +arguments: + - + name: geometry + type: + - geometry + - + name: maxDistance + type: + - int + optional: true + description: | + Distance in meters. + - + name: minDistance + type: + - int + optional: true + description: | + Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. diff --git a/generator/config/query/nin.yaml b/generator/config/query/nin.yaml new file mode 100644 index 000000000..8fccbe3b8 --- /dev/null +++ b/generator/config/query/nin.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $nin +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nin/' +type: + - fieldQuery +encode: single +description: | + Matches none of the values specified in an array. +arguments: + - + name: value + type: + - array # of expression diff --git a/generator/config/query/nor.yaml b/generator/config/query/nor.yaml new file mode 100644 index 000000000..c0fdf53d6 --- /dev/null +++ b/generator/config/query/nor.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $nor +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nor/' +type: + - query +encode: single +description: | + Joins query clauses with a logical NOR returns all documents that fail to match both clauses. +arguments: + - + name: expression + type: + - query + variadic: array diff --git a/generator/config/query/not.yaml b/generator/config/query/not.yaml new file mode 100644 index 000000000..bd7d1d0fe --- /dev/null +++ b/generator/config/query/not.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $not +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/not/' +type: + - fieldQuery +encode: single +description: | + Inverts the effect of a query expression and returns documents that do not match the query expression. +arguments: + - + name: expression + type: + - query + - regex diff --git a/generator/config/query/or.yaml b/generator/config/query/or.yaml new file mode 100644 index 000000000..8d763871c --- /dev/null +++ b/generator/config/query/or.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $or +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/or/' +type: + - query +encode: single +description: | + Joins query clauses with a logical OR returns all documents that match the conditions of either clause. +arguments: + - + name: expression + type: + - query + variadic: array diff --git a/generator/config/query/polygon.yaml b/generator/config/query/polygon.yaml new file mode 100644 index 000000000..1a46f2bc4 --- /dev/null +++ b/generator/config/query/polygon.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $polygon +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/polygon/' +type: + - geometry +encode: single +description: | + Specifies a polygon to using legacy coordinate pairs for $geoWithin queries. The 2d index supports $center. +arguments: + - + name: points + type: + - array diff --git a/generator/config/query/rand.yaml b/generator/config/query/rand.yaml new file mode 100644 index 000000000..b7a0ce107 --- /dev/null +++ b/generator/config/query/rand.yaml @@ -0,0 +1,8 @@ +# $schema: ../schema.json +name: $rand +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/rand/' +type: + - expression +encode: object +description: | + Generates a random float between 0 and 1. diff --git a/generator/config/query/regex.yaml b/generator/config/query/regex.yaml new file mode 100644 index 000000000..0b0b8f1af --- /dev/null +++ b/generator/config/query/regex.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $regex +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/regex/' +type: + - fieldQuery +encode: single +description: | + Selects documents where values match a specified regular expression. +arguments: + - + name: regex + type: + - regex diff --git a/generator/config/query/size.yaml b/generator/config/query/size.yaml new file mode 100644 index 000000000..0ce05de15 --- /dev/null +++ b/generator/config/query/size.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $size +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/size/' +type: + - fieldQuery +encode: single +description: | + Selects documents if the array field is a specified size. +arguments: + - + name: value + type: + - int diff --git a/generator/config/query/text.yaml b/generator/config/query/text.yaml new file mode 100644 index 000000000..c057cee5d --- /dev/null +++ b/generator/config/query/text.yaml @@ -0,0 +1,38 @@ +# $schema: ../schema.json +name: $text +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/text/' +type: + - query +encode: object +description: | + Performs text search. +arguments: + - + name: search + type: + - string + description: | + A string of terms that MongoDB parses and uses to query the text index. MongoDB performs a logical OR search of the terms unless specified as a phrase. + - + name: language + type: + - string + optional: true + description: | + The language that determines the list of stop words for the search and the rules for the stemmer and tokenizer. If not specified, the search uses the default language of the index. + If you specify a default_language value of none, then the text index parses through each word in the field, including stop words, and ignores suffix stemming. + - + name: caseSensitive + type: + - bool + optional: true + description: | + A boolean flag to enable or disable case sensitive search. Defaults to false; i.e. the search defers to the case insensitivity of the text index. + - + name: diacriticSensitive + type: + - bool + optional: true + description: | + A boolean flag to enable or disable diacritic sensitive search against version 3 text indexes. Defaults to false; i.e. the search defers to the diacritic insensitivity of the text index. + Text searches against earlier versions of the text index are inherently diacritic sensitive and cannot be diacritic insensitive. As such, the $diacriticSensitive option has no effect with earlier versions of the text index. diff --git a/generator/config/query/type.yaml b/generator/config/query/type.yaml new file mode 100644 index 000000000..769aea726 --- /dev/null +++ b/generator/config/query/type.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $type +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/type/' +type: + - fieldQuery +encode: single +description: | + Selects documents if a field is of the specified type. +arguments: + - + name: type + type: + - int + - string + - array # of int or string diff --git a/generator/config/query/where.yaml b/generator/config/query/where.yaml new file mode 100644 index 000000000..8845715d1 --- /dev/null +++ b/generator/config/query/where.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $where +link: 'https://www.mongodb.com/docs/manual/reference/operator/query/where/' +type: + - query +encode: single +description: | + Matches documents that satisfy a JavaScript expression. +arguments: + - + name: function + type: + - string diff --git a/generator/config/schema.json b/generator/config/schema.json new file mode 100644 index 000000000..ca61931bb --- /dev/null +++ b/generator/config/schema.json @@ -0,0 +1,165 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$ref": "#/definitions/Operator", + "definitions": { + "Operator": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "$comment": "The name of the operator. Must start with a $", + "type": "string", + "pattern": "^\\$[a-z][a-zA-Z]+$" + }, + "link": { + "$comment": "The link to the operator's documentation on MongoDB's website.", + "type": "string", + "format": "uri", + "qt-uri-protocols": [ + "https" + ] + }, + "type": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "accumulator", + "projection", + "stage", + "query", + "filter", + "window", + "geometry", + "resolvesToAny", + "resolvesToNumber", + "resolvesToDouble", + "resolvesToString", + "resolvesToObject", + "resolvesToArray", + "resolvesToBinData", + "resolvesToObjectId", + "resolvesToBool", + "resolvesToDate", + "resolvesToNull", + "resolvesToRegex", + "resolvesToJavascript", + "resolvesToInt", + "resolvesToTimestamp", + "resolvesToLong", + "resolvesToDecimal" + ] + } + }, + "encode": { + "$comment": "Specifies how operator parameters are encoded.", + "$comment": "array: parameters are encoded as an array of values in the order they are defined by the spec", + "$comment": "object: parameters are encoded as an object with keys matching the parameter names", + "$comment": "single: get the single parameter value", + "$comment": "group: specific for $group stage", + "type": "string", + "enum": [ + "array", + "object", + "single", + "group" + ] + }, + "description": { + "$comment": "The description of the argument from MongoDB's documentation.", + "type": "string" + }, + "arguments": { + "$comment": "An optional list of arguments for the operator.", + "type": "array", + "items": { + "$ref": "#/definitions/Argument" + } + } + }, + "required": [ + "description", + "encode", + "link", + "name", + "type" + ], + "title": "Operator" + }, + "Argument": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "pattern": "^([a-z][a-zA-Z0-9]*|N)$" + }, + "type": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "accumulator", + "query", + "pipeline", + "window", + "expression", + "geometry", + "any", + "resolvesToNumber", "numberFieldPath", "number", + "resolvesToDouble", "doubleFieldPath", "double", + "resolvesToString", "stringFieldPath", "string", + "resolvesToObject", "objectFieldPath", "object", + "resolvesToArray", "arrayFieldPath", "array", + "resolvesToBinData", "binDataFieldPath", "binData", + "resolvesToObjectId", "objectIdFieldPath", "objectId", + "resolvesToBool", "boolFieldPath", "bool", + "resolvesToDate", "dateFieldPath", "date", + "resolvesToNull", "nullFieldPath", "null", + "resolvesToRegex", "regexFieldPath", "regex", + "resolvesToJavascript", "javascriptFieldPath", "javascript", + "resolvesToInt", "intFieldPath", "int", + "resolvesToTimestamp", "timestampFieldPath", "timestamp", + "resolvesTolong", "longFieldPath", "long", + "resolvesToDecimal", "decimalFieldPath", "decimal" + ] + } + }, + "description": { + "$comment": "The description of the argument from MongoDB's documentation.", + "type": "string" + }, + "optional": { + "$comment": "Whether the argument is optional or not.", + "type": "boolean" + }, + "valueMin": { + "$comment": "The minimum value for a numeric argument.", + "type": "number" + }, + "valueMax": { + "$comment": "The minimum value for a numeric argument.", + "type": "number" + }, + "variadic": { + "$comment": "Whether the argument is variadic or not.", + "type": "string", + "enum": [ + "array", + "object" + ] + }, + "variadicMin": { + "$comment": "The minimum number of arguments for a variadic parameter.", + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "name", + "type" + ], + "title": "Argument" + } + } +} diff --git a/generator/config/stage/addFields.yaml b/generator/config/stage/addFields.yaml new file mode 100644 index 000000000..f4084f054 --- /dev/null +++ b/generator/config/stage/addFields.yaml @@ -0,0 +1,16 @@ +# $schema: ../schema.json +name: $addFields +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/' +type: + - stage +encode: single +description: | + Adds new fields to documents. Outputs documents that contain all existing fields from the input documents and newly added fields. +arguments: + - + name: expression + type: + - expression + variadic: object + description: | + Specify the name of each field to add and set its value to an aggregation expression or an empty object. diff --git a/generator/config/stage/bucket.yaml b/generator/config/stage/bucket.yaml new file mode 100644 index 000000000..f0e5a9e6e --- /dev/null +++ b/generator/config/stage/bucket.yaml @@ -0,0 +1,42 @@ +# $schema: ../schema.json +name: $bucket +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucket/' +type: + - stage +encode: object +description: | + Categorizes incoming documents into groups, called buckets, based on a specified expression and bucket boundaries. +arguments: + - + name: groupBy + type: + - expression # mainly fieldPath + description: | + An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. + Unless $bucket includes a default specification, each input document must resolve the groupBy field path or expression to a value that falls within one of the ranges specified by the boundaries. + - + name: boundaries + type: + - array # of expression + description: | + An array of values based on the groupBy expression that specify the boundaries for each bucket. Each adjacent pair of values acts as the inclusive lower boundary and the exclusive upper boundary for the bucket. You must specify at least two boundaries. + The specified values must be in ascending order and all of the same type. The exception is if the values are of mixed numeric types, such as: + - + name: default + type: + - expression + optional: true + description: | + A literal that specifies the _id of an additional bucket that contains all documents whose groupBy expression result does not fall into a bucket specified by boundaries. + If unspecified, each input document must resolve the groupBy expression to a value within one of the bucket ranges specified by boundaries or the operation throws an error. + The default value must be less than the lowest boundaries value, or greater than or equal to the highest boundaries value. + The default value can be of a different type than the entries in boundaries. + - + name: output + type: + - object # of Accumulator + optional: true + description: | + A document that specifies the fields to include in the output documents in addition to the _id field. To specify the field to include, you must use accumulator expressions. + If you do not specify an output document, the operation returns a count field containing the number of documents in each bucket. + If you specify an output document, only the fields specified in the document are returned; i.e. the count field is not returned unless it is explicitly included in the output document. diff --git a/generator/config/stage/bucketAuto.yaml b/generator/config/stage/bucketAuto.yaml new file mode 100644 index 000000000..a1c5736cf --- /dev/null +++ b/generator/config/stage/bucketAuto.yaml @@ -0,0 +1,37 @@ +# $schema: ../schema.json +name: $bucketAuto +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucketAuto/' +type: + - stage +encode: object +description: | + Categorizes incoming documents into a specific number of groups, called buckets, based on a specified expression. Bucket boundaries are automatically determined in an attempt to evenly distribute the documents into the specified number of buckets. +arguments: + - + name: groupBy + type: + - expression + description: | + An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. + - + name: buckets + type: + - int + description: | + A positive 32-bit integer that specifies the number of buckets into which input documents are grouped. + - + name: output + type: + - object # of Accumulator + optional: true + description: | + A document that specifies the fields to include in the output documents in addition to the _id field. To specify the field to include, you must use accumulator expressions. + The default count field is not included in the output document when output is specified. Explicitly specify the count expression as part of the output document to include it. + - + name: granularity + type: + - object # Granularity + optional: true + description: | + A string that specifies the preferred number series to use to ensure that the calculated boundary edges end on preferred round numbers or their powers of 10. + Available only if the all groupBy values are numeric and none of them are NaN. diff --git a/generator/config/stage/changeStream.yaml b/generator/config/stage/changeStream.yaml new file mode 100644 index 000000000..46a6e6d24 --- /dev/null +++ b/generator/config/stage/changeStream.yaml @@ -0,0 +1,59 @@ +# $schema: ../schema.json +name: $changeStream +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/changeStream/' +type: + - stage +encode: object +description: | + Returns a Change Stream cursor for the collection or database. This stage can only occur once in an aggregation pipeline and it must occur as the first stage. +arguments: + - + name: allChangesForCluster + type: + - bool + optional: true + description: | + A flag indicating whether the stream should report all changes that occur on the deployment, aside from those on internal databases or collections. + - + name: fullDocument + type: + - FullDocument + optional: true + description: | + Specifies whether change notifications include a copy of the full document when modified by update operations. + - + name: fullDocumentBeforeChange + type: + - FullDocumentBeforeChange + optional: true + description: | + Valid values are "off", "whenAvailable", or "required". If set to "off", the "fullDocumentBeforeChange" field of the output document is always omitted. If set to "whenAvailable", the "fullDocumentBeforeChange" field will be populated with the pre-image of the document modified by the current change event if such a pre-image is available, and will be omitted otherwise. If set to "required", then the "fullDocumentBeforeChange" field is always populated and an exception is thrown if the pre-image is not available. + - + name: resumeAfter + type: + - int + optional: true + description: | + Specifies a resume token as the logical starting point for the change stream. Cannot be used with startAfter or startAtOperationTime fields. + - + name: showExpandedEvents + type: + - bool + optional: true + description: | + Specifies whether to include additional change events, such as such as DDL and index operations. + New in MongoDB 6.0. + - + name: startAfter + type: + - object + optional: true + description: | + Specifies a resume token as the logical starting point for the change stream. Cannot be used with resumeAfter or startAtOperationTime fields. + - + name: startAtOperationTime + type: + - timestamp + optional: true + description: | + Specifies a time as the logical starting point for the change stream. Cannot be used with resumeAfter or startAfter fields. diff --git a/generator/config/stage/changeStreamSplitLargeEvent.yaml b/generator/config/stage/changeStreamSplitLargeEvent.yaml new file mode 100644 index 000000000..331c0dd2d --- /dev/null +++ b/generator/config/stage/changeStreamSplitLargeEvent.yaml @@ -0,0 +1,9 @@ +# $schema: ../schema.json +name: $changeStreamSplitLargeEvent +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/changeStreamSplitLargeEvent/' +type: + - stage +encode: object +description: | + Splits large change stream events that exceed 16 MB into smaller fragments returned in a change stream cursor. + You can only use $changeStreamSplitLargeEvent in a $changeStream pipeline and it must be the final stage in the pipeline. diff --git a/generator/config/stage/collStats.yaml b/generator/config/stage/collStats.yaml new file mode 100644 index 000000000..0e7bd8d04 --- /dev/null +++ b/generator/config/stage/collStats.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $collStats +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/' +type: + - stage +encode: single +description: | + Returns statistics regarding a collection or view. +arguments: + - + name: config + type: + - object diff --git a/generator/config/stage/count.yaml b/generator/config/stage/count.yaml new file mode 100644 index 000000000..37ed506c4 --- /dev/null +++ b/generator/config/stage/count.yaml @@ -0,0 +1,16 @@ +# $schema: ../schema.json +name: $count +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/count/' +type: + - stage +encode: single +description: | + Returns a count of the number of documents at this stage of the aggregation pipeline. + Distinct from the $count aggregation accumulator. +arguments: + - + name: field + type: + - string + description: | + Name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character. diff --git a/generator/config/stage/currentOp.yaml b/generator/config/stage/currentOp.yaml new file mode 100644 index 000000000..a07df0c48 --- /dev/null +++ b/generator/config/stage/currentOp.yaml @@ -0,0 +1,8 @@ +# $schema: ../schema.json +name: $currentOp +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/currentOp/' +type: + - stage +encode: object +description: | + Returns information on active and/or dormant operations for the MongoDB deployment. To run, use the db.aggregate() method. diff --git a/generator/config/stage/densify.yaml b/generator/config/stage/densify.yaml new file mode 100644 index 000000000..84977f34d --- /dev/null +++ b/generator/config/stage/densify.yaml @@ -0,0 +1,30 @@ +# $schema: ../schema.json +name: $densify +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/densify/' +type: + - stage +encode: object +description: | + Creates new documents in a sequence of documents where certain values in a field are missing. +arguments: + - + name: field + type: + - string # field name + description: | + The field to densify. The values of the specified field must either be all numeric values or all dates. + Documents that do not contain the specified field continue through the pipeline unmodified. + To specify a in an embedded document or in an array, use dot notation. + - + name: partitionByFields + type: + - array # of string + optional: true + description: | + The field(s) that will be used as the partition keys. + - + name: range + type: + - object # Range + description: | + Specification for range based densification. diff --git a/generator/config/stage/documents.yaml b/generator/config/stage/documents.yaml new file mode 100644 index 000000000..acfdfaa9e --- /dev/null +++ b/generator/config/stage/documents.yaml @@ -0,0 +1,19 @@ +# $schema: ../schema.json +name: $documents +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/documents/' +type: + - stage +encode: single +description: | + Returns literal documents from input values. +arguments: + - + name: documents + type: + - resolvesToArray # of object + description: | + $documents accepts any valid expression that resolves to an array of objects. This includes: + - system variables, such as $$NOW or $$SEARCH_META + - $let expressions + - variables in scope from $lookup expressions + Expressions that do not resolve to a current document, like $myField or $$ROOT, will result in an error. diff --git a/generator/config/stage/facet.yaml b/generator/config/stage/facet.yaml new file mode 100644 index 000000000..f82922793 --- /dev/null +++ b/generator/config/stage/facet.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $facet +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/facet/' +type: + - stage +encode: single +description: | + Processes multiple aggregation pipelines within a single stage on the same set of input documents. Enables the creation of multi-faceted aggregations capable of characterizing data across multiple dimensions, or facets, in a single stage. +arguments: + - + name: facet + type: + - pipeline + variadic: object diff --git a/generator/config/stage/fill.yaml b/generator/config/stage/fill.yaml new file mode 100644 index 000000000..5ad0ed993 --- /dev/null +++ b/generator/config/stage/fill.yaml @@ -0,0 +1,42 @@ +# $schema: ../schema.json +name: $fill +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/' +type: + - stage +encode: object +description: | + Populates null and missing field values within documents. +arguments: + - + name: partitionBy + type: + - object # of expression + - string + optional: true + description: | + Specifies an expression to group the documents. In the $fill stage, a group of documents is known as a partition. + If you omit partitionBy and partitionByFields, $fill uses one partition for the entire collection. + partitionBy and partitionByFields are mutually exclusive. + - + name: partitionByFields + type: + - array # of string + optional: true + description: | + Specifies an array of fields as the compound key to group the documents. In the $fill stage, each group of documents is known as a partition. + If you omit partitionBy and partitionByFields, $fill uses one partition for the entire collection. + partitionBy and partitionByFields are mutually exclusive. + - + name: sortBy + type: + - object # SortSpec + optional: true + description: | + Specifies the field or fields to sort the documents within each partition. Uses the same syntax as the $sort stage. + - + name: output + type: + - object # of object{value:expression} or object{method:string}> + description: | + Specifies an object containing each field for which to fill missing values. You can specify multiple fields in the output object. + The object name is the name of the field to fill. The object value specifies how the field is filled. diff --git a/generator/config/stage/geoNear.yaml b/generator/config/stage/geoNear.yaml new file mode 100644 index 000000000..95d7070ca --- /dev/null +++ b/generator/config/stage/geoNear.yaml @@ -0,0 +1,76 @@ +# $schema: ../schema.json +name: $geoNear +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/' +type: + - stage +encode: object +description: | + Returns an ordered stream of documents based on the proximity to a geospatial point. Incorporates the functionality of $match, $sort, and $limit for geospatial data. The output documents include an additional distance field and can include a location identifier field. +arguments: + - + name: distanceField + type: + - string + description: | + The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. + - + name: distanceMultiplier + type: + - number + optional: true + description: | + The factor to multiply all distances returned by the query. For example, use the distanceMultiplier to convert radians, as returned by a spherical query, to kilometers by multiplying by the radius of the Earth. + - + name: includeLocs + type: + - string + optional: true + description: | + This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. + - + name: key + type: + - string + optional: true + description: | + Specify the geospatial indexed field to use when calculating the distance. + - + name: maxDistance + type: + - number + optional: true + description: | + The maximum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall within the specified distance from the center point. + Specify the distance in meters if the specified point is GeoJSON and in radians if the specified point is legacy coordinate pairs. + - + name: minDistance + type: + - number + optional: true + description: | + The minimum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall outside the specified distance from the center point. + Specify the distance in meters for GeoJSON data and in radians for legacy coordinate pairs. + - + name: near + type: + - object # GeoPoint + description: | + The point for which to find the closest documents. + - + name: query + type: + - query + optional: true + description: | + Limits the results to the documents that match the query. The query syntax is the usual MongoDB read operation query syntax. + You cannot specify a $near predicate in the query field of the $geoNear stage. + - + name: spherical + type: + - bool + optional: true + description: | + Determines how MongoDB calculates the distance between two points: + - When true, MongoDB uses $nearSphere semantics and calculates distances using spherical geometry. + - When false, MongoDB uses $near semantics: spherical geometry for 2dsphere indexes and planar geometry for 2d indexes. + Default: false. diff --git a/generator/config/stage/graphLookup.yaml b/generator/config/stage/graphLookup.yaml new file mode 100644 index 000000000..48d62e780 --- /dev/null +++ b/generator/config/stage/graphLookup.yaml @@ -0,0 +1,62 @@ +# $schema: ../schema.json +name: $graphLookup +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/' +type: + - stage +encode: object +description: | + Performs a recursive search on a collection. To each output document, adds a new array field that contains the traversal results of the recursive search for that document. +arguments: + - + name: from + type: + - string + description: | + Target collection for the $graphLookup operation to search, recursively matching the connectFromField to the connectToField. The from collection must be in the same database as any other collections used in the operation. + Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. + - + name: startWith + type: + - expression + - array + description: | + Expression that specifies the value of the connectFromField with which to start the recursive search. Optionally, startWith may be array of values, each of which is individually followed through the traversal process. + - + name: connectFromField + type: + - string + description: | + Field name whose value $graphLookup uses to recursively match against the connectToField of other documents in the collection. If the value is an array, each element is individually followed through the traversal process. + - + name: connectToField + type: + - string + description: | + Field name in other documents against which to match the value of the field specified by the connectFromField parameter. + - + name: as + type: + - string + description: | + Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. + - + name: maxDepth + type: + - int + optional: true + description: | + Non-negative integral number specifying the maximum recursion depth. + - + name: depthField + type: + - string + optional: true + description: | + Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. + - + name: restrictSearchWithMatch + type: + - query + optional: true + description: | + A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. diff --git a/generator/config/stage/group.yaml b/generator/config/stage/group.yaml new file mode 100644 index 000000000..d5599661a --- /dev/null +++ b/generator/config/stage/group.yaml @@ -0,0 +1,22 @@ +# $schema: ../schema.json +name: $group +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/' +type: + - stage +encode: group +description: | + Groups input documents by a specified identifier expression and applies the accumulator expression(s), if specified, to each group. Consumes all input documents and outputs one document per each distinct group. The output documents only contain the identifier field and, if specified, accumulated fields. +arguments: + - + name: _id + type: + - expression + description: | + The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. + - + name: field + type: + - accumulator + variadic: object + description: | + Computed using the accumulator operators. diff --git a/generator/config/stage/indexStats.yaml b/generator/config/stage/indexStats.yaml new file mode 100644 index 000000000..fa3ea2f34 --- /dev/null +++ b/generator/config/stage/indexStats.yaml @@ -0,0 +1,8 @@ +# $schema: ../schema.json +name: $indexStats +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexStats/' +type: + - stage +encode: object +description: | + Returns statistics regarding the use of each index for the collection. diff --git a/generator/config/stage/limit.yaml b/generator/config/stage/limit.yaml new file mode 100644 index 000000000..bceb24d47 --- /dev/null +++ b/generator/config/stage/limit.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $limit +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/limit/' +type: + - stage +encode: single +description: | + Passes the first n documents unmodified to the pipeline where n is the specified limit. For each input document, outputs either one document (for the first n documents) or zero documents (after the first n documents). +arguments: + - + name: limit + type: + - int diff --git a/generator/config/stage/listLocalSessions.yaml b/generator/config/stage/listLocalSessions.yaml new file mode 100644 index 000000000..314dff092 --- /dev/null +++ b/generator/config/stage/listLocalSessions.yaml @@ -0,0 +1,23 @@ +# $schema: ../schema.json +name: $listLocalSessions +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listLocalSessions/' +type: + - stage +encode: object +description: | + Lists all active sessions recently in use on the currently connected mongos or mongod instance. These sessions may have not yet propagated to the system.sessions collection. +arguments: + - + name: users + type: + - array + optional: true + description: | + Returns all sessions for the specified users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster to list sessions for other users. + - + name: allUsers + type: + - bool + optional: true + description: | + Returns all sessions for all users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster. diff --git a/generator/config/stage/listSampledQueries.yaml b/generator/config/stage/listSampledQueries.yaml new file mode 100644 index 000000000..c09e9c318 --- /dev/null +++ b/generator/config/stage/listSampledQueries.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $listSampledQueries +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSampledQueries/' +type: + - stage +encode: object +description: | + Lists sampled queries for all collections or a specific collection. +arguments: + - + name: namespace + type: + - string + optional: true diff --git a/generator/config/stage/listSearchIndexes.yaml b/generator/config/stage/listSearchIndexes.yaml new file mode 100644 index 000000000..5439b3247 --- /dev/null +++ b/generator/config/stage/listSearchIndexes.yaml @@ -0,0 +1,23 @@ +# $schema: ../schema.json +name: $listSearchIndexes +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/' +type: + - stage +encode: object +description: | + Returns information about existing Atlas Search indexes on a specified collection. +arguments: + - + name: id + type: + - string + optional: true + description: | + The id of the index to return information about. + - + name: name + type: + - string + optional: true + description: | + The name of the index to return information about. diff --git a/generator/config/stage/listSessions.yaml b/generator/config/stage/listSessions.yaml new file mode 100644 index 000000000..eaf3b47c0 --- /dev/null +++ b/generator/config/stage/listSessions.yaml @@ -0,0 +1,23 @@ +# $schema: ../schema.json +name: $listSessions +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSessions/' +type: + - stage +encode: object +description: | + Lists all sessions that have been active long enough to propagate to the system.sessions collection. +arguments: + - + name: users + type: + - array + optional: true + description: | + Returns all sessions for the specified users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster to list sessions for other users. + - + name: allUsers + type: + - bool + optional: true + description: | + Returns all sessions for all users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster. diff --git a/generator/config/stage/lookup.yaml b/generator/config/stage/lookup.yaml new file mode 100644 index 000000000..5a1b407fb --- /dev/null +++ b/generator/config/stage/lookup.yaml @@ -0,0 +1,54 @@ +# $schema: ../schema.json +name: $lookup +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/' +type: + - stage +encode: object +description: | + Performs a left outer join to another collection in the same database to filter in documents from the "joined" collection for processing. +arguments: + - + name: from + type: + - string + optional: true + description: | + Specifies the collection in the same database to perform the join with. + from is optional, you can use a $documents stage in a $lookup stage instead. For an example, see Use a $documents Stage in a $lookup Stage. + Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. + - + name: localField + type: + - string + optional: true + description: | + Specifies the field from the documents input to the $lookup stage. $lookup performs an equality match on the localField to the foreignField from the documents of the from collection. If an input document does not contain the localField, the $lookup treats the field as having a value of null for matching purposes. + - + name: foreignField + type: + - string + optional: true + description: | + Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. + - + name: let + type: + - object # of expression + optional: true + description: | + Specifies variables to use in the pipeline stages. Use the variable expressions to access the fields from the joined collection's documents that are input to the pipeline. + - + name: pipeline + type: + - pipeline + optional: true + description: | + Specifies the pipeline to run on the joined collection. The pipeline determines the resulting documents from the joined collection. To return all documents, specify an empty pipeline []. + The pipeline cannot include the $out stage or the $mergestage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. + The pipeline cannot directly access the joined document fields. Instead, define variables for the joined document fields using the let option and then reference the variables in the pipeline stages. + - + name: as + type: + - string + description: | + Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten. diff --git a/generator/config/stage/match.yaml b/generator/config/stage/match.yaml new file mode 100644 index 000000000..a5196e65c --- /dev/null +++ b/generator/config/stage/match.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $match +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/' +type: + - stage +encode: single +description: | + Filters the document stream to allow only matching documents to pass unmodified into the next pipeline stage. $match uses standard MongoDB queries. For each input document, outputs either one document (a match) or zero documents (no match). +arguments: + - + name: query + type: + - query diff --git a/generator/config/stage/merge.yaml b/generator/config/stage/merge.yaml new file mode 100644 index 000000000..d54aa02d3 --- /dev/null +++ b/generator/config/stage/merge.yaml @@ -0,0 +1,46 @@ +# $schema: ../schema.json +name: $merge +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/' +type: + - stage +encode: object +description: | + Writes the resulting documents of the aggregation pipeline to a collection. The stage can incorporate (insert new documents, merge documents, replace documents, keep existing documents, fail the operation, process documents with a custom update pipeline) the results into an output collection. To use the $merge stage, it must be the last stage in the pipeline. + New in MongoDB 4.2. +arguments: + - + name: into + type: + - string + #- OutCollection + description: | + The output collection. + - + name: 'on' + type: + - string + - array # of string + optional: true + description: | + Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. + - + name: let + type: + - object + optional: true + description: | + Specifies variables for use in the whenMatched pipeline. + - + name: whenMatched + type: + - string # WhenMatched + optional: true + description: | + The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). + - + name: whenNotMatched + type: + - WhenNotMatched + optional: true + description: | + The behavior of $merge if a result document does not match an existing document in the out collection. diff --git a/generator/config/stage/out.yaml b/generator/config/stage/out.yaml new file mode 100644 index 000000000..5675a4b65 --- /dev/null +++ b/generator/config/stage/out.yaml @@ -0,0 +1,28 @@ +# $schema: ../schema.json +name: $out +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/' +type: + - stage +encode: object +description: | + Writes the resulting documents of the aggregation pipeline to a collection. To use the $out stage, it must be the last stage in the pipeline. +arguments: + - + name: db + type: + - string + description: | + Target collection name to write documents from $out to. + - + name: coll + type: + - string + description: | + Target database name to write documents from $out to. + - + name: timeseries + type: + - object + optional: true + description: | + If set, the aggregation stage will use these options to create or replace a time-series collection in the given namespace. diff --git a/generator/config/stage/planCacheStats.yaml b/generator/config/stage/planCacheStats.yaml new file mode 100644 index 000000000..b5edf8e58 --- /dev/null +++ b/generator/config/stage/planCacheStats.yaml @@ -0,0 +1,8 @@ +# $schema: ../schema.json +name: $planCacheStats +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/planCacheStats/' +type: + - stage +encode: object +description: | + Returns plan cache information for a collection. diff --git a/generator/config/stage/project.yaml b/generator/config/stage/project.yaml new file mode 100644 index 000000000..ee82ea070 --- /dev/null +++ b/generator/config/stage/project.yaml @@ -0,0 +1,18 @@ +# $schema: ../schema.json +name: $project +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/' +type: + - stage +encode: single +description: | + Reshapes each document in the stream, such as by adding new fields or removing existing fields. For each input document, outputs one document. +arguments: + - + name: specification + type: + - bool + - int + - projection + - resolvesToBool + - object + variadic: object diff --git a/generator/config/stage/redact.yaml b/generator/config/stage/redact.yaml new file mode 100644 index 000000000..8d541ae2f --- /dev/null +++ b/generator/config/stage/redact.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $redact +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/' +type: + - stage +encode: single +description: | + Reshapes each document in the stream by restricting the content for each document based on information stored in the documents themselves. Incorporates the functionality of $project and $match. Can be used to implement field level redaction. For each input document, outputs either one or zero documents. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/stage/replaceRoot.yaml b/generator/config/stage/replaceRoot.yaml new file mode 100644 index 000000000..5fa0036f1 --- /dev/null +++ b/generator/config/stage/replaceRoot.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $replaceRoot +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/' +type: + - stage +encode: object +description: | + Replaces a document with the specified embedded document. The operation replaces all existing fields in the input document, including the _id field. Specify a document embedded in the input document to promote the embedded document to the top level. +arguments: + - + name: newRoot + type: + - resolvesToObject diff --git a/generator/config/stage/replaceWith.yaml b/generator/config/stage/replaceWith.yaml new file mode 100644 index 000000000..c87e43b83 --- /dev/null +++ b/generator/config/stage/replaceWith.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $replaceWith +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/' +type: + - stage +encode: single +description: | + Replaces a document with the specified embedded document. The operation replaces all existing fields in the input document, including the _id field. Specify a document embedded in the input document to promote the embedded document to the top level. + Alias for $replaceRoot. +arguments: + - + name: expression + type: + - resolvesToObject diff --git a/generator/config/stage/sample.yaml b/generator/config/stage/sample.yaml new file mode 100644 index 000000000..c2b613eed --- /dev/null +++ b/generator/config/stage/sample.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $sample +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/' +type: + - stage +encode: object +description: | + Randomly selects the specified number of documents from its input. +arguments: + - + name: size + type: + - int + description: | + The number of documents to randomly select. diff --git a/generator/config/stage/search.yaml b/generator/config/stage/search.yaml new file mode 100644 index 000000000..54b053425 --- /dev/null +++ b/generator/config/stage/search.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $search +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/search/' +type: + - stage +encode: single +description: | + Performs a full-text search of the field or fields in an Atlas collection. + NOTE: $search is only available for MongoDB Atlas clusters, and is not available for self-managed deployments. +arguments: + - + name: search + type: + - object diff --git a/generator/config/stage/searchMeta.yaml b/generator/config/stage/searchMeta.yaml new file mode 100644 index 000000000..80d28c11f --- /dev/null +++ b/generator/config/stage/searchMeta.yaml @@ -0,0 +1,14 @@ +# $schema: ../schema.json +name: $searchMeta +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/searchMeta/' +type: + - stage +encode: single +description: | + Returns different types of metadata result documents for the Atlas Search query against an Atlas collection. + NOTE: $searchMeta is only available for MongoDB Atlas clusters running MongoDB v4.4.9 or higher, and is not available for self-managed deployments. +arguments: + - + name: meta + type: + - object diff --git a/generator/config/stage/set.yaml b/generator/config/stage/set.yaml new file mode 100644 index 000000000..1955df4ff --- /dev/null +++ b/generator/config/stage/set.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $set +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/' +type: + - stage +encode: single +description: | + Adds new fields to documents. Outputs documents that contain all existing fields from the input documents and newly added fields. + Alias for $addFields. +arguments: + - + name: field + type: + - expression + variadic: object diff --git a/generator/config/stage/setWindowFields.yaml b/generator/config/stage/setWindowFields.yaml new file mode 100644 index 000000000..df8bb52bd --- /dev/null +++ b/generator/config/stage/setWindowFields.yaml @@ -0,0 +1,36 @@ +# $schema: ../schema.json +name: $setWindowFields +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/' +type: + - stage +encode: object +description: | + Groups documents into windows and applies one or more operators to the documents in each window. + New in MongoDB 5.0. +arguments: + - + name: partitionBy + type: + - expression + description: | + Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. + - + name: sortBy + type: + - SortSpec + description: | + Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. + - + name: output + type: + - object + description: | + Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. + A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. + - + name: window + type: + - Window + optional: true + description: | + Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. diff --git a/generator/config/stage/shardedDataDistribution.yaml b/generator/config/stage/shardedDataDistribution.yaml new file mode 100644 index 000000000..8cf84660e --- /dev/null +++ b/generator/config/stage/shardedDataDistribution.yaml @@ -0,0 +1,9 @@ +# $schema: ../schema.json +name: $shardedDataDistribution +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/shardedDataDistribution/' +type: + - stage +encode: object +description: | + Provides data and size distribution information on sharded collections. + New in MongoDB 6.0.3. diff --git a/generator/config/stage/skip.yaml b/generator/config/stage/skip.yaml new file mode 100644 index 000000000..8b0118454 --- /dev/null +++ b/generator/config/stage/skip.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $skip +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/skip/' +type: + - stage +encode: single +description: | + Skips the first n documents where n is the specified skip number and passes the remaining documents unmodified to the pipeline. For each input document, outputs either zero documents (for the first n documents) or one document (if after the first n documents). +arguments: + - + name: skip + type: + - int diff --git a/generator/config/stage/sort.yaml b/generator/config/stage/sort.yaml new file mode 100644 index 000000000..d0700ff22 --- /dev/null +++ b/generator/config/stage/sort.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $sort +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/' +type: + - stage +encode: single +description: | + Reorders the document stream by a specified sort key. Only the order changes; the documents remain unmodified. For each input document, outputs one document. +arguments: + - + name: sort + type: + - SortSpec diff --git a/generator/config/stage/sortByCount.yaml b/generator/config/stage/sortByCount.yaml new file mode 100644 index 000000000..c102323f6 --- /dev/null +++ b/generator/config/stage/sortByCount.yaml @@ -0,0 +1,13 @@ +# $schema: ../schema.json +name: $sortByCount +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortByCount/' +type: + - stage +encode: object +description: | + Groups incoming documents based on the value of a specified expression, then computes the count of documents in each distinct group. +arguments: + - + name: expression + type: + - expression diff --git a/generator/config/stage/unionWith.yaml b/generator/config/stage/unionWith.yaml new file mode 100644 index 000000000..ebf8f903f --- /dev/null +++ b/generator/config/stage/unionWith.yaml @@ -0,0 +1,24 @@ +# $schema: ../schema.json +name: $unionWith +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unionWith/' +type: + - stage +encode: object +description: | + Performs a union of two collections; i.e. combines pipeline results from two collections into a single result set. + New in MongoDB 4.4. +arguments: + - + name: coll + type: + - string + description: | + The collection or view whose pipeline results you wish to include in the result set. + - + name: pipeline + type: + - pipeline + optional: true + description: | + An aggregation pipeline to apply to the specified coll. + The pipeline cannot include the $out and $merge stages. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. diff --git a/generator/config/stage/unset.yaml b/generator/config/stage/unset.yaml new file mode 100644 index 000000000..848eed3ca --- /dev/null +++ b/generator/config/stage/unset.yaml @@ -0,0 +1,15 @@ +# $schema: ../schema.json +name: $unset +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/' +type: + - stage +encode: single +description: | + Removes or excludes fields from documents. + Alias for $project stage that removes or excludes fields. +arguments: + - + name: field + type: + - fieldPath + variadic: array diff --git a/generator/config/stage/unwind.yaml b/generator/config/stage/unwind.yaml new file mode 100644 index 000000000..8fd255971 --- /dev/null +++ b/generator/config/stage/unwind.yaml @@ -0,0 +1,31 @@ +# $schema: ../schema.json +name: $unwind +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/' +type: + - stage +encode: object +description: | + Deconstructs an array field from the input documents to output a document for each element. Each output document replaces the array with an element value. For each input document, outputs n documents where n is the number of array elements and can be zero for an empty array. +arguments: + - + name: path + type: + - arrayFieldPath + description: | + Field path to an array field. + - + name: includeArrayIndex + type: + - string + optional: true + description: | + The name of a new field to hold the array index of the element. The name cannot start with a dollar sign $. + - + name: preserveNullAndEmptyArrays + type: + - bool + optional: true + description: | + If true, if the path is null, missing, or an empty array, $unwind outputs the document. + If false, if path is null, missing, or an empty array, $unwind does not output a document. + The default value is false. diff --git a/generator/generate b/generator/generate new file mode 100755 index 000000000..d67515b70 --- /dev/null +++ b/generator/generate @@ -0,0 +1,17 @@ +#!/usr/bin/env php +add(new GenerateCommand(__DIR__ . '/../', __DIR__ . '/config')); +$application->setDefaultCommand('generate'); +$application->run(); diff --git a/generator/src/AbstractGenerator.php b/generator/src/AbstractGenerator.php new file mode 100644 index 000000000..17fb61eb3 --- /dev/null +++ b/generator/src/AbstractGenerator.php @@ -0,0 +1,95 @@ +printer = new PsrPrinter(); + } + + /** + * Split the namespace and class name from a fully qualified class name. + * + * @return array{0: string, 1: string} + */ + final protected function splitNamespaceAndClassName(string $fqcn): array + { + $parts = explode('\\', ltrim($fqcn, '\\')); + $className = array_pop($parts); + + return [implode('\\', $parts), $className]; + } + + final protected function writeFile(PhpNamespace $namespace): void + { + $classes = $namespace->getClasses(); + assert(count($classes) === 1, sprintf('Expected exactly one class in namespace "%s", got %d.', $namespace->getName(), count($classes))); + + $filename = $this->rootDir . $this->getFileName($namespace->getName(), current($classes)->getName()); + + $dirname = dirname($filename); + if (! is_dir($dirname)) { + mkdir($dirname, 0755, true); + } + + $file = new PhpFile(); + $file->setStrictTypes(); + $file->setComment('THIS FILE IS AUTO-GENERATED. ANY CHANGES WILL BE LOST!'); + $file->addNamespace($namespace); + + file_put_contents($filename, $this->printer->printFile($file)); + } + + /** + * Thanks to PSR-4, the file name can be determined from the fully qualified class name. + * + * @param string ...$fqcn Fully qualified class name, merged if multiple parts + * + * @return string File name relative to the root directory + */ + private function getFileName(string ...$fqcn): string + { + $fqcn = implode('\\', $fqcn); + + // Config from composer.json autoload + $config = [ + 'MongoDB\\Tests\\' => 'tests/', + 'MongoDB\\' => 'src/', + ]; + foreach ($config as $namespace => $directory) { + if (str_starts_with($fqcn, $namespace)) { + return $directory . str_replace([$namespace, '\\'], ['', '/'], $fqcn) . '.php'; + } + } + + throw new InvalidArgumentException(sprintf('Could not determine file name for "%s"', $fqcn)); + } +} diff --git a/generator/src/Command/GenerateCommand.php b/generator/src/Command/GenerateCommand.php new file mode 100644 index 000000000..78482963e --- /dev/null +++ b/generator/src/Command/GenerateCommand.php @@ -0,0 +1,90 @@ +setName('generate'); + $this->setDescription('Generate code for mongodb/mongodb library'); + $this->setHelp('Generate code for mongodb/mongodb library'); + } + + public function execute(InputInterface $input, OutputInterface $output): int + { + $output->writeln('Generating code for mongodb/mongodb library'); + + $expressions = $this->generateExpressionClasses($output); + $this->generateOperatorClasses($expressions, $output); + + return Command::SUCCESS; + } + + /** @return array */ + private function generateExpressionClasses(OutputInterface $output): array + { + $output->writeln('Generating expression classes'); + + $config = require $this->configDir . '/expressions.php'; + assert(is_array($config)); + + $definitions = []; + $generator = new ExpressionClassGenerator($this->rootDir); + foreach ($config as $name => $def) { + assert(is_array($def)); + assert(! array_key_exists($name, $definitions), sprintf('Duplicate expression name "%s".', $name)); + $definitions[$name] = $def = new ExpressionDefinition($name, ...$def); + $generator->generate($def); + } + + $generator = new ExpressionFactoryGenerator($this->rootDir); + $generator->generate($definitions); + + return $definitions; + } + + /** @param array $expressions */ + private function generateOperatorClasses(array $expressions, OutputInterface $output): void + { + $config = require $this->configDir . '/definitions.php'; + assert(is_array($config)); + + foreach ($config as $def) { + assert(is_array($def)); + $definition = new GeneratorDefinition(...$def); + + foreach ($definition->generators as $generatorClass) { + $output->writeln(sprintf('Generating classes for %s with %s', basename($definition->configFiles), $generatorClass)); + assert(is_a($generatorClass, OperatorGenerator::class, true)); + $generator = new $generatorClass($this->rootDir, $expressions); + $generator->generate($definition); + } + } + } +} diff --git a/generator/src/Definition/ArgumentDefinition.php b/generator/src/Definition/ArgumentDefinition.php new file mode 100644 index 000000000..4b03ba966 --- /dev/null +++ b/generator/src/Definition/ArgumentDefinition.php @@ -0,0 +1,47 @@ + $type */ + public array $type, + public string|null $description = null, + public bool $optional = false, + string|null $variadic = null, + int|null $variadicMin = null, + ) { + if (is_array($type)) { + assert(array_is_list($type), 'Type must be a list or a single string'); + foreach ($type as $t) { + assert(is_string($t), sprintf('Type must be a list of strings. Got %s', get_debug_type($type))); + } + } + + if ($variadic) { + $this->variadic = VariadicType::from($variadic); + if ($variadicMin === null) { + $this->variadicMin = $optional ? 0 : 1; + } else { + $this->variadicMin = $variadicMin; + } + } else { + $this->variadic = null; + $this->variadicMin = null; + } + } +} diff --git a/generator/src/Definition/ExpressionDefinition.php b/generator/src/Definition/ExpressionDefinition.php new file mode 100644 index 000000000..cbe37febc --- /dev/null +++ b/generator/src/Definition/ExpressionDefinition.php @@ -0,0 +1,37 @@ + */ + public array $acceptedTypes, + /** Interface to implement for operators that resolve to this type. Generated class/enum/interface. */ + public string|null $returnType = null, + public string|null $extends = null, + /** @var list */ + public array $implements = [], + public array $values = [], + public PhpObject|null $generate = null, + ) { + assert($generate === PhpObject::PhpClass || ! $extends, $name . ': Cannot specify "extends" when "generate" is not "class"'); + assert($generate === PhpObject::PhpEnum || ! $this->values, $name . ': Cannot specify "values" when "generate" is not "enum"'); + //assert($returnType === null || interface_exists($returnType), $name . ': Return type must be an interface'); + + foreach ($acceptedTypes as $acceptedType) { + assert(is_string($acceptedType), $name . ': AcceptedTypes must be an array of strings.'); + } + + if ($generate) { + $this->returnType = 'MongoDB\\Builder\\Expression\\' . ucfirst($this->name); + } + } +} diff --git a/generator/src/Definition/GeneratorDefinition.php b/generator/src/Definition/GeneratorDefinition.php new file mode 100644 index 000000000..2faa2fac3 --- /dev/null +++ b/generator/src/Definition/GeneratorDefinition.php @@ -0,0 +1,42 @@ +> */ + public array $generators, + public string $namespace, + public string $classNameSuffix = '', + public array $interfaces = [], + public string|null $parentClass = null, + ) { + assert(str_starts_with($namespace, 'MongoDB\\'), sprintf('Namespace must start with "MongoDB\\". Got "%s"', $namespace)); + assert(! str_ends_with($namespace, '\\'), sprintf('Namespace must not end with "\\". Got "%s"', $namespace)); + + assert(array_is_list($interfaces), 'Generators must be a list of class names'); + foreach ($interfaces as $interface) { + assert(is_string($interface) && class_exists($interface), sprintf('Interface "%s" does not exist', $interface)); + } + + assert(array_is_list($generators), 'Generators must be a list of class names'); + foreach ($generators as $class) { + assert(is_string($class) && is_subclass_of($class, OperatorGenerator::class), sprintf('Generator class "%s" must extend "%s"', $class, OperatorGenerator::class)); + } + } +} diff --git a/generator/src/Definition/OperatorDefinition.php b/generator/src/Definition/OperatorDefinition.php new file mode 100644 index 000000000..50ed595df --- /dev/null +++ b/generator/src/Definition/OperatorDefinition.php @@ -0,0 +1,58 @@ + */ + public array $arguments; + + public function __construct( + public string $name, + public string $link, + string $encode, + public array $type, + public string|null $description = null, + array $arguments = [], + ) { + $this->encode = match ($encode) { + 'single' => Encode::Single, + 'array' => Encode::Array, + 'object' => Encode::Object, + 'group' => Encode::Group, + default => throw new UnexpectedValueException(sprintf('Unexpected "encode" value for operator "%s". Got "%s"', $name, $encode)), + }; + + // Convert arguments to ArgumentDefinition objects + // Optional arguments must be after required arguments + $requiredArgs = $optionalArgs = []; + foreach ($arguments as $arg) { + $arg = new ArgumentDefinition(...$arg); + if ($arg->optional) { + $optionalArgs[] = $arg; + } else { + $requiredArgs[] = $arg; + } + } + + // "single" encode operators must have one required argument + if ($this->encode === Encode::Single) { + assert(count($requiredArgs) === 1, sprintf('Single encode operator "%s" must have one argument', $name)); + assert(count($optionalArgs) === 0, sprintf('Single encode operator "%s" argument cannot be optional', $name)); + } + + $this->arguments = array_merge($requiredArgs, $optionalArgs); + } +} diff --git a/generator/src/Definition/PhpObject.php b/generator/src/Definition/PhpObject.php new file mode 100644 index 000000000..5e815e0b6 --- /dev/null +++ b/generator/src/Definition/PhpObject.php @@ -0,0 +1,15 @@ + */ + public function read(string $dirname): array + { + $finder = new Finder(); + $finder->files()->in($dirname)->name('*.yaml')->sortByName(); + + $definitions = []; + foreach ($finder as $file) { + $operator = Yaml::parseFile($file->getPathname()); + assert(is_array($operator)); + $definitions[] = new OperatorDefinition(...$operator); + } + + return $definitions; + } +} diff --git a/generator/src/ExpressionClassGenerator.php b/generator/src/ExpressionClassGenerator.php new file mode 100644 index 000000000..44d549c14 --- /dev/null +++ b/generator/src/ExpressionClassGenerator.php @@ -0,0 +1,84 @@ +generate) { + return; + } + + try { + $this->writeFile($this->createClassOrInterface($definition)); + } catch (Throwable $e) { + throw new RuntimeException('Failed to generate expression class for ' . $definition->name, 0, $e); + } + } + + public function createClassOrInterface(ExpressionDefinition $definition): PhpNamespace + { + [$namespace, $className] = $this->splitNamespaceAndClassName($definition->returnType); + $namespace = new PhpNamespace($namespace); + foreach ($definition->implements as $interface) { + $namespace->addUse($interface); + } + + $types = array_map( + fn (string $type): string => match ($type) { + 'list' => 'array', + default => $type, + }, + $definition->acceptedTypes, + ); + + if ($definition->generate === PhpObject::PhpClass) { + $class = $namespace->addClass($className); + $class->setImplements($definition->implements); + $class->setExtends($definition->extends); + + // Replace with promoted property in PHP 8.1 + $propertyType = Type::union(...$types); + $class->addProperty('name') + ->setType($propertyType) + ->setReadOnly() + ->setPublic(); + + $constructor = $class->addMethod('__construct'); + $constructor->addParameter('name')->setType($propertyType); + $constructor->addBody('$this->name = $name;'); + } elseif ($definition->generate === PhpObject::PhpInterface) { + $interface = $namespace->addInterface($className); + $interface->setExtends($definition->implements); + } elseif ($definition->generate === PhpObject::PhpEnum) { + $enum = $namespace->addEnum($className); + $enum->setType('string'); + array_map( + fn (string $case) => $enum->addCase(ucfirst($case), $case), + $definition->values, + ); + } else { + throw new LogicException('Unknown generate type: ' . var_export($definition->generate, true)); + } + + return $namespace; + } +} diff --git a/generator/src/ExpressionFactoryGenerator.php b/generator/src/ExpressionFactoryGenerator.php new file mode 100644 index 000000000..b9ccdc698 --- /dev/null +++ b/generator/src/ExpressionFactoryGenerator.php @@ -0,0 +1,49 @@ + $definitions */ + public function generate(array $expressions): void + { + $this->writeFile($this->createFactoryClass($expressions)); + } + + /** @param array $expressions */ + private function createFactoryClass(array $expressions): PhpNamespace + { + $namespace = new PhpNamespace('MongoDB\\Builder\\Expression'); + $trait = $namespace->addTrait('ExpressionFactoryTrait'); + $trait->addComment('@internal'); + + // Pedantry requires methods to be ordered alphabetically + usort($expressions, fn (ExpressionDefinition $a, ExpressionDefinition $b) => $a->name <=> $b->name); + + foreach ($expressions as $expression) { + if ($expression->generate !== PhpObject::PhpClass) { + continue; + } + + $namespace->addUse($expression->returnType); + $expressionShortClassName = $this->splitNamespaceAndClassName($expression->returnType)[1]; + + $method = $trait->addMethod(lcfirst($expressionShortClassName)); + $method->setStatic(); + $method->addParameter('name')->setType('string'); + $method->addBody('return new ' . $expressionShortClassName . '($name);'); + $method->setReturnType($expression->returnType); + } + + return $namespace; + } +} diff --git a/generator/src/OperatorClassGenerator.php b/generator/src/OperatorClassGenerator.php new file mode 100644 index 000000000..42aa6d109 --- /dev/null +++ b/generator/src/OperatorClassGenerator.php @@ -0,0 +1,177 @@ +getOperators($definition) as $operator) { + try { + $this->writeFile($this->createClass($definition, $operator)); + } catch (Throwable $e) { + throw new RuntimeException(sprintf('Failed to generate class for operator "%s"', $operator->name), 0, $e); + } + } + } + + public function createClass(GeneratorDefinition $definition, OperatorDefinition $operator): PhpNamespace + { + $namespace = new PhpNamespace($definition->namespace); + + $interfaces = $this->getInterfaces($operator); + foreach ($interfaces as $interface) { + $namespace->addUse($interface); + } + + $class = $namespace->addClass($this->getOperatorClassName($definition, $operator)); + $class->setImplements($interfaces); + $namespace->addUse(OperatorInterface::class); + $class->addImplement(OperatorInterface::class); + + // Expose operator metadata as constants + // @todo move to encoder class + $class->addComment($operator->description); + $class->addComment('@see ' . $operator->link); + $namespace->addUse(Encode::class); + $class->addConstant('ENCODE', new Literal('Encode::' . $operator->encode->name)); + + $constuctor = $class->addMethod('__construct'); + foreach ($operator->arguments as $argument) { + $type = $this->getAcceptedTypes($argument); + foreach ($type->use as $use) { + $namespace->addUse($use); + } + + $property = $class->addProperty($argument->name); + $property->setReadOnly(); + $constuctorParam = $constuctor->addParameter($argument->name); + $constuctorParam->setType($type->native); + + if ($argument->variadic) { + $constuctor->setVariadic(); + $constuctor->addComment('@param ' . $type->doc . ' ...$' . $argument->name . rtrim(' ' . $argument->description)); + + if ($argument->variadicMin !== null) { + $constuctor->addBody(<<name}) < {$argument->variadicMin}) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for \${$argument->name}, got %d.', {$argument->variadicMin}, \count(\${$argument->name}))); + } + PHP); + } + + if ($argument->variadic === VariadicType::Array) { + $property->setType('array'); + $property->addComment('@var list<' . $type->doc . '> ...$' . $argument->name . rtrim(' ' . $argument->description)); + // Warn that named arguments are not supported + // @see https://psalm.dev/docs/running_psalm/issues/NamedArgumentNotAllowed/ + $constuctor->addComment('@no-named-arguments'); + $namespace->addUseFunction('array_is_list'); + $namespace->addUse(InvalidArgumentException::class); + $constuctor->addBody(<<name})) { + throw new InvalidArgumentException('Expected \${$argument->name} arguments to be a list (array), named arguments are not supported'); + } + PHP); + } elseif ($argument->variadic === VariadicType::Object) { + $namespace->addUse(stdClass::class); + $property->setType(stdClass::class); + $property->addComment('@var stdClass<' . $type->doc . '> ...$' . $argument->name . rtrim(' ' . $argument->description)); + $namespace->addUseFunction('is_string'); + $namespace->addUse(InvalidArgumentException::class); + $constuctor->addBody(<<name} as \$key => \$value) { + if (! is_string(\$key)) { + throw new InvalidArgumentException('Expected \${$argument->name} arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); + } + } + \${$argument->name} = (object) \${$argument->name}; + PHP); + } + } else { + // Non-variadic arguments + $property->addComment('@var ' . $type->doc . ' $' . $argument->name . rtrim(' ' . $argument->description)); + $property->setType($type->native); + $constuctor->addComment('@param ' . $type->doc . ' $' . $argument->name . rtrim(' ' . $argument->description)); + + if ($argument->optional) { + // We use a special Optional::Undefined type to differentiate between null and undefined + $constuctorParam->setDefaultValue(new Literal('Optional::Undefined')); + } + + // List type must be validated with array_is_list() + if ($type->list) { + $namespace->addUseFunction('is_array'); + $namespace->addUseFunction('array_is_list'); + $namespace->addUse(InvalidArgumentException::class); + $constuctor->addBody(<<name}) && ! array_is_list(\${$argument->name})) { + throw new InvalidArgumentException('Expected \${$argument->name} argument to be a list, got an associative array.'); + } + + PHP); + } + + if ($type->query) { + $namespace->addUseFunction('is_array'); + $namespace->addUseFunction('is_object'); + $namespace->addUse(QueryObject::class); + $constuctor->addBody(<<name}) || is_object(\${$argument->name})) { + \${$argument->name} = QueryObject::create(...\${$argument->name}); + } + + PHP); + } + } + + // Set property from constructor argument + $constuctor->addBody('$this->' . $argument->name . ' = $' . $argument->name . ';'); + } + + $class->addMethod('getOperator') + ->setReturnType('string') + ->setBody('return ' . var_export($operator->name, true) . ';'); + + return $namespace; + } + + /** + * Operator classes interfaces are defined by their return type as a MongoDB expression. + */ + private function getInterfaces(OperatorDefinition $definition): array + { + $interfaces = []; + + foreach ($definition->type as $type) { + $interfaces[] = $interface = $this->getType($type)->returnType; + assert(interface_exists($interface), sprintf('"%s" is not an interface.', $interface)); + } + + return $interfaces; + } +} diff --git a/generator/src/OperatorFactoryGenerator.php b/generator/src/OperatorFactoryGenerator.php new file mode 100644 index 000000000..f04c56c40 --- /dev/null +++ b/generator/src/OperatorFactoryGenerator.php @@ -0,0 +1,87 @@ +writeFile($this->createFactoryTrait($definition)); + } + + private function createFactoryTrait(GeneratorDefinition $definition): PhpNamespace + { + $namespace = new PhpNamespace($definition->namespace); + $trait = $namespace->addTrait('FactoryTrait'); + $trait->addComment('@internal'); + + // Pedantry requires methods to be ordered alphabetically + $operators = $this->getOperators($definition); + usort($operators, fn (OperatorDefinition $a, OperatorDefinition $b) => strcasecmp($a->name, $b->name)); + + foreach ($operators as $operator) { + try { + $this->addMethod($definition, $operator, $namespace, $trait); + } catch (Throwable $e) { + throw new RuntimeException(sprintf('Failed to generate class for operator "%s"', $operator->name), 0, $e); + } + } + + return $namespace; + } + + private function addMethod(GeneratorDefinition $definition, OperatorDefinition $operator, PhpNamespace $namespace, TraitType $trait): void + { + $operatorClassName = '\\' . $definition->namespace . '\\' . $this->getOperatorClassName($definition, $operator); + $namespace->addUse($operatorClassName); + + $method = $trait->addMethod(ltrim($operator->name, '$')); + $method->setStatic(); + $method->addComment($operator->description); + $method->addComment('@see ' . $operator->link); + $args = []; + foreach ($operator->arguments as $argument) { + $type = $this->getAcceptedTypes($argument); + foreach ($type->use as $use) { + $namespace->addUse($use); + } + + $parameter = $method->addParameter($argument->name); + $parameter->setType($type->native); + if ($argument->variadic) { + $method->setVariadic(); + $method->addComment('@param ' . $type->doc . ' ...$' . $argument->name . rtrim(' ' . $argument->description)); + $args[] = '...$' . $argument->name; + } else { + if ($argument->optional) { + $parameter->setDefaultValue(new Literal('Optional::Undefined')); + } + + $method->addComment('@param ' . $type->doc . ' $' . $argument->name . rtrim(' ' . $argument->description)); + $args[] = '$' . $argument->name; + } + } + + $operatorShortClassName = ltrim(str_replace($definition->namespace, '', $operatorClassName), '\\'); + $method->addBody('return new ' . $operatorShortClassName . '(' . implode(', ', $args) . ');'); + $method->setReturnType($operatorClassName); + } +} diff --git a/generator/src/OperatorGenerator.php b/generator/src/OperatorGenerator.php new file mode 100644 index 000000000..5ce32d9e7 --- /dev/null +++ b/generator/src/OperatorGenerator.php @@ -0,0 +1,154 @@ + */ + private array $expressions, + ) { + parent::__construct($rootDir); + + $this->yamlReader = new YamlReader(); + } + + abstract public function generate(GeneratorDefinition $definition): void; + + final protected function getOperators(GeneratorDefinition $definition): array + { + // Remove unsupported operators + return array_filter( + $this->yamlReader->read($definition->configFiles), + fn (OperatorDefinition $operator): bool => ! in_array($operator->name, ['$'], true), + ); + } + + final protected function getOperatorClassName(GeneratorDefinition $definition, OperatorDefinition $operator): string + { + return ucfirst(ltrim($operator->name, '$')) . $definition->classNameSuffix; + } + + final protected function getType(string $type): ExpressionDefinition + { + assert(array_key_exists($type, $this->expressions), sprintf('Invalid expression type "%s".', $type)); + + return $this->expressions[$type]; + } + + /** + * Expression types can contain class names, interface, native types or "list". + * PHPDoc types are more precise than native types, so we use them systematically even if redundant. + * + * @return object{native:string,doc:string,use:list,list:bool,query:bool} + */ + final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass + { + $nativeTypes = []; + + foreach ($arg->type as $type) { + $type = $this->getType($type); + $nativeTypes = array_merge($nativeTypes, $type->acceptedTypes); + + if (isset($type->returnType)) { + $nativeTypes[] = $type->returnType; + } + } + + if ($arg->optional) { + $use[] = '\\' . Optional::class; + $nativeTypes[] = Optional::class; + } + + $docTypes = $nativeTypes = array_unique($nativeTypes); + $use = []; + + foreach ($nativeTypes as $key => $typeName) { + // strings cannot be empty + if ($typeName === 'string') { + $docTypes[$key] = 'non-empty-string'; + } + + if (interface_exists($typeName) || class_exists($typeName)) { + $use[] = $nativeTypes[$key] = '\\' . $typeName; + $docTypes[$key] = $this->splitNamespaceAndClassName($typeName)[1]; + // A union cannot contain both object and a class type + if (in_array('object', $nativeTypes, true)) { + unset($nativeTypes[$key]); + } + } + } + + // If an array is expected, but not an object, we can check for a list + $listCheck = in_array('\\' . PackedArray::class, $nativeTypes, true) + && ! in_array('\\' . Document::class, $nativeTypes, true); + + // If the argument is a query, we need to convert it to a QueryObject + $isQuery = in_array('query', $arg->type, true); + + // mixed can only be used as a standalone type + if (in_array('mixed', $nativeTypes, true)) { + $nativeTypes = ['mixed']; + } + + usort($nativeTypes, self::sortTypesCallback(...)); + usort($docTypes, self::sortTypesCallback(...)); + sort($use); + + return (object) [ + 'native' => Type::union(...array_unique($nativeTypes)), + 'doc' => Type::union(...array_unique($docTypes)), + 'use' => array_unique($use), + 'list' => $listCheck, + 'query' => $isQuery, + ]; + } + + /** + * usort() callback for sorting types. + * "Optional" is always first, for documentation of optional parameters, + * then types are sorted alphabetically. + */ + private static function sortTypesCallback(string $type1, string $type2): int + { + if ($type1 === 'Optional' || $type1 === '\\' . Optional::class) { + return -1; + } + + if ($type2 === 'Optional' || $type2 === '\\' . Optional::class) { + return 1; + } + + return $type1 <=> $type2; + } +} diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 000000000..e5f818658 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,89 @@ + + + + + + + + + + + + src + generator/src + generator/config + tests + + + src/Builder/(Accumulator|Expression|Query|Projection|Stage)/*\.php + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tests + + + tests + + + tests + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 000000000..2e696ee93 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + ./tests/ + + + diff --git a/psalm.xml.dist b/psalm.xml.dist new file mode 100644 index 000000000..c039b023a --- /dev/null +++ b/psalm.xml.dist @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/src/Builder/Accumulator.php b/src/Builder/Accumulator.php new file mode 100644 index 000000000..4748d824c --- /dev/null +++ b/src/Builder/Accumulator.php @@ -0,0 +1,32 @@ +init = $init; + $this->accumulate = $accumulate; + if (is_array($accumulateArgs) && ! array_is_list($accumulateArgs)) { + throw new InvalidArgumentException('Expected $accumulateArgs argument to be a list, got an associative array.'); + } + + $this->accumulateArgs = $accumulateArgs; + $this->merge = $merge; + $this->lang = $lang; + if (is_array($initArgs) && ! array_is_list($initArgs)) { + throw new InvalidArgumentException('Expected $initArgs argument to be a list, got an associative array.'); + } + + $this->initArgs = $initArgs; + $this->finalize = $finalize; + } + + public function getOperator(): string + { + return '$accumulator'; + } +} diff --git a/src/Builder/Accumulator/AddToSetAccumulator.php b/src/Builder/Accumulator/AddToSetAccumulator.php new file mode 100644 index 000000000..02a202990 --- /dev/null +++ b/src/Builder/Accumulator/AddToSetAccumulator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$addToSet'; + } +} diff --git a/src/Builder/Accumulator/AvgAccumulator.php b/src/Builder/Accumulator/AvgAccumulator.php new file mode 100644 index 000000000..da0b7730a --- /dev/null +++ b/src/Builder/Accumulator/AvgAccumulator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$avg'; + } +} diff --git a/src/Builder/Accumulator/BottomAccumulator.php b/src/Builder/Accumulator/BottomAccumulator.php new file mode 100644 index 000000000..b2337debd --- /dev/null +++ b/src/Builder/Accumulator/BottomAccumulator.php @@ -0,0 +1,53 @@ +sortBy = $sortBy; + $this->output = $output; + } + + public function getOperator(): string + { + return '$bottom'; + } +} diff --git a/src/Builder/Accumulator/BottomNAccumulator.php b/src/Builder/Accumulator/BottomNAccumulator.php new file mode 100644 index 000000000..004f6a019 --- /dev/null +++ b/src/Builder/Accumulator/BottomNAccumulator.php @@ -0,0 +1,61 @@ +n = $n; + $this->sortBy = $sortBy; + $this->output = $output; + } + + public function getOperator(): string + { + return '$bottomN'; + } +} diff --git a/src/Builder/Accumulator/CountAccumulator.php b/src/Builder/Accumulator/CountAccumulator.php new file mode 100644 index 000000000..384996417 --- /dev/null +++ b/src/Builder/Accumulator/CountAccumulator.php @@ -0,0 +1,35 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$covariancePop'; + } +} diff --git a/src/Builder/Accumulator/CovarianceSampAccumulator.php b/src/Builder/Accumulator/CovarianceSampAccumulator.php new file mode 100644 index 000000000..1266b263e --- /dev/null +++ b/src/Builder/Accumulator/CovarianceSampAccumulator.php @@ -0,0 +1,50 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$covarianceSamp'; + } +} diff --git a/src/Builder/Accumulator/DenseRankAccumulator.php b/src/Builder/Accumulator/DenseRankAccumulator.php new file mode 100644 index 000000000..7dea1de67 --- /dev/null +++ b/src/Builder/Accumulator/DenseRankAccumulator.php @@ -0,0 +1,33 @@ +input = $input; + $this->unit = $unit; + } + + public function getOperator(): string + { + return '$derivative'; + } +} diff --git a/src/Builder/Accumulator/DocumentNumberAccumulator.php b/src/Builder/Accumulator/DocumentNumberAccumulator.php new file mode 100644 index 000000000..4318dbd81 --- /dev/null +++ b/src/Builder/Accumulator/DocumentNumberAccumulator.php @@ -0,0 +1,33 @@ +input = $input; + $this->N = $N; + $this->alpha = $alpha; + } + + public function getOperator(): string + { + return '$expMovingAvg'; + } +} diff --git a/src/Builder/Accumulator/FactoryTrait.php b/src/Builder/Accumulator/FactoryTrait.php new file mode 100644 index 000000000..d0f5d8989 --- /dev/null +++ b/src/Builder/Accumulator/FactoryTrait.php @@ -0,0 +1,512 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$first'; + } +} diff --git a/src/Builder/Accumulator/FirstNAccumulator.php b/src/Builder/Accumulator/FirstNAccumulator.php new file mode 100644 index 000000000..fd76b0aff --- /dev/null +++ b/src/Builder/Accumulator/FirstNAccumulator.php @@ -0,0 +1,57 @@ +input = $input; + $this->n = $n; + } + + public function getOperator(): string + { + return '$firstN'; + } +} diff --git a/src/Builder/Accumulator/LastAccumulator.php b/src/Builder/Accumulator/LastAccumulator.php new file mode 100644 index 000000000..0d142f548 --- /dev/null +++ b/src/Builder/Accumulator/LastAccumulator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$last'; + } +} diff --git a/src/Builder/Accumulator/LastNAccumulator.php b/src/Builder/Accumulator/LastNAccumulator.php new file mode 100644 index 000000000..def4370b4 --- /dev/null +++ b/src/Builder/Accumulator/LastNAccumulator.php @@ -0,0 +1,56 @@ +input = $input; + $this->n = $n; + } + + public function getOperator(): string + { + return '$lastN'; + } +} diff --git a/src/Builder/Accumulator/MaxAccumulator.php b/src/Builder/Accumulator/MaxAccumulator.php new file mode 100644 index 000000000..517fdd23a --- /dev/null +++ b/src/Builder/Accumulator/MaxAccumulator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$max'; + } +} diff --git a/src/Builder/Accumulator/MaxNAccumulator.php b/src/Builder/Accumulator/MaxNAccumulator.php new file mode 100644 index 000000000..c952eedfa --- /dev/null +++ b/src/Builder/Accumulator/MaxNAccumulator.php @@ -0,0 +1,57 @@ +input = $input; + $this->n = $n; + } + + public function getOperator(): string + { + return '$maxN'; + } +} diff --git a/src/Builder/Accumulator/MedianAccumulator.php b/src/Builder/Accumulator/MedianAccumulator.php new file mode 100644 index 000000000..9236c2f7f --- /dev/null +++ b/src/Builder/Accumulator/MedianAccumulator.php @@ -0,0 +1,53 @@ +input = $input; + $this->method = $method; + } + + public function getOperator(): string + { + return '$median'; + } +} diff --git a/src/Builder/Accumulator/MergeObjectsAccumulator.php b/src/Builder/Accumulator/MergeObjectsAccumulator.php new file mode 100644 index 000000000..e42a3e93c --- /dev/null +++ b/src/Builder/Accumulator/MergeObjectsAccumulator.php @@ -0,0 +1,53 @@ + ...$document Any valid expression that resolves to a document. */ + public readonly array $document; + + /** + * @param Document|ResolvesToObject|Serializable|array|stdClass ...$document Any valid expression that resolves to a document. + * @no-named-arguments + */ + public function __construct(Document|Serializable|ResolvesToObject|stdClass|array ...$document) + { + if (\count($document) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $document, got %d.', 1, \count($document))); + } + if (! array_is_list($document)) { + throw new InvalidArgumentException('Expected $document arguments to be a list (array), named arguments are not supported'); + } + $this->document = $document; + } + + public function getOperator(): string + { + return '$mergeObjects'; + } +} diff --git a/src/Builder/Accumulator/MinAccumulator.php b/src/Builder/Accumulator/MinAccumulator.php new file mode 100644 index 000000000..23681a7f5 --- /dev/null +++ b/src/Builder/Accumulator/MinAccumulator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$min'; + } +} diff --git a/src/Builder/Accumulator/MinNAccumulator.php b/src/Builder/Accumulator/MinNAccumulator.php new file mode 100644 index 000000000..dab804cc5 --- /dev/null +++ b/src/Builder/Accumulator/MinNAccumulator.php @@ -0,0 +1,57 @@ +input = $input; + $this->n = $n; + } + + public function getOperator(): string + { + return '$minN'; + } +} diff --git a/src/Builder/Accumulator/PercentileAccumulator.php b/src/Builder/Accumulator/PercentileAccumulator.php new file mode 100644 index 000000000..f17f40e33 --- /dev/null +++ b/src/Builder/Accumulator/PercentileAccumulator.php @@ -0,0 +1,79 @@ +input = $input; + if (is_array($p) && ! array_is_list($p)) { + throw new InvalidArgumentException('Expected $p argument to be a list, got an associative array.'); + } + + $this->p = $p; + $this->method = $method; + } + + public function getOperator(): string + { + return '$percentile'; + } +} diff --git a/src/Builder/Accumulator/PushAccumulator.php b/src/Builder/Accumulator/PushAccumulator.php new file mode 100644 index 000000000..4368debad --- /dev/null +++ b/src/Builder/Accumulator/PushAccumulator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$push'; + } +} diff --git a/src/Builder/Accumulator/ShiftAccumulator.php b/src/Builder/Accumulator/ShiftAccumulator.php new file mode 100644 index 000000000..cf1739cc2 --- /dev/null +++ b/src/Builder/Accumulator/ShiftAccumulator.php @@ -0,0 +1,72 @@ +output = $output; + $this->by = $by; + $this->default = $default; + } + + public function getOperator(): string + { + return '$shift'; + } +} diff --git a/src/Builder/Accumulator/StdDevPopAccumulator.php b/src/Builder/Accumulator/StdDevPopAccumulator.php new file mode 100644 index 000000000..fce7f5fdc --- /dev/null +++ b/src/Builder/Accumulator/StdDevPopAccumulator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$stdDevPop'; + } +} diff --git a/src/Builder/Accumulator/StdDevSampAccumulator.php b/src/Builder/Accumulator/StdDevSampAccumulator.php new file mode 100644 index 000000000..b3a83434e --- /dev/null +++ b/src/Builder/Accumulator/StdDevSampAccumulator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$stdDevSamp'; + } +} diff --git a/src/Builder/Accumulator/SumAccumulator.php b/src/Builder/Accumulator/SumAccumulator.php new file mode 100644 index 000000000..faf0ace44 --- /dev/null +++ b/src/Builder/Accumulator/SumAccumulator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$sum'; + } +} diff --git a/src/Builder/Accumulator/TopAccumulator.php b/src/Builder/Accumulator/TopAccumulator.php new file mode 100644 index 000000000..11f4caca0 --- /dev/null +++ b/src/Builder/Accumulator/TopAccumulator.php @@ -0,0 +1,54 @@ +sortBy = $sortBy; + $this->output = $output; + } + + public function getOperator(): string + { + return '$top'; + } +} diff --git a/src/Builder/Accumulator/TopNAccumulator.php b/src/Builder/Accumulator/TopNAccumulator.php new file mode 100644 index 000000000..dabec3721 --- /dev/null +++ b/src/Builder/Accumulator/TopNAccumulator.php @@ -0,0 +1,61 @@ +n = $n; + $this->sortBy = $sortBy; + $this->output = $output; + } + + public function getOperator(): string + { + return '$topN'; + } +} diff --git a/src/Builder/BuilderEncoder.php b/src/Builder/BuilderEncoder.php new file mode 100644 index 000000000..3704462cd --- /dev/null +++ b/src/Builder/BuilderEncoder.php @@ -0,0 +1,293 @@ + */ +class BuilderEncoder implements Encoder +{ + use EncodeIfSupported; + + /** + * {@inheritdoc} + */ + public function canEncode($value): bool + { + return $value instanceof Pipeline + || $value instanceof OperatorInterface + || $value instanceof ExpressionInterface + || $value instanceof QueryInterface + || $value instanceof FieldQueryInterface + || $value instanceof AccumulatorInterface + || $value instanceof ProjectionInterface + || $value instanceof WindowInterface; + } + + /** + * {@inheritdoc} + */ + public function encode($value): stdClass|array|string + { + if (! $this->canEncode($value)) { + throw UnsupportedValueException::invalidEncodableValue($value); + } + + // A pipeline is encoded as a list of stages + if ($value instanceof Pipeline) { + $encoded = []; + foreach ($value->getIterator() as $stage) { + $encoded[] = $this->encodeIfSupported($stage); + } + + return $encoded; + } + + // This specific encoding code if temporary until we have a generic way to encode stages and operators + if ($value instanceof FieldPathInterface) { + return '$' . $value->name; + } + + if ($value instanceof Variable) { + return '$$' . $value->name; + } + + if ($value instanceof QueryObject) { + return $this->encodeQueryObject($value); + } + + if ($value instanceof CombinedFieldQuery) { + return $this->encodeCombinedFilter($value); + } + + if ($value instanceof OutputWindow) { + return $this->encodeOutputWindow($value); + } + + if (! $value instanceof OperatorInterface) { + throw new LogicException(sprintf('Class "%s" does not implement OperatorInterface.', $value::class)); + } + + // The generic but incomplete encoding code + switch ($value::ENCODE) { + case Encode::Single: + return $this->encodeAsSingle($value); + + case Encode::Array: + return $this->encodeAsArray($value); + + case Encode::Object: + return $this->encodeAsObject($value); + + case Encode::Group: + assert($value instanceof GroupStage); + + return $this->encodeAsGroup($value); + } + + throw new LogicException(sprintf('Class "%s" does not have a valid ENCODE constant.', $value::class)); + } + + /** + * Encode the value as an array of properties, in the order they are defined in the class. + */ + private function encodeAsArray(OperatorInterface $value): stdClass + { + $result = []; + /** @var mixed $val */ + foreach (get_object_vars($value) as $val) { + // Skip optional arguments. + // $slice operator has the optional argument in the middle of the array + if ($val === Optional::Undefined) { + continue; + } + + $result[] = $this->recursiveEncode($val); + } + + return $this->wrap($value, $result); + } + + /** + * $group stage have a specific encoding because the _id argument is required and others are variadic + */ + private function encodeAsGroup(GroupStage $value): stdClass + { + $result = new stdClass(); + $result->_id = $this->recursiveEncode($value->_id); + + foreach (get_object_vars($value->field) as $key => $val) { + $result->{$key} = $this->recursiveEncode($val); + } + + return $this->wrap($value, $result); + } + + private function encodeAsObject(OperatorInterface $value): stdClass + { + $result = new stdClass(); + foreach (get_object_vars($value) as $key => $val) { + // Skip optional arguments. If they have a default value, it is resolved by the server. + if ($val === Optional::Undefined) { + continue; + } + + $result->{$key} = $this->recursiveEncode($val); + } + + return $this->wrap($value, $result); + } + + /** + * Get the unique property of the operator as value + */ + private function encodeAsSingle(OperatorInterface $value): stdClass + { + foreach (get_object_vars($value) as $val) { + $result = $this->recursiveEncode($val); + + return $this->wrap($value, $result); + } + + throw new LogicException(sprintf('Class "%s" does not have a single property.', $value::class)); + } + + private function encodeCombinedFilter(CombinedFieldQuery $filter): stdClass + { + $result = new stdClass(); + foreach ($filter->fieldQueries as $filter) { + $filter = $this->recursiveEncode($filter); + if (is_object($filter)) { + $filter = get_object_vars($filter); + } elseif (! is_array($filter)) { + throw new LogicException(sprintf('Query filters must an array or an object. Got "%s"', get_debug_type($filter))); + } + + foreach ($filter as $key => $value) { + $result->{$key} = $value; + } + } + + return $result; + } + + /** + * Query objects are encoded by merging query operator with field path to filter operators in the same object. + */ + private function encodeQueryObject(QueryObject $query): stdClass + { + $result = new stdClass(); + foreach ($query->queries as $key => $value) { + if ($value instanceof QueryInterface) { + // The sub-objects is merged into the main object, replacing duplicate keys + foreach (get_object_vars($this->recursiveEncode($value)) as $subKey => $subValue) { + if (property_exists($result, $subKey)) { + throw new LogicException(sprintf('Duplicate key "%s" in query object', $subKey)); + } + + $result->{$subKey} = $subValue; + } + } else { + if (property_exists($result, $key)) { + throw new LogicException(sprintf('Duplicate key "%s" in query object', $key)); + } + + $result->{$key} = $this->encodeIfSupported($value); + } + } + + return $result; + } + + /** + * For the $setWindowFields stage output parameter, the optional window parameter is encoded in the same object + * of the window operator. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/ + */ + private function encodeOutputWindow(OutputWindow $outputWindow): stdClass + { + $result = $this->recursiveEncode($outputWindow->operator); + + // Transform the result into an stdClass if a document is provided + if (! $outputWindow->operator instanceof WindowInterface && (is_array($result) || is_object($result))) { + if (! is_first_key_operator($result)) { + throw new LogicException(sprintf('Expected OutputWindow::$operator to be an operator. Got "%s"', array_key_first($result))); + } + + $result = (object) $result; + } + + if (! $result instanceof stdClass) { + throw new LogicException(sprintf('Expected OutputWindow::$operator to be an stdClass, array or WindowInterface. Got "%s"', get_debug_type($result))); + } + + if ($outputWindow->window !== Optional::Undefined) { + $result->window = $this->recursiveEncode($outputWindow->window); + } + + return $result; + } + + /** + * Nested arrays and objects must be encoded recursively. + */ + private function recursiveEncode(mixed $value): mixed + { + if (is_array($value)) { + foreach ($value as $key => $val) { + $value[$key] = $this->recursiveEncode($val); + } + + return $value; + } + + if ($value instanceof stdClass) { + foreach (get_object_vars($value) as $key => $val) { + $value->{$key} = $this->recursiveEncode($val); + } + } + + return $this->encodeIfSupported($value); + } + + private function wrap(OperatorInterface $value, mixed $result): stdClass + { + $object = new stdClass(); + $object->{$value->getOperator()} = $result; + + return $object; + } +} diff --git a/src/Builder/Expression.php b/src/Builder/Expression.php new file mode 100644 index 000000000..0ad2d5e11 --- /dev/null +++ b/src/Builder/Expression.php @@ -0,0 +1,16 @@ +value = $value; + } + + public function getOperator(): string + { + return '$abs'; + } +} diff --git a/src/Builder/Expression/AcosOperator.php b/src/Builder/Expression/AcosOperator.php new file mode 100644 index 000000000..38a12b6c4 --- /dev/null +++ b/src/Builder/Expression/AcosOperator.php @@ -0,0 +1,46 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$acos'; + } +} diff --git a/src/Builder/Expression/AcoshOperator.php b/src/Builder/Expression/AcoshOperator.php new file mode 100644 index 000000000..ba956df43 --- /dev/null +++ b/src/Builder/Expression/AcoshOperator.php @@ -0,0 +1,46 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$acosh'; + } +} diff --git a/src/Builder/Expression/AddOperator.php b/src/Builder/Expression/AddOperator.php new file mode 100644 index 000000000..3e18334c7 --- /dev/null +++ b/src/Builder/Expression/AddOperator.php @@ -0,0 +1,51 @@ + ...$expression The arguments can be any valid expression as long as they resolve to either all numbers or to numbers and a date. */ + public readonly array $expression; + + /** + * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int ...$expression The arguments can be any valid expression as long as they resolve to either all numbers or to numbers and a date. + * @no-named-arguments + */ + public function __construct(Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$add'; + } +} diff --git a/src/Builder/Expression/AllElementsTrueOperator.php b/src/Builder/Expression/AllElementsTrueOperator.php new file mode 100644 index 000000000..36aa996a2 --- /dev/null +++ b/src/Builder/Expression/AllElementsTrueOperator.php @@ -0,0 +1,50 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression + * @no-named-arguments + */ + public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$allElementsTrue'; + } +} diff --git a/src/Builder/Expression/AndOperator.php b/src/Builder/Expression/AndOperator.php new file mode 100644 index 000000000..ab874f9da --- /dev/null +++ b/src/Builder/Expression/AndOperator.php @@ -0,0 +1,54 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Decimal128|ExpressionInterface|Int64|ResolvesToBool|ResolvesToNull|ResolvesToNumber|ResolvesToString|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @no-named-arguments + */ + public function __construct( + Decimal128|Int64|Type|ResolvesToBool|ResolvesToNull|ResolvesToNumber|ResolvesToString|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, + ) { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$and'; + } +} diff --git a/src/Builder/Expression/AnyElementTrueOperator.php b/src/Builder/Expression/AnyElementTrueOperator.php new file mode 100644 index 000000000..68cef3242 --- /dev/null +++ b/src/Builder/Expression/AnyElementTrueOperator.php @@ -0,0 +1,48 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$anyElementTrue'; + } +} diff --git a/src/Builder/Expression/ArrayElemAtOperator.php b/src/Builder/Expression/ArrayElemAtOperator.php new file mode 100644 index 000000000..e7fd09073 --- /dev/null +++ b/src/Builder/Expression/ArrayElemAtOperator.php @@ -0,0 +1,53 @@ +array = $array; + $this->idx = $idx; + } + + public function getOperator(): string + { + return '$arrayElemAt'; + } +} diff --git a/src/Builder/Expression/ArrayFieldPath.php b/src/Builder/Expression/ArrayFieldPath.php new file mode 100644 index 000000000..90ab52822 --- /dev/null +++ b/src/Builder/Expression/ArrayFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/ArrayToObjectOperator.php b/src/Builder/Expression/ArrayToObjectOperator.php new file mode 100644 index 000000000..4c6935875 --- /dev/null +++ b/src/Builder/Expression/ArrayToObjectOperator.php @@ -0,0 +1,48 @@ +array = $array; + } + + public function getOperator(): string + { + return '$arrayToObject'; + } +} diff --git a/src/Builder/Expression/AsinOperator.php b/src/Builder/Expression/AsinOperator.php new file mode 100644 index 000000000..f46031231 --- /dev/null +++ b/src/Builder/Expression/AsinOperator.php @@ -0,0 +1,46 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$asin'; + } +} diff --git a/src/Builder/Expression/AsinhOperator.php b/src/Builder/Expression/AsinhOperator.php new file mode 100644 index 000000000..ab466a0d8 --- /dev/null +++ b/src/Builder/Expression/AsinhOperator.php @@ -0,0 +1,46 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$asinh'; + } +} diff --git a/src/Builder/Expression/Atan2Operator.php b/src/Builder/Expression/Atan2Operator.php new file mode 100644 index 000000000..c08e7fbf8 --- /dev/null +++ b/src/Builder/Expression/Atan2Operator.php @@ -0,0 +1,53 @@ +y = $y; + $this->x = $x; + } + + public function getOperator(): string + { + return '$atan2'; + } +} diff --git a/src/Builder/Expression/AtanOperator.php b/src/Builder/Expression/AtanOperator.php new file mode 100644 index 000000000..b6d99973d --- /dev/null +++ b/src/Builder/Expression/AtanOperator.php @@ -0,0 +1,46 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$atan'; + } +} diff --git a/src/Builder/Expression/AtanhOperator.php b/src/Builder/Expression/AtanhOperator.php new file mode 100644 index 000000000..38b9208f6 --- /dev/null +++ b/src/Builder/Expression/AtanhOperator.php @@ -0,0 +1,46 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$atanh'; + } +} diff --git a/src/Builder/Expression/AvgOperator.php b/src/Builder/Expression/AvgOperator.php new file mode 100644 index 000000000..efbe4254a --- /dev/null +++ b/src/Builder/Expression/AvgOperator.php @@ -0,0 +1,51 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression + * @no-named-arguments + */ + public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$avg'; + } +} diff --git a/src/Builder/Expression/BinDataFieldPath.php b/src/Builder/Expression/BinDataFieldPath.php new file mode 100644 index 000000000..b0152472c --- /dev/null +++ b/src/Builder/Expression/BinDataFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/BinarySizeOperator.php b/src/Builder/Expression/BinarySizeOperator.php new file mode 100644 index 000000000..d4e93f345 --- /dev/null +++ b/src/Builder/Expression/BinarySizeOperator.php @@ -0,0 +1,39 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$binarySize'; + } +} diff --git a/src/Builder/Expression/BitAndOperator.php b/src/Builder/Expression/BitAndOperator.php new file mode 100644 index 000000000..221237aac --- /dev/null +++ b/src/Builder/Expression/BitAndOperator.php @@ -0,0 +1,50 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Int64|ResolvesToInt|ResolvesToLong|int ...$expression + * @no-named-arguments + */ + public function __construct(Int64|ResolvesToInt|ResolvesToLong|int ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$bitAnd'; + } +} diff --git a/src/Builder/Expression/BitNotOperator.php b/src/Builder/Expression/BitNotOperator.php new file mode 100644 index 000000000..d75322971 --- /dev/null +++ b/src/Builder/Expression/BitNotOperator.php @@ -0,0 +1,40 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$bitNot'; + } +} diff --git a/src/Builder/Expression/BitOrOperator.php b/src/Builder/Expression/BitOrOperator.php new file mode 100644 index 000000000..85962655d --- /dev/null +++ b/src/Builder/Expression/BitOrOperator.php @@ -0,0 +1,50 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Int64|ResolvesToInt|ResolvesToLong|int ...$expression + * @no-named-arguments + */ + public function __construct(Int64|ResolvesToInt|ResolvesToLong|int ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$bitOr'; + } +} diff --git a/src/Builder/Expression/BitXorOperator.php b/src/Builder/Expression/BitXorOperator.php new file mode 100644 index 000000000..e95f4f8f9 --- /dev/null +++ b/src/Builder/Expression/BitXorOperator.php @@ -0,0 +1,50 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Int64|ResolvesToInt|ResolvesToLong|int ...$expression + * @no-named-arguments + */ + public function __construct(Int64|ResolvesToInt|ResolvesToLong|int ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$bitXor'; + } +} diff --git a/src/Builder/Expression/BoolFieldPath.php b/src/Builder/Expression/BoolFieldPath.php new file mode 100644 index 000000000..0258c3dcd --- /dev/null +++ b/src/Builder/Expression/BoolFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/BsonSizeOperator.php b/src/Builder/Expression/BsonSizeOperator.php new file mode 100644 index 000000000..a243f2b06 --- /dev/null +++ b/src/Builder/Expression/BsonSizeOperator.php @@ -0,0 +1,41 @@ +object = $object; + } + + public function getOperator(): string + { + return '$bsonSize'; + } +} diff --git a/src/Builder/Expression/CeilOperator.php b/src/Builder/Expression/CeilOperator.php new file mode 100644 index 000000000..95a01aa14 --- /dev/null +++ b/src/Builder/Expression/CeilOperator.php @@ -0,0 +1,40 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$ceil'; + } +} diff --git a/src/Builder/Expression/CmpOperator.php b/src/Builder/Expression/CmpOperator.php new file mode 100644 index 000000000..c80ba393c --- /dev/null +++ b/src/Builder/Expression/CmpOperator.php @@ -0,0 +1,48 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$cmp'; + } +} diff --git a/src/Builder/Expression/ConcatArraysOperator.php b/src/Builder/Expression/ConcatArraysOperator.php new file mode 100644 index 000000000..95c0aae66 --- /dev/null +++ b/src/Builder/Expression/ConcatArraysOperator.php @@ -0,0 +1,50 @@ + ...$array */ + public readonly array $array; + + /** + * @param BSONArray|PackedArray|ResolvesToArray|array ...$array + * @no-named-arguments + */ + public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$array) + { + if (\count($array) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $array, got %d.', 1, \count($array))); + } + if (! array_is_list($array)) { + throw new InvalidArgumentException('Expected $array arguments to be a list (array), named arguments are not supported'); + } + $this->array = $array; + } + + public function getOperator(): string + { + return '$concatArrays'; + } +} diff --git a/src/Builder/Expression/ConcatOperator.php b/src/Builder/Expression/ConcatOperator.php new file mode 100644 index 000000000..6b401f95c --- /dev/null +++ b/src/Builder/Expression/ConcatOperator.php @@ -0,0 +1,48 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param ResolvesToString|non-empty-string ...$expression + * @no-named-arguments + */ + public function __construct(ResolvesToString|string ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$concat'; + } +} diff --git a/src/Builder/Expression/CondOperator.php b/src/Builder/Expression/CondOperator.php new file mode 100644 index 000000000..dfd53ba6f --- /dev/null +++ b/src/Builder/Expression/CondOperator.php @@ -0,0 +1,54 @@ +if = $if; + $this->then = $then; + $this->else = $else; + } + + public function getOperator(): string + { + return '$cond'; + } +} diff --git a/src/Builder/Expression/ConvertOperator.php b/src/Builder/Expression/ConvertOperator.php new file mode 100644 index 000000000..ecfdafed6 --- /dev/null +++ b/src/Builder/Expression/ConvertOperator.php @@ -0,0 +1,70 @@ +input = $input; + $this->to = $to; + $this->onError = $onError; + $this->onNull = $onNull; + } + + public function getOperator(): string + { + return '$convert'; + } +} diff --git a/src/Builder/Expression/CosOperator.php b/src/Builder/Expression/CosOperator.php new file mode 100644 index 000000000..d3fe010e2 --- /dev/null +++ b/src/Builder/Expression/CosOperator.php @@ -0,0 +1,44 @@ + resolves to a 128-bit decimal value. + */ + public readonly Decimal128|Int64|ResolvesToNumber|float|int $expression; + + /** + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression $cos takes any valid expression that resolves to a number. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the result to radians. + * By default $cos returns values as a double. $cos can also return values as a 128-bit decimal as long as the resolves to a 128-bit decimal value. + */ + public function __construct(Decimal128|Int64|ResolvesToNumber|float|int $expression) + { + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$cos'; + } +} diff --git a/src/Builder/Expression/CoshOperator.php b/src/Builder/Expression/CoshOperator.php new file mode 100644 index 000000000..53530064f --- /dev/null +++ b/src/Builder/Expression/CoshOperator.php @@ -0,0 +1,44 @@ + resolves to a 128-bit decimal value. + */ + public readonly Decimal128|Int64|ResolvesToNumber|float|int $expression; + + /** + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression $cosh takes any valid expression that resolves to a number, measured in radians. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the value to radians. + * By default $cosh returns values as a double. $cosh can also return values as a 128-bit decimal if the resolves to a 128-bit decimal value. + */ + public function __construct(Decimal128|Int64|ResolvesToNumber|float|int $expression) + { + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$cosh'; + } +} diff --git a/src/Builder/Expression/DateAddOperator.php b/src/Builder/Expression/DateAddOperator.php new file mode 100644 index 000000000..183d7b405 --- /dev/null +++ b/src/Builder/Expression/DateAddOperator.php @@ -0,0 +1,62 @@ +startDate = $startDate; + $this->unit = $unit; + $this->amount = $amount; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$dateAdd'; + } +} diff --git a/src/Builder/Expression/DateDiffOperator.php b/src/Builder/Expression/DateDiffOperator.php new file mode 100644 index 000000000..62cedac3f --- /dev/null +++ b/src/Builder/Expression/DateDiffOperator.php @@ -0,0 +1,67 @@ +startDate = $startDate; + $this->endDate = $endDate; + $this->unit = $unit; + $this->timezone = $timezone; + $this->startOfWeek = $startOfWeek; + } + + public function getOperator(): string + { + return '$dateDiff'; + } +} diff --git a/src/Builder/Expression/DateFieldPath.php b/src/Builder/Expression/DateFieldPath.php new file mode 100644 index 000000000..231ebd676 --- /dev/null +++ b/src/Builder/Expression/DateFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/DateFromPartsOperator.php b/src/Builder/Expression/DateFromPartsOperator.php new file mode 100644 index 000000000..f86284456 --- /dev/null +++ b/src/Builder/Expression/DateFromPartsOperator.php @@ -0,0 +1,102 @@ +year = $year; + $this->isoWeekYear = $isoWeekYear; + $this->month = $month; + $this->isoWeek = $isoWeek; + $this->day = $day; + $this->isoDayOfWeek = $isoDayOfWeek; + $this->hour = $hour; + $this->minute = $minute; + $this->second = $second; + $this->millisecond = $millisecond; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$dateFromParts'; + } +} diff --git a/src/Builder/Expression/DateFromStringOperator.php b/src/Builder/Expression/DateFromStringOperator.php new file mode 100644 index 000000000..cbda447b8 --- /dev/null +++ b/src/Builder/Expression/DateFromStringOperator.php @@ -0,0 +1,79 @@ +dateString = $dateString; + $this->format = $format; + $this->timezone = $timezone; + $this->onError = $onError; + $this->onNull = $onNull; + } + + public function getOperator(): string + { + return '$dateFromString'; + } +} diff --git a/src/Builder/Expression/DateSubtractOperator.php b/src/Builder/Expression/DateSubtractOperator.php new file mode 100644 index 000000000..8229a35fa --- /dev/null +++ b/src/Builder/Expression/DateSubtractOperator.php @@ -0,0 +1,62 @@ +startDate = $startDate; + $this->unit = $unit; + $this->amount = $amount; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$dateSubtract'; + } +} diff --git a/src/Builder/Expression/DateToPartsOperator.php b/src/Builder/Expression/DateToPartsOperator.php new file mode 100644 index 000000000..37d7a4cfc --- /dev/null +++ b/src/Builder/Expression/DateToPartsOperator.php @@ -0,0 +1,55 @@ +date = $date; + $this->timezone = $timezone; + $this->iso8601 = $iso8601; + } + + public function getOperator(): string + { + return '$dateToParts'; + } +} diff --git a/src/Builder/Expression/DateToStringOperator.php b/src/Builder/Expression/DateToStringOperator.php new file mode 100644 index 000000000..0180068e4 --- /dev/null +++ b/src/Builder/Expression/DateToStringOperator.php @@ -0,0 +1,72 @@ +date = $date; + $this->format = $format; + $this->timezone = $timezone; + $this->onNull = $onNull; + } + + public function getOperator(): string + { + return '$dateToString'; + } +} diff --git a/src/Builder/Expression/DateTruncOperator.php b/src/Builder/Expression/DateTruncOperator.php new file mode 100644 index 000000000..9464e7752 --- /dev/null +++ b/src/Builder/Expression/DateTruncOperator.php @@ -0,0 +1,81 @@ +date = $date; + $this->unit = $unit; + $this->binSize = $binSize; + $this->timezone = $timezone; + $this->startOfWeek = $startOfWeek; + } + + public function getOperator(): string + { + return '$dateTrunc'; + } +} diff --git a/src/Builder/Expression/DayOfMonthOperator.php b/src/Builder/Expression/DayOfMonthOperator.php new file mode 100644 index 000000000..a13e3b983 --- /dev/null +++ b/src/Builder/Expression/DayOfMonthOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$dayOfMonth'; + } +} diff --git a/src/Builder/Expression/DayOfWeekOperator.php b/src/Builder/Expression/DayOfWeekOperator.php new file mode 100644 index 000000000..c593600d2 --- /dev/null +++ b/src/Builder/Expression/DayOfWeekOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$dayOfWeek'; + } +} diff --git a/src/Builder/Expression/DayOfYearOperator.php b/src/Builder/Expression/DayOfYearOperator.php new file mode 100644 index 000000000..3b0762612 --- /dev/null +++ b/src/Builder/Expression/DayOfYearOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$dayOfYear'; + } +} diff --git a/src/Builder/Expression/DecimalFieldPath.php b/src/Builder/Expression/DecimalFieldPath.php new file mode 100644 index 000000000..a78420cb0 --- /dev/null +++ b/src/Builder/Expression/DecimalFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/DegreesToRadiansOperator.php b/src/Builder/Expression/DegreesToRadiansOperator.php new file mode 100644 index 000000000..a9329e16e --- /dev/null +++ b/src/Builder/Expression/DegreesToRadiansOperator.php @@ -0,0 +1,44 @@ + resolves to a 128-bit decimal value. + */ + public readonly Decimal128|Int64|ResolvesToNumber|float|int $expression; + + /** + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression $degreesToRadians takes any valid expression that resolves to a number. + * By default $degreesToRadians returns values as a double. $degreesToRadians can also return values as a 128-bit decimal as long as the resolves to a 128-bit decimal value. + */ + public function __construct(Decimal128|Int64|ResolvesToNumber|float|int $expression) + { + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$degreesToRadians'; + } +} diff --git a/src/Builder/Expression/DivideOperator.php b/src/Builder/Expression/DivideOperator.php new file mode 100644 index 000000000..bda2f503a --- /dev/null +++ b/src/Builder/Expression/DivideOperator.php @@ -0,0 +1,47 @@ +dividend = $dividend; + $this->divisor = $divisor; + } + + public function getOperator(): string + { + return '$divide'; + } +} diff --git a/src/Builder/Expression/DoubleFieldPath.php b/src/Builder/Expression/DoubleFieldPath.php new file mode 100644 index 000000000..4ed86c523 --- /dev/null +++ b/src/Builder/Expression/DoubleFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/EqOperator.php b/src/Builder/Expression/EqOperator.php new file mode 100644 index 000000000..c60717006 --- /dev/null +++ b/src/Builder/Expression/EqOperator.php @@ -0,0 +1,48 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$eq'; + } +} diff --git a/src/Builder/Expression/ExpOperator.php b/src/Builder/Expression/ExpOperator.php new file mode 100644 index 000000000..a168a6acd --- /dev/null +++ b/src/Builder/Expression/ExpOperator.php @@ -0,0 +1,40 @@ +exponent = $exponent; + } + + public function getOperator(): string + { + return '$exp'; + } +} diff --git a/src/Builder/Expression/ExpressionFactoryTrait.php b/src/Builder/Expression/ExpressionFactoryTrait.php new file mode 100644 index 000000000..3f5f04339 --- /dev/null +++ b/src/Builder/Expression/ExpressionFactoryTrait.php @@ -0,0 +1,105 @@ + resolves to a 128-bit decimal value. + */ + public static function cos(Decimal128|Int64|ResolvesToNumber|float|int $expression): CosOperator + { + return new CosOperator($expression); + } + + /** + * Returns the hyperbolic cosine of a value that is measured in radians. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/cosh/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression $cosh takes any valid expression that resolves to a number, measured in radians. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the value to radians. + * By default $cosh returns values as a double. $cosh can also return values as a 128-bit decimal if the resolves to a 128-bit decimal value. + */ + public static function cosh(Decimal128|Int64|ResolvesToNumber|float|int $expression): CoshOperator + { + return new CoshOperator($expression); + } + + /** + * Adds a number of time units to a date object. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateAdd/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param ResolvesToString|non-empty-string $unit The unit used to measure the amount of time added to the startDate. + * @param Int64|ResolvesToInt|ResolvesToLong|int $amount + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function dateAdd( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, + ResolvesToString|string $unit, + Int64|ResolvesToInt|ResolvesToLong|int $amount, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): DateAddOperator + { + return new DateAddOperator($startDate, $unit, $amount, $timezone); + } + + /** + * Returns the difference between two dates. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateDiff/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The start of the time period. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $endDate The end of the time period. The endDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param ResolvesToString|non-empty-string $unit The time measurement unit between the startDate and endDate + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|non-empty-string $startOfWeek Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string + */ + public static function dateDiff( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $endDate, + ResolvesToString|string $unit, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + Optional|ResolvesToString|string $startOfWeek = Optional::Undefined, + ): DateDiffOperator + { + return new DateDiffOperator($startDate, $endDate, $unit, $timezone, $startOfWeek); + } + + /** + * Constructs a BSON Date object given the date's constituent parts. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateFromParts/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $year Calendar year. Can be any expression that evaluates to a number. + * @param Decimal128|Int64|ResolvesToNumber|float|int $isoWeekYear ISO Week Date Year. Can be any expression that evaluates to a number. + * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $month Month. Defaults to 1. + * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $isoWeek Week of year. Defaults to 1. + * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $day Day of month. Defaults to 1. + * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $isoDayOfWeek Day of week (Monday 1 - Sunday 7). Defaults to 1. + * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $hour Hour. Defaults to 0. + * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $minute Minute. Defaults to 0. + * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $second Second. Defaults to 0. + * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $millisecond Millisecond. Defaults to 0. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function dateFromParts( + Decimal128|Int64|ResolvesToNumber|float|int $year, + Decimal128|Int64|ResolvesToNumber|float|int $isoWeekYear, + Optional|Decimal128|Int64|ResolvesToNumber|float|int $month = Optional::Undefined, + Optional|Decimal128|Int64|ResolvesToNumber|float|int $isoWeek = Optional::Undefined, + Optional|Decimal128|Int64|ResolvesToNumber|float|int $day = Optional::Undefined, + Optional|Decimal128|Int64|ResolvesToNumber|float|int $isoDayOfWeek = Optional::Undefined, + Optional|Decimal128|Int64|ResolvesToNumber|float|int $hour = Optional::Undefined, + Optional|Decimal128|Int64|ResolvesToNumber|float|int $minute = Optional::Undefined, + Optional|Decimal128|Int64|ResolvesToNumber|float|int $second = Optional::Undefined, + Optional|Decimal128|Int64|ResolvesToNumber|float|int $millisecond = Optional::Undefined, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): DateFromPartsOperator + { + return new DateFromPartsOperator($year, $isoWeekYear, $month, $isoWeek, $day, $isoDayOfWeek, $hour, $minute, $second, $millisecond, $timezone); + } + + /** + * Converts a date/time string to a date object. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateFromString/ + * @param ResolvesToString|non-empty-string $dateString The date/time string to convert to a date object. + * @param Optional|ResolvesToString|non-empty-string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + * If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. + * @param Optional|ResolvesToString|non-empty-string $timezone The time zone to use to format the date. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onError If $dateFromString encounters an error while parsing the given dateString, it outputs the result value of the provided onError expression. This result value can be of any type. + * If you do not specify onError, $dateFromString throws an error if it cannot parse dateString. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. This result value can be of any type. + * If you do not specify onNull and dateString is null or missing, then $dateFromString outputs null. + */ + public static function dateFromString( + ResolvesToString|string $dateString, + Optional|ResolvesToString|string $format = Optional::Undefined, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $onError = Optional::Undefined, + Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $onNull = Optional::Undefined, + ): DateFromStringOperator + { + return new DateFromStringOperator($dateString, $format, $timezone, $onError, $onNull); + } + + /** + * Subtracts a number of time units from a date object. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateSubtract/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param ResolvesToString|non-empty-string $unit The unit used to measure the amount of time added to the startDate. + * @param Int64|ResolvesToInt|ResolvesToLong|int $amount + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function dateSubtract( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, + ResolvesToString|string $unit, + Int64|ResolvesToInt|ResolvesToLong|int $amount, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): DateSubtractOperator + { + return new DateSubtractOperator($startDate, $unit, $amount, $timezone); + } + + /** + * Returns a document containing the constituent parts of a date. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToParts/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The input date for which to return parts. date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|bool $iso8601 If set to true, modifies the output document to use ISO week date fields. Defaults to false. + */ + public static function dateToParts( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + Optional|bool $iso8601 = Optional::Undefined, + ): DateToPartsOperator + { + return new DateToPartsOperator($date, $timezone, $iso8601); + } + + /** + * Returns the date as a formatted string. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to convert to string. Must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + * If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. + * @param Optional|ResolvesToString|non-empty-string $timezone The time zone to use to format the date. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull The value to return if the date is null or missing. + * If unspecified, $dateToString returns null if the date is null or missing. + */ + public static function dateToString( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $format = Optional::Undefined, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $onNull = Optional::Undefined, + ): DateToStringOperator + { + return new DateToStringOperator($date, $format, $timezone, $onNull); + } + + /** + * Truncates a date. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateTrunc/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to truncate, specified in UTC. The date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param ResolvesToString|non-empty-string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. + * Together, binSize and unit specify the time period used in the $dateTrunc calculation. + * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $binSize The numeric time value, specified as an expression that must resolve to a positive non-zero number. Defaults to 1. + * Together, binSize and unit specify the time period used in the $dateTrunc calculation. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|non-empty-string $startOfWeek The start of the week. Used when + * unit is week. Defaults to Sunday. + */ + public static function dateTrunc( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + ResolvesToString|string $unit, + Optional|Decimal128|Int64|ResolvesToNumber|float|int $binSize = Optional::Undefined, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + Optional|string $startOfWeek = Optional::Undefined, + ): DateTruncOperator + { + return new DateTruncOperator($date, $unit, $binSize, $timezone, $startOfWeek); + } + + /** + * Returns the day of the month for a date as a number between 1 and 31. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfMonth/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function dayOfMonth( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): DayOfMonthOperator + { + return new DayOfMonthOperator($date, $timezone); + } + + /** + * Returns the day of the week for a date as a number between 1 (Sunday) and 7 (Saturday). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfWeek/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function dayOfWeek( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): DayOfWeekOperator + { + return new DayOfWeekOperator($date, $timezone); + } + + /** + * Returns the day of the year for a date as a number between 1 and 366 (leap year). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfYear/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function dayOfYear( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): DayOfYearOperator + { + return new DayOfYearOperator($date, $timezone); + } + + /** + * Converts a value from degrees to radians. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/degreesToRadians/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression $degreesToRadians takes any valid expression that resolves to a number. + * By default $degreesToRadians returns values as a double. $degreesToRadians can also return values as a 128-bit decimal as long as the resolves to a 128-bit decimal value. + */ + public static function degreesToRadians( + Decimal128|Int64|ResolvesToNumber|float|int $expression, + ): DegreesToRadiansOperator + { + return new DegreesToRadiansOperator($expression); + } + + /** + * Returns the result of dividing the first number by the second. Accepts two argument expressions. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/divide/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $dividend The first argument is the dividend, and the second argument is the divisor; i.e. the first argument is divided by the second argument. + * @param Decimal128|Int64|ResolvesToNumber|float|int $divisor + */ + public static function divide( + Decimal128|Int64|ResolvesToNumber|float|int $dividend, + Decimal128|Int64|ResolvesToNumber|float|int $divisor, + ): DivideOperator + { + return new DivideOperator($dividend, $divisor); + } + + /** + * Returns true if the values are equivalent. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/eq/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + */ + public static function eq( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2, + ): EqOperator + { + return new EqOperator($expression1, $expression2); + } + + /** + * Raises e to the specified exponent. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/exp/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $exponent + */ + public static function exp(Decimal128|Int64|ResolvesToNumber|float|int $exponent): ExpOperator + { + return new ExpOperator($exponent); + } + + /** + * Selects a subset of the array to return an array with only the elements that match the filter condition. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/ + * @param BSONArray|PackedArray|ResolvesToArray|array $input + * @param ResolvesToBool|bool $cond An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. + * @param Optional|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + * @param Optional|ResolvesToInt|int $limit A number expression that restricts the number of matching array elements that $filter returns. You cannot specify a limit less than 1. The matching array elements are returned in the order they appear in the input array. + * If the specified limit is greater than the number of matching array elements, $filter returns all matching array elements. If the limit is null, $filter returns all matching array elements. + */ + public static function filter( + PackedArray|ResolvesToArray|BSONArray|array $input, + ResolvesToBool|bool $cond, + Optional|string $as = Optional::Undefined, + Optional|ResolvesToInt|int $limit = Optional::Undefined, + ): FilterOperator + { + return new FilterOperator($input, $cond, $as, $limit); + } + + /** + * Returns the largest integer less than or equal to the specified number. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/floor/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression + */ + public static function floor(Decimal128|Int64|ResolvesToNumber|float|int $expression): FloorOperator + { + return new FloorOperator($expression); + } + + /** + * Defines a custom function. + * New in MongoDB 4.4. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/ + * @param non-empty-string $body The function definition. You can specify the function definition as either BSON type Code or String. + * @param BSONArray|PackedArray|array $args Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ]. + * @param non-empty-string $lang + */ + public static function function(string $body, PackedArray|BSONArray|array $args, string $lang): FunctionOperator + { + return new FunctionOperator($body, $args, $lang); + } + + /** + * Returns the value of a specified field from a document. You can use $getField to retrieve the value of fields with names that contain periods (.) or start with dollar signs ($). + * New in MongoDB 5.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/ + * @param non-empty-string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. + * If field begins with a dollar sign ($), place the field name inside of a $literal expression to return its value. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input Default: $$CURRENT + * A valid expression that contains the field for which you want to return a value. input must resolve to an object, missing, null, or undefined. If omitted, defaults to the document currently being processed in the pipeline ($$CURRENT). + */ + public static function getField( + string $field, + Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $input = Optional::Undefined, + ): GetFieldOperator + { + return new GetFieldOperator($field, $input); + } + + /** + * Returns true if the first value is greater than the second. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/gt/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + */ + public static function gt( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2, + ): GtOperator + { + return new GtOperator($expression1, $expression2); + } + + /** + * Returns true if the first value is greater than or equal to the second. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/gte/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + */ + public static function gte( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2, + ): GteOperator + { + return new GteOperator($expression1, $expression2); + } + + /** + * Returns the hour for a date as a number between 0 and 23. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/hour/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function hour( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): HourOperator + { + return new HourOperator($date, $timezone); + } + + /** + * Returns either the non-null result of the first expression or the result of the second expression if the first expression results in a null result. Null result encompasses instances of undefined values or missing fields. Accepts two expressions as arguments. The result of the second expression can be null. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ifNull/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + */ + public static function ifNull( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, + ): IfNullOperator + { + return new IfNullOperator(...$expression); + } + + /** + * Returns a boolean indicating whether a specified value is in an array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/in/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression Any valid expression expression. + * @param BSONArray|PackedArray|ResolvesToArray|array $array Any valid expression that resolves to an array. + */ + public static function in( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + PackedArray|ResolvesToArray|BSONArray|array $array, + ): InOperator + { + return new InOperator($expression, $array); + } + + /** + * Searches an array for an occurrence of a specified value and returns the array index of the first occurrence. Array indexes start at zero. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfArray/ + * @param BSONArray|PackedArray|ResolvesToArray|array $array Can be any valid expression as long as it resolves to an array. + * If the array expression resolves to a value of null or refers to a field that is missing, $indexOfArray returns null. + * If the array expression does not resolve to an array or null nor refers to a missing field, $indexOfArray returns an error. + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $search + * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. + * If unspecified, the starting index position for the search is the beginning of the string. + * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + * If unspecified, the ending index position for the search is the end of the string. + */ + public static function indexOfArray( + PackedArray|ResolvesToArray|BSONArray|array $array, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $search, + Optional|ResolvesToInt|int $start = Optional::Undefined, + Optional|ResolvesToInt|int $end = Optional::Undefined, + ): IndexOfArrayOperator + { + return new IndexOfArrayOperator($array, $search, $start, $end); + } + + /** + * Searches a string for an occurrence of a substring and returns the UTF-8 byte index of the first occurrence. If the substring is not found, returns -1. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfBytes/ + * @param ResolvesToString|non-empty-string $string Can be any valid expression as long as it resolves to a string. + * If the string expression resolves to a value of null or refers to a field that is missing, $indexOfBytes returns null. + * If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfBytes returns an error. + * @param ResolvesToString|non-empty-string $substring Can be any valid expression as long as it resolves to a string. + * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. + * If unspecified, the starting index position for the search is the beginning of the string. + * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + * If unspecified, the ending index position for the search is the end of the string. + */ + public static function indexOfBytes( + ResolvesToString|string $string, + ResolvesToString|string $substring, + Optional|ResolvesToInt|int $start = Optional::Undefined, + Optional|ResolvesToInt|int $end = Optional::Undefined, + ): IndexOfBytesOperator + { + return new IndexOfBytesOperator($string, $substring, $start, $end); + } + + /** + * Searches a string for an occurrence of a substring and returns the UTF-8 code point index of the first occurrence. If the substring is not found, returns -1 + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfCP/ + * @param ResolvesToString|non-empty-string $string Can be any valid expression as long as it resolves to a string. + * If the string expression resolves to a value of null or refers to a field that is missing, $indexOfCP returns null. + * If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfCP returns an error. + * @param ResolvesToString|non-empty-string $substring Can be any valid expression as long as it resolves to a string. + * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. + * If unspecified, the starting index position for the search is the beginning of the string. + * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + * If unspecified, the ending index position for the search is the end of the string. + */ + public static function indexOfCP( + ResolvesToString|string $string, + ResolvesToString|string $substring, + Optional|ResolvesToInt|int $start = Optional::Undefined, + Optional|ResolvesToInt|int $end = Optional::Undefined, + ): IndexOfCPOperator + { + return new IndexOfCPOperator($string, $substring, $start, $end); + } + + /** + * Returns the approximation of the area under a curve. + * New in MongoDB 5.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/ + * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input + * @param Optional|ResolvesToString|non-empty-string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. + */ + public static function integral( + Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input, + Optional|ResolvesToString|string $unit = Optional::Undefined, + ): IntegralOperator + { + return new IntegralOperator($input, $unit); + } + + /** + * Determines if the operand is an array. Returns a boolean. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isArray/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + */ + public static function isArray( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, + ): IsArrayOperator + { + return new IsArrayOperator(...$expression); + } + + /** + * Returns boolean true if the specified expression resolves to an integer, decimal, double, or long. + * Returns boolean false if the expression resolves to any other BSON type, null, or a missing field. + * New in MongoDB 4.4. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + */ + public static function isNumber( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, + ): IsNumberOperator + { + return new IsNumberOperator(...$expression); + } + + /** + * Returns the weekday number in ISO 8601 format, ranging from 1 (for Monday) to 7 (for Sunday). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoDayOfWeek/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function isoDayOfWeek( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): IsoDayOfWeekOperator + { + return new IsoDayOfWeekOperator($date, $timezone); + } + + /** + * Returns the week number in ISO 8601 format, ranging from 1 to 53. Week numbers start at 1 with the week (Monday through Sunday) that contains the year's first Thursday. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoWeek/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function isoWeek( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): IsoWeekOperator + { + return new IsoWeekOperator($date, $timezone); + } + + /** + * Returns the year number in ISO 8601 format. The year starts with the Monday of week 1 (ISO 8601) and ends with the Sunday of the last week (ISO 8601). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoWeekYear/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function isoWeekYear( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): IsoWeekYearOperator + { + return new IsoWeekYearOperator($date, $timezone); + } + + /** + * Defines variables for use within the scope of a subexpression and returns the result of the subexpression. Accepts named parameters. + * Accepts any number of argument expressions. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/let/ + * @param Document|Serializable|array|stdClass $vars Assignment block for the variables accessible in the in expression. To assign a variable, specify a string for the variable name and assign a valid expression for the value. + * The variable assignments have no meaning outside the in expression, not even within the vars block itself. + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in The expression to evaluate. + */ + public static function let( + Document|Serializable|stdClass|array $vars, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $in, + ): LetOperator + { + return new LetOperator($vars, $in); + } + + /** + * Fills null and missing fields in a window using linear interpolation based on surrounding field values. + * Available in the $setWindowFields stage. + * New in MongoDB 5.3. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression + */ + public static function linearFill(Decimal128|Int64|ResolvesToNumber|float|int $expression): LinearFillOperator + { + return new LinearFillOperator($expression); + } + + /** + * Return a value without parsing. Use for values that the aggregation pipeline may interpret as an expression. For example, use a $literal expression to a string that starts with a dollar sign ($) to avoid parsing as a field path. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/literal/ + * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value If the value is an expression, $literal does not evaluate the expression but instead returns the unparsed expression. + */ + public static function literal(Type|stdClass|array|bool|float|int|null|string $value): LiteralOperator + { + return new LiteralOperator($value); + } + + /** + * Calculates the natural log of a number. + * $ln is equivalent to $log: [ , Math.E ] expression, where Math.E is a JavaScript representation for Euler's number e. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ln/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $number Any valid expression as long as it resolves to a non-negative number. For more information on expressions, see Expressions. + */ + public static function ln(Decimal128|Int64|ResolvesToNumber|float|int $number): LnOperator + { + return new LnOperator($number); + } + + /** + * Last observation carried forward. Sets values for null and missing fields in a window to the last non-null value for the field. + * Available in the $setWindowFields stage. + * New in MongoDB 5.2. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function locf( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): LocfOperator + { + return new LocfOperator($expression); + } + + /** + * Calculates the log of a number in the specified base. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/log/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $number Any valid expression as long as it resolves to a non-negative number. + * @param Decimal128|Int64|ResolvesToNumber|float|int $base Any valid expression as long as it resolves to a positive number greater than 1. + */ + public static function log( + Decimal128|Int64|ResolvesToNumber|float|int $number, + Decimal128|Int64|ResolvesToNumber|float|int $base, + ): LogOperator + { + return new LogOperator($number, $base); + } + + /** + * Calculates the log base 10 of a number. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/log10/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $number Any valid expression as long as it resolves to a non-negative number. + */ + public static function log10(Decimal128|Int64|ResolvesToNumber|float|int $number): Log10Operator + { + return new Log10Operator($number); + } + + /** + * Returns true if the first value is less than the second. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lt/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + */ + public static function lt( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2, + ): LtOperator + { + return new LtOperator($expression1, $expression2); + } + + /** + * Returns true if the first value is less than or equal to the second. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lte/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + */ + public static function lte( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2, + ): LteOperator + { + return new LteOperator($expression1, $expression2); + } + + /** + * Removes whitespace or the specified characters from the beginning of a string. + * New in MongoDB 4.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ltrim/ + * @param ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. + * @param Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. + * If unspecified, $ltrim removes whitespace characters, including the null character. + */ + public static function ltrim( + ResolvesToString|string $input, + Optional|ResolvesToString|string $chars = Optional::Undefined, + ): LtrimOperator + { + return new LtrimOperator($input, $chars); + } + + /** + * Applies a subexpression to each element of an array and returns the array of resulting values in order. Accepts named parameters. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/ + * @param BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to an array. + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in An expression that is applied to each element of the input array. The expression references each element individually with the variable name specified in as. + * @param Optional|ResolvesToString|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + */ + public static function map( + PackedArray|ResolvesToArray|BSONArray|array $input, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $in, + Optional|ResolvesToString|string $as = Optional::Undefined, + ): MapOperator + { + return new MapOperator($input, $in, $as); + } + + /** + * Returns the maximum value that results from applying an expression to each document. + * Changed in MongoDB 5.0: Available in the $setWindowFields stage. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + */ + public static function max( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, + ): MaxOperator + { + return new MaxOperator(...$expression); + } + + /** + * Returns the n largest values in an array. Distinct from the $maxN accumulator. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN-array-element/ + * @param BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to the array from which to return the maximal n elements. + * @param ResolvesToInt|int $n An expression that resolves to a positive integer. The integer specifies the number of array elements that $maxN returns. + */ + public static function maxN( + PackedArray|ResolvesToArray|BSONArray|array $input, + ResolvesToInt|int $n, + ): MaxNOperator + { + return new MaxNOperator($input, $n); + } + + /** + * Returns an approximation of the median, the 50th percentile, as a scalar value. + * New in MongoDB 7.0. + * This operator is available as an accumulator in these stages: + * $group + * $setWindowFields + * It is also available as an aggregation expression. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. + * @param non-empty-string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. + */ + public static function median(Decimal128|Int64|ResolvesToNumber|float|int $input, string $method): MedianOperator + { + return new MedianOperator($input, $method); + } + + /** + * Access available per-document metadata related to the aggregation operation. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/ + * @param non-empty-string $keyword + */ + public static function meta(string $keyword): MetaOperator + { + return new MetaOperator($keyword); + } + + /** + * Returns the milliseconds of a date as a number between 0 and 999. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/millisecond/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function millisecond( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): MillisecondOperator + { + return new MillisecondOperator($date, $timezone); + } + + /** + * Returns the minimum value that results from applying an expression to each document. + * Changed in MongoDB 5.0: Available in the $setWindowFields stage. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + */ + public static function min( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, + ): MinOperator + { + return new MinOperator(...$expression); + } + + /** + * Returns the n smallest values in an array. Distinct from the $minN accumulator. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN-array-element/ + * @param BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to the array from which to return the maximal n elements. + * @param ResolvesToInt|int $n An expression that resolves to a positive integer. The integer specifies the number of array elements that $maxN returns. + */ + public static function minN( + PackedArray|ResolvesToArray|BSONArray|array $input, + ResolvesToInt|int $n, + ): MinNOperator + { + return new MinNOperator($input, $n); + } + + /** + * Returns the minute for a date as a number between 0 and 59. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/minute/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function minute( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): MinuteOperator + { + return new MinuteOperator($date, $timezone); + } + + /** + * Returns the remainder of the first number divided by the second. Accepts two argument expressions. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/mod/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $dividend The first argument is the dividend, and the second argument is the divisor; i.e. first argument is divided by the second argument. + * @param Decimal128|Int64|ResolvesToNumber|float|int $divisor + */ + public static function mod( + Decimal128|Int64|ResolvesToNumber|float|int $dividend, + Decimal128|Int64|ResolvesToNumber|float|int $divisor, + ): ModOperator + { + return new ModOperator($dividend, $divisor); + } + + /** + * Returns the month for a date as a number between 1 (January) and 12 (December). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/month/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function month( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): MonthOperator + { + return new MonthOperator($date, $timezone); + } + + /** + * Multiplies numbers to return the product. Accepts any number of argument expressions. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/multiply/ + * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression The arguments can be any valid expression as long as they resolve to numbers. + * Starting in MongoDB 6.1 you can optimize the $multiply operation. To improve performance, group references at the end of the argument list. + */ + public static function multiply(Decimal128|Int64|ResolvesToNumber|float|int ...$expression): MultiplyOperator + { + return new MultiplyOperator(...$expression); + } + + /** + * Returns true if the values are not equivalent. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ne/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + */ + public static function ne( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2, + ): NeOperator + { + return new NeOperator($expression1, $expression2); + } + + /** + * Returns the boolean value that is the opposite of its argument expression. Accepts a single argument expression. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/not/ + * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function not( + Type|ResolvesToBool|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): NotOperator + { + return new NotOperator($expression); + } + + /** + * Converts a document to an array of documents representing key-value pairs. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/ + * @param Document|ResolvesToObject|Serializable|array|stdClass $object Any valid expression as long as it resolves to a document object. $objectToArray applies to the top-level fields of its argument. If the argument is a document that itself contains embedded document fields, the $objectToArray does not recursively apply to the embedded document fields. + */ + public static function objectToArray( + Document|Serializable|ResolvesToObject|stdClass|array $object, + ): ObjectToArrayOperator + { + return new ObjectToArrayOperator($object); + } + + /** + * Returns true when any of its expressions evaluates to true. Accepts any number of argument expressions. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/or/ + * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + */ + public static function or( + Type|ResolvesToBool|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, + ): OrOperator + { + return new OrOperator(...$expression); + } + + /** + * Returns an array of scalar values that correspond to specified percentile values. + * New in MongoDB 7.0. + * + * This operator is available as an accumulator in these stages: + * $group + * + * $setWindowFields + * + * It is also available as an aggregation expression. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. + * @param BSONArray|PackedArray|ResolvesToArray|array $p $percentile calculates a percentile value for each element in p. The elements represent percentages and must evaluate to numeric values in the range 0.0 to 1.0, inclusive. + * $percentile returns results in the same order as the elements in p. + * @param non-empty-string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. + */ + public static function percentile( + Decimal128|Int64|ResolvesToNumber|float|int $input, + PackedArray|ResolvesToArray|BSONArray|array $p, + string $method, + ): PercentileOperator + { + return new PercentileOperator($input, $p, $method); + } + + /** + * Raises a number to the specified exponent. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/pow/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $number + * @param Decimal128|Int64|ResolvesToNumber|float|int $exponent + */ + public static function pow( + Decimal128|Int64|ResolvesToNumber|float|int $number, + Decimal128|Int64|ResolvesToNumber|float|int $exponent, + ): PowOperator + { + return new PowOperator($number, $exponent); + } + + /** + * Converts a value from radians to degrees. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/radiansToDegrees/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression + */ + public static function radiansToDegrees( + Decimal128|Int64|ResolvesToNumber|float|int $expression, + ): RadiansToDegreesOperator + { + return new RadiansToDegreesOperator($expression); + } + + /** + * Returns a random float between 0 and 1 + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rand/ + */ + public static function rand(): RandOperator + { + return new RandOperator(); + } + + /** + * Outputs an array containing a sequence of integers according to user-defined inputs. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/range/ + * @param ResolvesToInt|int $start An integer that specifies the start of the sequence. Can be any valid expression that resolves to an integer. + * @param ResolvesToInt|int $end An integer that specifies the exclusive upper limit of the sequence. Can be any valid expression that resolves to an integer. + * @param Optional|ResolvesToInt|int $step An integer that specifies the increment value. Can be any valid expression that resolves to a non-zero integer. Defaults to 1. + */ + public static function range( + ResolvesToInt|int $start, + ResolvesToInt|int $end, + Optional|ResolvesToInt|int $step = Optional::Undefined, + ): RangeOperator + { + return new RangeOperator($start, $end, $step); + } + + /** + * Returns the document position (known as the rank) relative to other documents in the $setWindowFields stage partition. + * New in MongoDB 5.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/ + */ + public static function rank(): RankOperator + { + return new RankOperator(); + } + + /** + * Applies an expression to each element in an array and combines them into a single value. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/ + * @param BSONArray|PackedArray|ResolvesToArray|array $input Can be any valid expression that resolves to an array. + * If the argument resolves to a value of null or refers to a missing field, $reduce returns null. + * If the argument does not resolve to an array or null nor refers to a missing field, $reduce returns an error. + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $initialValue The initial cumulative value set before in is applied to the first element of the input array. + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in A valid expression that $reduce applies to each element in the input array in left-to-right order. Wrap the input value with $reverseArray to yield the equivalent of applying the combining expression from right-to-left. + * During evaluation of the in expression, two variables will be available: + * - value is the variable that represents the cumulative value of the expression. + * - this is the variable that refers to the element being processed. + */ + public static function reduce( + PackedArray|ResolvesToArray|BSONArray|array $input, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $initialValue, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $in, + ): ReduceOperator + { + return new ReduceOperator($input, $initialValue, $in); + } + + /** + * Applies a regular expression (regex) to a string and returns information on the first matched substring. + * New in MongoDB 4.2. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFind/ + * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|non-empty-string $options + */ + public static function regexFind( + ResolvesToString|string $input, + Regex|ResolvesToString|string $regex, + Optional|string $options = Optional::Undefined, + ): RegexFindOperator + { + return new RegexFindOperator($input, $regex, $options); + } + + /** + * Applies a regular expression (regex) to a string and returns information on the all matched substrings. + * New in MongoDB 4.2. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFindAll/ + * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|non-empty-string $options + */ + public static function regexFindAll( + ResolvesToString|string $input, + Regex|ResolvesToString|string $regex, + Optional|string $options = Optional::Undefined, + ): RegexFindAllOperator + { + return new RegexFindAllOperator($input, $regex, $options); + } + + /** + * Applies a regular expression (regex) to a string and returns a boolean that indicates if a match is found or not. + * New in MongoDB 4.2. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexMatch/ + * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|non-empty-string $options + */ + public static function regexMatch( + ResolvesToString|string $input, + Regex|ResolvesToString|string $regex, + Optional|string $options = Optional::Undefined, + ): RegexMatchOperator + { + return new RegexMatchOperator($input, $regex, $options); + } + + /** + * Replaces all instances of a search string in an input string with a replacement string. + * $replaceAll is both case-sensitive and diacritic-sensitive, and ignores any collation present on a collection. + * New in MongoDB 4.4. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceAll/ + * @param ResolvesToNull|ResolvesToString|non-empty-string|null $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|non-empty-string|null $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|non-empty-string|null $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. + */ + public static function replaceAll( + ResolvesToNull|ResolvesToString|null|string $input, + ResolvesToNull|ResolvesToString|null|string $find, + ResolvesToNull|ResolvesToString|null|string $replacement, + ): ReplaceAllOperator + { + return new ReplaceAllOperator($input, $find, $replacement); + } + + /** + * Replaces the first instance of a matched string in a given input. + * New in MongoDB 4.4. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceOne/ + * @param ResolvesToNull|ResolvesToString|non-empty-string|null $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|non-empty-string|null $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|non-empty-string|null $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. + */ + public static function replaceOne( + ResolvesToNull|ResolvesToString|null|string $input, + ResolvesToNull|ResolvesToString|null|string $find, + ResolvesToNull|ResolvesToString|null|string $replacement, + ): ReplaceOneOperator + { + return new ReplaceOneOperator($input, $find, $replacement); + } + + /** + * Returns an array with the elements in reverse order. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/reverseArray/ + * @param BSONArray|PackedArray|ResolvesToArray|array $expression The argument can be any valid expression as long as it resolves to an array. + */ + public static function reverseArray(PackedArray|ResolvesToArray|BSONArray|array $expression): ReverseArrayOperator + { + return new ReverseArrayOperator($expression); + } + + /** + * Rounds a number to to a whole integer or to a specified decimal place. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/ + * @param Decimal128|Int64|ResolvesToDecimal|ResolvesToDouble|ResolvesToInt|ResolvesToLong|float|int $number Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. + * $round returns an error if the expression resolves to a non-numeric data type. + * @param Optional|ResolvesToInt|int $place Can be any valid expression that resolves to an integer between -20 and 100, exclusive. + */ + public static function round( + Decimal128|Int64|ResolvesToDecimal|ResolvesToDouble|ResolvesToInt|ResolvesToLong|float|int $number, + Optional|ResolvesToInt|int $place = Optional::Undefined, + ): RoundOperator + { + return new RoundOperator($number, $place); + } + + /** + * Removes whitespace characters, including null, or the specified characters from the end of a string. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rtrim/ + * @param ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. + * @param Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. + * If unspecified, $ltrim removes whitespace characters, including the null character. + */ + public static function rtrim( + ResolvesToString|string $input, + Optional|ResolvesToString|string $chars = Optional::Undefined, + ): RtrimOperator + { + return new RtrimOperator($input, $chars); + } + + /** + * Randomly select documents at a given rate. Although the exact number of documents selected varies on each run, the quantity chosen approximates the sample rate expressed as a percentage of the total number of documents. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sampleRate/ + * @param Int64|ResolvesToDouble|float|int $rate The selection process uses a uniform random distribution. The sample rate is a floating point number between 0 and 1, inclusive, which represents the probability that a given document will be selected as it passes through the pipeline. + * For example, a sample rate of 0.33 selects roughly one document in three. + */ + public static function sampleRate(Int64|ResolvesToDouble|float|int $rate): SampleRateOperator + { + return new SampleRateOperator($rate); + } + + /** + * Returns the seconds for a date as a number between 0 and 60 (leap seconds). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/second/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function second( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): SecondOperator + { + return new SecondOperator($date, $timezone); + } + + /** + * Returns a set with elements that appear in the first set but not in the second set; i.e. performs a relative complement of the second set relative to the first. Accepts exactly two argument expressions. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setDifference/ + * @param BSONArray|PackedArray|ResolvesToArray|array $expression1 The arguments can be any valid expression as long as they each resolve to an array. + * @param BSONArray|PackedArray|ResolvesToArray|array $expression2 The arguments can be any valid expression as long as they each resolve to an array. + */ + public static function setDifference( + PackedArray|ResolvesToArray|BSONArray|array $expression1, + PackedArray|ResolvesToArray|BSONArray|array $expression2, + ): SetDifferenceOperator + { + return new SetDifferenceOperator($expression1, $expression2); + } + + /** + * Returns true if the input sets have the same distinct elements. Accepts two or more argument expressions. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setEquals/ + * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression + */ + public static function setEquals(PackedArray|ResolvesToArray|BSONArray|array ...$expression): SetEqualsOperator + { + return new SetEqualsOperator(...$expression); + } + + /** + * Adds, updates, or removes a specified field in a document. You can use $setField to add, update, or remove fields with names that contain periods (.) or start with dollar signs ($). + * New in MongoDB 5.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/ + * @param ResolvesToString|non-empty-string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. + * @param Document|ResolvesToObject|Serializable|array|stdClass $input Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $value The value that you want to assign to field. value can be any valid expression. + * Set to $$REMOVE to remove field from the input document. + */ + public static function setField( + ResolvesToString|string $field, + Document|Serializable|ResolvesToObject|stdClass|array $input, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $value, + ): SetFieldOperator + { + return new SetFieldOperator($field, $input, $value); + } + + /** + * Returns a set with elements that appear in all of the input sets. Accepts any number of argument expressions. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/ + * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression + */ + public static function setIntersection( + PackedArray|ResolvesToArray|BSONArray|array ...$expression, + ): SetIntersectionOperator + { + return new SetIntersectionOperator(...$expression); + } + + /** + * Returns true if all elements of the first set appear in the second set, including when the first set equals the second set; i.e. not a strict subset. Accepts exactly two argument expressions. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIsSubset/ + * @param BSONArray|PackedArray|ResolvesToArray|array $expression1 + * @param BSONArray|PackedArray|ResolvesToArray|array $expression2 + */ + public static function setIsSubset( + PackedArray|ResolvesToArray|BSONArray|array $expression1, + PackedArray|ResolvesToArray|BSONArray|array $expression2, + ): SetIsSubsetOperator + { + return new SetIsSubsetOperator($expression1, $expression2); + } + + /** + * Returns a set with elements that appear in any of the input sets. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/ + * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression + */ + public static function setUnion(PackedArray|ResolvesToArray|BSONArray|array ...$expression): SetUnionOperator + { + return new SetUnionOperator(...$expression); + } + + /** + * Returns the sine of a value that is measured in radians. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sin/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression $sin takes any valid expression that resolves to a number. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the result to radians. + * By default $sin returns values as a double. $sin can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. + */ + public static function sin(Decimal128|Int64|ResolvesToNumber|float|int $expression): SinOperator + { + return new SinOperator($expression); + } + + /** + * Returns the hyperbolic sine of a value that is measured in radians. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sinh/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression $sinh takes any valid expression that resolves to a number, measured in radians. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the value to radians. + * By default $sinh returns values as a double. $sinh can also return values as a 128-bit decimal if the expression resolves to a 128-bit decimal value. + */ + public static function sinh(Decimal128|Int64|ResolvesToNumber|float|int $expression): SinhOperator + { + return new SinhOperator($expression); + } + + /** + * Returns the number of elements in the array. Accepts a single expression as argument. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/size/ + * @param BSONArray|PackedArray|ResolvesToArray|array $expression The argument for $size can be any expression as long as it resolves to an array. + */ + public static function size(PackedArray|ResolvesToArray|BSONArray|array $expression): SizeOperator + { + return new SizeOperator($expression); + } + + /** + * Returns a subset of an array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/slice/ + * @param BSONArray|PackedArray|ResolvesToArray|array $expression Any valid expression as long as it resolves to an array. + * @param ResolvesToInt|int $n Any valid expression as long as it resolves to an integer. If position is specified, n must resolve to a positive integer. + * If positive, $slice returns up to the first n elements in the array. If the position is specified, $slice returns the first n elements starting from the position. + * If negative, $slice returns up to the last n elements in the array. n cannot resolve to a negative number if is specified. + * @param Optional|ResolvesToInt|int $position Any valid expression as long as it resolves to an integer. + * If positive, $slice determines the starting position from the start of the array. If position is greater than the number of elements, the $slice returns an empty array. + * If negative, $slice determines the starting position from the end of the array. If the absolute value of the is greater than the number of elements, the starting position is the start of the array. + */ + public static function slice( + PackedArray|ResolvesToArray|BSONArray|array $expression, + ResolvesToInt|int $n, + Optional|ResolvesToInt|int $position = Optional::Undefined, + ): SliceOperator + { + return new SliceOperator($expression, $n, $position); + } + + /** + * Sorts the elements of an array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/ + * @param BSONArray|PackedArray|ResolvesToArray|array $input The array to be sorted. + * The result is null if the expression: is missing, evaluates to null, or evaluates to undefined + * If the expression evaluates to any other non-array value, the document returns an error. + * @param Document|Serializable|array|stdClass $sortBy The document specifies a sort ordering. + */ + public static function sortArray( + PackedArray|ResolvesToArray|BSONArray|array $input, + Document|Serializable|stdClass|array $sortBy, + ): SortArrayOperator + { + return new SortArrayOperator($input, $sortBy); + } + + /** + * Splits a string into substrings based on a delimiter. Returns an array of substrings. If the delimiter is not found within the string, returns an array containing the original string. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/split/ + * @param ResolvesToString|non-empty-string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|non-empty-string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. + */ + public static function split(ResolvesToString|string $string, ResolvesToString|string $delimiter): SplitOperator + { + return new SplitOperator($string, $delimiter); + } + + /** + * Calculates the square root. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sqrt/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $number The argument can be any valid expression as long as it resolves to a non-negative number. + */ + public static function sqrt(Decimal128|Int64|ResolvesToNumber|float|int $number): SqrtOperator + { + return new SqrtOperator($number); + } + + /** + * Calculates the population standard deviation of the input values. Use if the values encompass the entire population of data you want to represent and do not wish to generalize about a larger population. $stdDevPop ignores non-numeric values. + * If the values represent only a sample of a population of data from which to generalize about the population, use $stdDevSamp instead. + * Changed in MongoDB 5.0: Available in the $setWindowFields stage. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/ + * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression + */ + public static function stdDevPop(Decimal128|Int64|ResolvesToNumber|float|int ...$expression): StdDevPopOperator + { + return new StdDevPopOperator(...$expression); + } + + /** + * Calculates the sample standard deviation of the input values. Use if the values encompass a sample of a population of data from which to generalize about the population. $stdDevSamp ignores non-numeric values. + * If the values represent the entire population of data or you do not wish to generalize about a larger population, use $stdDevPop instead. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevSamp/ + * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression + */ + public static function stdDevSamp(Decimal128|Int64|ResolvesToNumber|float|int ...$expression): StdDevSampOperator + { + return new StdDevSampOperator(...$expression); + } + + /** + * Performs case-insensitive string comparison and returns: 0 if two strings are equivalent, 1 if the first string is greater than the second, and -1 if the first string is less than the second. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/strcasecmp/ + * @param ResolvesToString|non-empty-string $expression1 + * @param ResolvesToString|non-empty-string $expression2 + */ + public static function strcasecmp( + ResolvesToString|string $expression1, + ResolvesToString|string $expression2, + ): StrcasecmpOperator + { + return new StrcasecmpOperator($expression1, $expression2); + } + + /** + * Returns the number of UTF-8 encoded bytes in a string. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenBytes/ + * @param ResolvesToString|non-empty-string $expression + */ + public static function strLenBytes(ResolvesToString|string $expression): StrLenBytesOperator + { + return new StrLenBytesOperator($expression); + } + + /** + * Returns the number of UTF-8 code points in a string. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenCP/ + * @param ResolvesToString|non-empty-string $expression + */ + public static function strLenCP(ResolvesToString|string $expression): StrLenCPOperator + { + return new StrLenCPOperator($expression); + } + + /** + * Deprecated. Use $substrBytes or $substrCP. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substr/ + * @param ResolvesToString|non-empty-string $string + * @param ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". + * @param ResolvesToInt|int $length If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. + */ + public static function substr( + ResolvesToString|string $string, + ResolvesToInt|int $start, + ResolvesToInt|int $length, + ): SubstrOperator + { + return new SubstrOperator($string, $start, $length); + } + + /** + * Returns the substring of a string. Starts with the character at the specified UTF-8 byte index (zero-based) in the string and continues for the specified number of bytes. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrBytes/ + * @param ResolvesToString|non-empty-string $string + * @param ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". + * @param ResolvesToInt|int $length If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. + */ + public static function substrBytes( + ResolvesToString|string $string, + ResolvesToInt|int $start, + ResolvesToInt|int $length, + ): SubstrBytesOperator + { + return new SubstrBytesOperator($string, $start, $length); + } + + /** + * Returns the substring of a string. Starts with the character at the specified UTF-8 code point (CP) index (zero-based) in the string and continues for the number of code points specified. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrCP/ + * @param ResolvesToString|non-empty-string $string + * @param ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". + * @param ResolvesToInt|int $length If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. + */ + public static function substrCP( + ResolvesToString|string $string, + ResolvesToInt|int $start, + ResolvesToInt|int $length, + ): SubstrCPOperator + { + return new SubstrCPOperator($string, $start, $length); + } + + /** + * Returns the result of subtracting the second value from the first. If the two values are numbers, return the difference. If the two values are dates, return the difference in milliseconds. If the two values are a date and a number in milliseconds, return the resulting date. Accepts two argument expressions. If the two values are a date and a number, specify the date argument first as it is not meaningful to subtract a date from a number. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/ + * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $expression1 + * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $expression2 + */ + public static function subtract( + Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $expression1, + Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $expression2, + ): SubtractOperator + { + return new SubtractOperator($expression1, $expression2); + } + + /** + * Returns a sum of numerical values. Ignores non-numeric values. + * Changed in MongoDB 5.0: Available in the $setWindowFields stage. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/ + * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression + */ + public static function sum(Decimal128|Int64|ResolvesToNumber|float|int ...$expression): SumOperator + { + return new SumOperator(...$expression); + } + + /** + * Evaluates a series of case expressions. When it finds an expression which evaluates to true, $switch executes a specified expression and breaks out of the control flow. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/switch/ + * @param BSONArray|PackedArray|array $branches An array of control branch documents. Each branch is a document with the following fields: + * - case Can be any valid expression that resolves to a boolean. If the result is not a boolean, it is coerced to a boolean value. More information about how MongoDB evaluates expressions as either true or false can be found here. + * - then Can be any valid expression. + * The branches array must contain at least one branch document. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $default The path to take if no branch case expression evaluates to true. + * Although optional, if default is unspecified and no branch case evaluates to true, $switch returns an error. + */ + public static function switch( + PackedArray|BSONArray|array $branches, + Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $default = Optional::Undefined, + ): SwitchOperator + { + return new SwitchOperator($branches, $default); + } + + /** + * Returns the tangent of a value that is measured in radians. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/tan/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression $tan takes any valid expression that resolves to a number. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the result to radians. + * By default $tan returns values as a double. $tan can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. + */ + public static function tan(Decimal128|Int64|ResolvesToNumber|float|int $expression): TanOperator + { + return new TanOperator($expression); + } + + /** + * Returns the hyperbolic tangent of a value that is measured in radians. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/tanh/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression $tanh takes any valid expression that resolves to a number, measured in radians. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the value to radians. + * By default $tanh returns values as a double. $tanh can also return values as a 128-bit decimal if the expression resolves to a 128-bit decimal value. + */ + public static function tanh(Decimal128|Int64|ResolvesToNumber|float|int $expression): TanhOperator + { + return new TanhOperator($expression); + } + + /** + * Converts value to a boolean. + * New in MongoDB 4.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toBool/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function toBool( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): ToBoolOperator + { + return new ToBoolOperator($expression); + } + + /** + * Converts value to a Date. + * New in MongoDB 4.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDate/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function toDate( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): ToDateOperator + { + return new ToDateOperator($expression); + } + + /** + * Converts value to a Decimal128. + * New in MongoDB 4.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDecimal/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function toDecimal( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): ToDecimalOperator + { + return new ToDecimalOperator($expression); + } + + /** + * Converts value to a double. + * New in MongoDB 4.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDouble/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function toDouble( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): ToDoubleOperator + { + return new ToDoubleOperator($expression); + } + + /** + * Converts value to an integer. + * New in MongoDB 4.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toInt/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function toInt( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): ToIntOperator + { + return new ToIntOperator($expression); + } + + /** + * Converts value to a long. + * New in MongoDB 4.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLong/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function toLong( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): ToLongOperator + { + return new ToLongOperator($expression); + } + + /** + * Converts a string to lowercase. Accepts a single argument expression. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLower/ + * @param ResolvesToString|non-empty-string $expression + */ + public static function toLower(ResolvesToString|string $expression): ToLowerOperator + { + return new ToLowerOperator($expression); + } + + /** + * Converts value to an ObjectId. + * New in MongoDB 4.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toObjectId/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function toObjectId( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): ToObjectIdOperator + { + return new ToObjectIdOperator($expression); + } + + /** + * Converts value to a string. + * New in MongoDB 4.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toString/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function toString( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): ToStringOperator + { + return new ToStringOperator($expression); + } + + /** + * Converts a string to uppercase. Accepts a single argument expression. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toUpper/ + * @param ResolvesToString|non-empty-string $expression + */ + public static function toUpper(ResolvesToString|string $expression): ToUpperOperator + { + return new ToUpperOperator($expression); + } + + /** + * Removes whitespace or the specified characters from the beginning and end of a string. + * New in MongoDB 4.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/trim/ + * @param ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. + * @param Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. + * If unspecified, $ltrim removes whitespace characters, including the null character. + */ + public static function trim( + ResolvesToString|string $input, + Optional|ResolvesToString|string $chars = Optional::Undefined, + ): TrimOperator + { + return new TrimOperator($input, $chars); + } + + /** + * Truncates a number to a whole integer or to a specified decimal place. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/trunc/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $number Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. + * $trunc returns an error if the expression resolves to a non-numeric data type. + * @param Optional|ResolvesToInt|int $place Can be any valid expression that resolves to an integer between -20 and 100, exclusive. e.g. -20 < place < 100. Defaults to 0. + */ + public static function trunc( + Decimal128|Int64|ResolvesToNumber|float|int $number, + Optional|ResolvesToInt|int $place = Optional::Undefined, + ): TruncOperator + { + return new TruncOperator($number, $place); + } + + /** + * Returns the incrementing ordinal from a timestamp as a long. + * New in MongoDB 5.1. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsIncrement/ + * @param ResolvesToTimestamp|Timestamp|int $expression + */ + public static function tsIncrement(Timestamp|ResolvesToTimestamp|int $expression): TsIncrementOperator + { + return new TsIncrementOperator($expression); + } + + /** + * Returns the seconds from a timestamp as a long. + * New in MongoDB 5.1. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsSecond/ + * @param ResolvesToTimestamp|Timestamp|int $expression + */ + public static function tsSecond(Timestamp|ResolvesToTimestamp|int $expression): TsSecondOperator + { + return new TsSecondOperator($expression); + } + + /** + * Return the BSON data type of the field. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/type/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function type( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): TypeOperator + { + return new TypeOperator($expression); + } + + /** + * You can use $unsetField to remove fields with names that contain periods (.) or that start with dollar signs ($). + * $unsetField is an alias for $setField using $$REMOVE to remove fields. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/ + * @param ResolvesToString|non-empty-string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. + * @param Document|ResolvesToObject|Serializable|array|stdClass $input Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. + */ + public static function unsetField( + ResolvesToString|string $field, + Document|Serializable|ResolvesToObject|stdClass|array $input, + ): UnsetFieldOperator + { + return new UnsetFieldOperator($field, $input); + } + + /** + * Returns the week number for a date as a number between 0 (the partial week that precedes the first Sunday of the year) and 53 (leap year). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/week/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function week( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): WeekOperator + { + return new WeekOperator($date, $timezone); + } + + /** + * Returns the year for a date as a number (e.g. 2014). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/year/ + * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. + * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + */ + public static function year( + ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, + Optional|ResolvesToString|string $timezone = Optional::Undefined, + ): YearOperator + { + return new YearOperator($date, $timezone); + } + + /** + * Merge two arrays together. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/zip/ + * @param BSONArray|PackedArray|ResolvesToArray|array $inputs An array of expressions that resolve to arrays. The elements of these input arrays combine to form the arrays of the output array. + * If any of the inputs arrays resolves to a value of null or refers to a missing field, $zip returns null. + * If any of the inputs arrays does not resolve to an array or null nor refers to a missing field, $zip returns an error. + * @param bool $useLongestLength A boolean which specifies whether the length of the longest array determines the number of arrays in the output array. + * The default value is false: the shortest array length determines the number of arrays in the output array. + * @param BSONArray|PackedArray|array $defaults An array of default element values to use if the input arrays have different lengths. You must specify useLongestLength: true along with this field, or else $zip will return an error. + * If useLongestLength: true but defaults is empty or not specified, $zip uses null as the default value. + * If specifying a non-empty defaults, you must specify a default for each input array or else $zip will return an error. + */ + public static function zip( + PackedArray|ResolvesToArray|BSONArray|array $inputs, + bool $useLongestLength, + PackedArray|BSONArray|array $defaults, + ): ZipOperator + { + return new ZipOperator($inputs, $useLongestLength, $defaults); + } +} diff --git a/src/Builder/Expression/FieldPath.php b/src/Builder/Expression/FieldPath.php new file mode 100644 index 000000000..b58b40461 --- /dev/null +++ b/src/Builder/Expression/FieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/FilterOperator.php b/src/Builder/Expression/FilterOperator.php new file mode 100644 index 000000000..261332a26 --- /dev/null +++ b/src/Builder/Expression/FilterOperator.php @@ -0,0 +1,72 @@ +input = $input; + $this->cond = $cond; + $this->as = $as; + $this->limit = $limit; + } + + public function getOperator(): string + { + return '$filter'; + } +} diff --git a/src/Builder/Expression/FloorOperator.php b/src/Builder/Expression/FloorOperator.php new file mode 100644 index 000000000..4dfac2de1 --- /dev/null +++ b/src/Builder/Expression/FloorOperator.php @@ -0,0 +1,40 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$floor'; + } +} diff --git a/src/Builder/Expression/FunctionOperator.php b/src/Builder/Expression/FunctionOperator.php new file mode 100644 index 000000000..68a671d7c --- /dev/null +++ b/src/Builder/Expression/FunctionOperator.php @@ -0,0 +1,59 @@ +body = $body; + if (is_array($args) && ! array_is_list($args)) { + throw new InvalidArgumentException('Expected $args argument to be a list, got an associative array.'); + } + + $this->args = $args; + $this->lang = $lang; + } + + public function getOperator(): string + { + return '$function'; + } +} diff --git a/src/Builder/Expression/GetFieldOperator.php b/src/Builder/Expression/GetFieldOperator.php new file mode 100644 index 000000000..52eeae160 --- /dev/null +++ b/src/Builder/Expression/GetFieldOperator.php @@ -0,0 +1,58 @@ +field = $field; + $this->input = $input; + } + + public function getOperator(): string + { + return '$getField'; + } +} diff --git a/src/Builder/Expression/GtOperator.php b/src/Builder/Expression/GtOperator.php new file mode 100644 index 000000000..5252fbf01 --- /dev/null +++ b/src/Builder/Expression/GtOperator.php @@ -0,0 +1,48 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$gt'; + } +} diff --git a/src/Builder/Expression/GteOperator.php b/src/Builder/Expression/GteOperator.php new file mode 100644 index 000000000..0df0de975 --- /dev/null +++ b/src/Builder/Expression/GteOperator.php @@ -0,0 +1,48 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$gte'; + } +} diff --git a/src/Builder/Expression/HourOperator.php b/src/Builder/Expression/HourOperator.php new file mode 100644 index 000000000..b53c0ed4c --- /dev/null +++ b/src/Builder/Expression/HourOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$hour'; + } +} diff --git a/src/Builder/Expression/IfNullOperator.php b/src/Builder/Expression/IfNullOperator.php new file mode 100644 index 000000000..7d70602d1 --- /dev/null +++ b/src/Builder/Expression/IfNullOperator.php @@ -0,0 +1,51 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @no-named-arguments + */ + public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$ifNull'; + } +} diff --git a/src/Builder/Expression/InOperator.php b/src/Builder/Expression/InOperator.php new file mode 100644 index 000000000..62811a191 --- /dev/null +++ b/src/Builder/Expression/InOperator.php @@ -0,0 +1,58 @@ +expression = $expression; + if (is_array($array) && ! array_is_list($array)) { + throw new InvalidArgumentException('Expected $array argument to be a list, got an associative array.'); + } + + $this->array = $array; + } + + public function getOperator(): string + { + return '$in'; + } +} diff --git a/src/Builder/Expression/IndexOfArrayOperator.php b/src/Builder/Expression/IndexOfArrayOperator.php new file mode 100644 index 000000000..9d48f69eb --- /dev/null +++ b/src/Builder/Expression/IndexOfArrayOperator.php @@ -0,0 +1,85 @@ + index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + * If unspecified, the ending index position for the search is the end of the string. + */ + public readonly Optional|ResolvesToInt|int $end; + + /** + * @param BSONArray|PackedArray|ResolvesToArray|array $array Can be any valid expression as long as it resolves to an array. + * If the array expression resolves to a value of null or refers to a field that is missing, $indexOfArray returns null. + * If the array expression does not resolve to an array or null nor refers to a missing field, $indexOfArray returns an error. + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $search + * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. + * If unspecified, the starting index position for the search is the beginning of the string. + * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + * If unspecified, the ending index position for the search is the end of the string. + */ + public function __construct( + PackedArray|ResolvesToArray|BSONArray|array $array, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $search, + Optional|ResolvesToInt|int $start = Optional::Undefined, + Optional|ResolvesToInt|int $end = Optional::Undefined, + ) { + if (is_array($array) && ! array_is_list($array)) { + throw new InvalidArgumentException('Expected $array argument to be a list, got an associative array.'); + } + + $this->array = $array; + $this->search = $search; + $this->start = $start; + $this->end = $end; + } + + public function getOperator(): string + { + return '$indexOfArray'; + } +} diff --git a/src/Builder/Expression/IndexOfBytesOperator.php b/src/Builder/Expression/IndexOfBytesOperator.php new file mode 100644 index 000000000..fe696a5f1 --- /dev/null +++ b/src/Builder/Expression/IndexOfBytesOperator.php @@ -0,0 +1,72 @@ + index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + * If unspecified, the ending index position for the search is the end of the string. + */ + public readonly Optional|ResolvesToInt|int $end; + + /** + * @param ResolvesToString|non-empty-string $string Can be any valid expression as long as it resolves to a string. + * If the string expression resolves to a value of null or refers to a field that is missing, $indexOfBytes returns null. + * If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfBytes returns an error. + * @param ResolvesToString|non-empty-string $substring Can be any valid expression as long as it resolves to a string. + * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. + * If unspecified, the starting index position for the search is the beginning of the string. + * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + * If unspecified, the ending index position for the search is the end of the string. + */ + public function __construct( + ResolvesToString|string $string, + ResolvesToString|string $substring, + Optional|ResolvesToInt|int $start = Optional::Undefined, + Optional|ResolvesToInt|int $end = Optional::Undefined, + ) { + $this->string = $string; + $this->substring = $substring; + $this->start = $start; + $this->end = $end; + } + + public function getOperator(): string + { + return '$indexOfBytes'; + } +} diff --git a/src/Builder/Expression/IndexOfCPOperator.php b/src/Builder/Expression/IndexOfCPOperator.php new file mode 100644 index 000000000..e6c5632f6 --- /dev/null +++ b/src/Builder/Expression/IndexOfCPOperator.php @@ -0,0 +1,72 @@ + index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + * If unspecified, the ending index position for the search is the end of the string. + */ + public readonly Optional|ResolvesToInt|int $end; + + /** + * @param ResolvesToString|non-empty-string $string Can be any valid expression as long as it resolves to a string. + * If the string expression resolves to a value of null or refers to a field that is missing, $indexOfCP returns null. + * If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfCP returns an error. + * @param ResolvesToString|non-empty-string $substring Can be any valid expression as long as it resolves to a string. + * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. + * If unspecified, the starting index position for the search is the beginning of the string. + * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. + * If unspecified, the ending index position for the search is the end of the string. + */ + public function __construct( + ResolvesToString|string $string, + ResolvesToString|string $substring, + Optional|ResolvesToInt|int $start = Optional::Undefined, + Optional|ResolvesToInt|int $end = Optional::Undefined, + ) { + $this->string = $string; + $this->substring = $substring; + $this->start = $start; + $this->end = $end; + } + + public function getOperator(): string + { + return '$indexOfCP'; + } +} diff --git a/src/Builder/Expression/IntFieldPath.php b/src/Builder/Expression/IntFieldPath.php new file mode 100644 index 000000000..1e68182bf --- /dev/null +++ b/src/Builder/Expression/IntFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/IntegralOperator.php b/src/Builder/Expression/IntegralOperator.php new file mode 100644 index 000000000..97f48957c --- /dev/null +++ b/src/Builder/Expression/IntegralOperator.php @@ -0,0 +1,54 @@ +input = $input; + $this->unit = $unit; + } + + public function getOperator(): string + { + return '$integral'; + } +} diff --git a/src/Builder/Expression/IsArrayOperator.php b/src/Builder/Expression/IsArrayOperator.php new file mode 100644 index 000000000..bcde5c71a --- /dev/null +++ b/src/Builder/Expression/IsArrayOperator.php @@ -0,0 +1,51 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @no-named-arguments + */ + public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$isArray'; + } +} diff --git a/src/Builder/Expression/IsNumberOperator.php b/src/Builder/Expression/IsNumberOperator.php new file mode 100644 index 000000000..281a1a304 --- /dev/null +++ b/src/Builder/Expression/IsNumberOperator.php @@ -0,0 +1,53 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @no-named-arguments + */ + public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$isNumber'; + } +} diff --git a/src/Builder/Expression/IsoDayOfWeekOperator.php b/src/Builder/Expression/IsoDayOfWeekOperator.php new file mode 100644 index 000000000..3f9cb49dd --- /dev/null +++ b/src/Builder/Expression/IsoDayOfWeekOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$isoDayOfWeek'; + } +} diff --git a/src/Builder/Expression/IsoWeekOperator.php b/src/Builder/Expression/IsoWeekOperator.php new file mode 100644 index 000000000..17419af5b --- /dev/null +++ b/src/Builder/Expression/IsoWeekOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$isoWeek'; + } +} diff --git a/src/Builder/Expression/IsoWeekYearOperator.php b/src/Builder/Expression/IsoWeekYearOperator.php new file mode 100644 index 000000000..d228a3339 --- /dev/null +++ b/src/Builder/Expression/IsoWeekYearOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$isoWeekYear'; + } +} diff --git a/src/Builder/Expression/JavascriptFieldPath.php b/src/Builder/Expression/JavascriptFieldPath.php new file mode 100644 index 000000000..2866c9f8d --- /dev/null +++ b/src/Builder/Expression/JavascriptFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/LetOperator.php b/src/Builder/Expression/LetOperator.php new file mode 100644 index 000000000..82fde5db5 --- /dev/null +++ b/src/Builder/Expression/LetOperator.php @@ -0,0 +1,55 @@ +vars = $vars; + $this->in = $in; + } + + public function getOperator(): string + { + return '$let'; + } +} diff --git a/src/Builder/Expression/LinearFillOperator.php b/src/Builder/Expression/LinearFillOperator.php new file mode 100644 index 000000000..c01873e4b --- /dev/null +++ b/src/Builder/Expression/LinearFillOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$linearFill'; + } +} diff --git a/src/Builder/Expression/LiteralOperator.php b/src/Builder/Expression/LiteralOperator.php new file mode 100644 index 000000000..c8b97a59f --- /dev/null +++ b/src/Builder/Expression/LiteralOperator.php @@ -0,0 +1,40 @@ +value = $value; + } + + public function getOperator(): string + { + return '$literal'; + } +} diff --git a/src/Builder/Expression/LnOperator.php b/src/Builder/Expression/LnOperator.php new file mode 100644 index 000000000..43d0de15b --- /dev/null +++ b/src/Builder/Expression/LnOperator.php @@ -0,0 +1,41 @@ +, Math.E ] expression, where Math.E is a JavaScript representation for Euler's number e. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ln/ + */ +class LnOperator implements ResolvesToDouble, OperatorInterface +{ + public const ENCODE = Encode::Single; + + /** @var Decimal128|Int64|ResolvesToNumber|float|int $number Any valid expression as long as it resolves to a non-negative number. For more information on expressions, see Expressions. */ + public readonly Decimal128|Int64|ResolvesToNumber|float|int $number; + + /** + * @param Decimal128|Int64|ResolvesToNumber|float|int $number Any valid expression as long as it resolves to a non-negative number. For more information on expressions, see Expressions. + */ + public function __construct(Decimal128|Int64|ResolvesToNumber|float|int $number) + { + $this->number = $number; + } + + public function getOperator(): string + { + return '$ln'; + } +} diff --git a/src/Builder/Expression/LocfOperator.php b/src/Builder/Expression/LocfOperator.php new file mode 100644 index 000000000..bd4feef7d --- /dev/null +++ b/src/Builder/Expression/LocfOperator.php @@ -0,0 +1,43 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$locf'; + } +} diff --git a/src/Builder/Expression/Log10Operator.php b/src/Builder/Expression/Log10Operator.php new file mode 100644 index 000000000..ccdc67666 --- /dev/null +++ b/src/Builder/Expression/Log10Operator.php @@ -0,0 +1,40 @@ +number = $number; + } + + public function getOperator(): string + { + return '$log10'; + } +} diff --git a/src/Builder/Expression/LogOperator.php b/src/Builder/Expression/LogOperator.php new file mode 100644 index 000000000..8359e1a53 --- /dev/null +++ b/src/Builder/Expression/LogOperator.php @@ -0,0 +1,47 @@ +number = $number; + $this->base = $base; + } + + public function getOperator(): string + { + return '$log'; + } +} diff --git a/src/Builder/Expression/LongFieldPath.php b/src/Builder/Expression/LongFieldPath.php new file mode 100644 index 000000000..de3e98263 --- /dev/null +++ b/src/Builder/Expression/LongFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/LtOperator.php b/src/Builder/Expression/LtOperator.php new file mode 100644 index 000000000..78e4e12f0 --- /dev/null +++ b/src/Builder/Expression/LtOperator.php @@ -0,0 +1,48 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$lt'; + } +} diff --git a/src/Builder/Expression/LteOperator.php b/src/Builder/Expression/LteOperator.php new file mode 100644 index 000000000..dd60b848a --- /dev/null +++ b/src/Builder/Expression/LteOperator.php @@ -0,0 +1,48 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$lte'; + } +} diff --git a/src/Builder/Expression/LtrimOperator.php b/src/Builder/Expression/LtrimOperator.php new file mode 100644 index 000000000..944ced074 --- /dev/null +++ b/src/Builder/Expression/LtrimOperator.php @@ -0,0 +1,53 @@ +input = $input; + $this->chars = $chars; + } + + public function getOperator(): string + { + return '$ltrim'; + } +} diff --git a/src/Builder/Expression/MapOperator.php b/src/Builder/Expression/MapOperator.php new file mode 100644 index 000000000..a9b9386f3 --- /dev/null +++ b/src/Builder/Expression/MapOperator.php @@ -0,0 +1,65 @@ +input = $input; + $this->in = $in; + $this->as = $as; + } + + public function getOperator(): string + { + return '$map'; + } +} diff --git a/src/Builder/Expression/MaxNOperator.php b/src/Builder/Expression/MaxNOperator.php new file mode 100644 index 000000000..2cda849d7 --- /dev/null +++ b/src/Builder/Expression/MaxNOperator.php @@ -0,0 +1,53 @@ +input = $input; + $this->n = $n; + } + + public function getOperator(): string + { + return '$maxN'; + } +} diff --git a/src/Builder/Expression/MaxOperator.php b/src/Builder/Expression/MaxOperator.php new file mode 100644 index 000000000..1d1c49f8f --- /dev/null +++ b/src/Builder/Expression/MaxOperator.php @@ -0,0 +1,52 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @no-named-arguments + */ + public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$max'; + } +} diff --git a/src/Builder/Expression/MedianOperator.php b/src/Builder/Expression/MedianOperator.php new file mode 100644 index 000000000..0ba14a0d8 --- /dev/null +++ b/src/Builder/Expression/MedianOperator.php @@ -0,0 +1,50 @@ +input = $input; + $this->method = $method; + } + + public function getOperator(): string + { + return '$median'; + } +} diff --git a/src/Builder/Expression/MetaOperator.php b/src/Builder/Expression/MetaOperator.php new file mode 100644 index 000000000..e8b37e308 --- /dev/null +++ b/src/Builder/Expression/MetaOperator.php @@ -0,0 +1,38 @@ +keyword = $keyword; + } + + public function getOperator(): string + { + return '$meta'; + } +} diff --git a/src/Builder/Expression/MillisecondOperator.php b/src/Builder/Expression/MillisecondOperator.php new file mode 100644 index 000000000..9fbd11294 --- /dev/null +++ b/src/Builder/Expression/MillisecondOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$millisecond'; + } +} diff --git a/src/Builder/Expression/MinNOperator.php b/src/Builder/Expression/MinNOperator.php new file mode 100644 index 000000000..f47976cb5 --- /dev/null +++ b/src/Builder/Expression/MinNOperator.php @@ -0,0 +1,53 @@ +input = $input; + $this->n = $n; + } + + public function getOperator(): string + { + return '$minN'; + } +} diff --git a/src/Builder/Expression/MinOperator.php b/src/Builder/Expression/MinOperator.php new file mode 100644 index 000000000..0321bdb00 --- /dev/null +++ b/src/Builder/Expression/MinOperator.php @@ -0,0 +1,52 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @no-named-arguments + */ + public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$min'; + } +} diff --git a/src/Builder/Expression/MinuteOperator.php b/src/Builder/Expression/MinuteOperator.php new file mode 100644 index 000000000..aa961cf9a --- /dev/null +++ b/src/Builder/Expression/MinuteOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$minute'; + } +} diff --git a/src/Builder/Expression/ModOperator.php b/src/Builder/Expression/ModOperator.php new file mode 100644 index 000000000..1a01019d7 --- /dev/null +++ b/src/Builder/Expression/ModOperator.php @@ -0,0 +1,47 @@ +dividend = $dividend; + $this->divisor = $divisor; + } + + public function getOperator(): string + { + return '$mod'; + } +} diff --git a/src/Builder/Expression/MonthOperator.php b/src/Builder/Expression/MonthOperator.php new file mode 100644 index 000000000..9e3ccfd88 --- /dev/null +++ b/src/Builder/Expression/MonthOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$month'; + } +} diff --git a/src/Builder/Expression/MultiplyOperator.php b/src/Builder/Expression/MultiplyOperator.php new file mode 100644 index 000000000..d6ee6802b --- /dev/null +++ b/src/Builder/Expression/MultiplyOperator.php @@ -0,0 +1,54 @@ + ...$expression The arguments can be any valid expression as long as they resolve to numbers. + * Starting in MongoDB 6.1 you can optimize the $multiply operation. To improve performance, group references at the end of the argument list. + */ + public readonly array $expression; + + /** + * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression The arguments can be any valid expression as long as they resolve to numbers. + * Starting in MongoDB 6.1 you can optimize the $multiply operation. To improve performance, group references at the end of the argument list. + * @no-named-arguments + */ + public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$multiply'; + } +} diff --git a/src/Builder/Expression/NeOperator.php b/src/Builder/Expression/NeOperator.php new file mode 100644 index 000000000..f2d4d675d --- /dev/null +++ b/src/Builder/Expression/NeOperator.php @@ -0,0 +1,48 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$ne'; + } +} diff --git a/src/Builder/Expression/NotOperator.php b/src/Builder/Expression/NotOperator.php new file mode 100644 index 000000000..6ed151c0c --- /dev/null +++ b/src/Builder/Expression/NotOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$not'; + } +} diff --git a/src/Builder/Expression/NullFieldPath.php b/src/Builder/Expression/NullFieldPath.php new file mode 100644 index 000000000..62f021260 --- /dev/null +++ b/src/Builder/Expression/NullFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/NumberFieldPath.php b/src/Builder/Expression/NumberFieldPath.php new file mode 100644 index 000000000..45b399d09 --- /dev/null +++ b/src/Builder/Expression/NumberFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/ObjectFieldPath.php b/src/Builder/Expression/ObjectFieldPath.php new file mode 100644 index 000000000..93bd17e15 --- /dev/null +++ b/src/Builder/Expression/ObjectFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/ObjectIdFieldPath.php b/src/Builder/Expression/ObjectIdFieldPath.php new file mode 100644 index 000000000..d152e1f0e --- /dev/null +++ b/src/Builder/Expression/ObjectIdFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/ObjectToArrayOperator.php b/src/Builder/Expression/ObjectToArrayOperator.php new file mode 100644 index 000000000..1ce5c5c11 --- /dev/null +++ b/src/Builder/Expression/ObjectToArrayOperator.php @@ -0,0 +1,41 @@ +object = $object; + } + + public function getOperator(): string + { + return '$objectToArray'; + } +} diff --git a/src/Builder/Expression/OrOperator.php b/src/Builder/Expression/OrOperator.php new file mode 100644 index 000000000..2523eda44 --- /dev/null +++ b/src/Builder/Expression/OrOperator.php @@ -0,0 +1,52 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @no-named-arguments + */ + public function __construct( + Type|ResolvesToBool|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, + ) { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$or'; + } +} diff --git a/src/Builder/Expression/PercentileOperator.php b/src/Builder/Expression/PercentileOperator.php new file mode 100644 index 000000000..2eadeb6b4 --- /dev/null +++ b/src/Builder/Expression/PercentileOperator.php @@ -0,0 +1,75 @@ +input = $input; + if (is_array($p) && ! array_is_list($p)) { + throw new InvalidArgumentException('Expected $p argument to be a list, got an associative array.'); + } + + $this->p = $p; + $this->method = $method; + } + + public function getOperator(): string + { + return '$percentile'; + } +} diff --git a/src/Builder/Expression/PowOperator.php b/src/Builder/Expression/PowOperator.php new file mode 100644 index 000000000..1def31e7a --- /dev/null +++ b/src/Builder/Expression/PowOperator.php @@ -0,0 +1,47 @@ +number = $number; + $this->exponent = $exponent; + } + + public function getOperator(): string + { + return '$pow'; + } +} diff --git a/src/Builder/Expression/RadiansToDegreesOperator.php b/src/Builder/Expression/RadiansToDegreesOperator.php new file mode 100644 index 000000000..670c26d92 --- /dev/null +++ b/src/Builder/Expression/RadiansToDegreesOperator.php @@ -0,0 +1,40 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$radiansToDegrees'; + } +} diff --git a/src/Builder/Expression/RandOperator.php b/src/Builder/Expression/RandOperator.php new file mode 100644 index 000000000..dd52b877a --- /dev/null +++ b/src/Builder/Expression/RandOperator.php @@ -0,0 +1,31 @@ +start = $start; + $this->end = $end; + $this->step = $step; + } + + public function getOperator(): string + { + return '$range'; + } +} diff --git a/src/Builder/Expression/RankOperator.php b/src/Builder/Expression/RankOperator.php new file mode 100644 index 000000000..e7522ae99 --- /dev/null +++ b/src/Builder/Expression/RankOperator.php @@ -0,0 +1,32 @@ +input = $input; + $this->initialValue = $initialValue; + $this->in = $in; + } + + public function getOperator(): string + { + return '$reduce'; + } +} diff --git a/src/Builder/Expression/RegexFieldPath.php b/src/Builder/Expression/RegexFieldPath.php new file mode 100644 index 000000000..33901a9b3 --- /dev/null +++ b/src/Builder/Expression/RegexFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/RegexFindAllOperator.php b/src/Builder/Expression/RegexFindAllOperator.php new file mode 100644 index 000000000..c05bb081a --- /dev/null +++ b/src/Builder/Expression/RegexFindAllOperator.php @@ -0,0 +1,54 @@ +/. When using the regex //, you can also specify the regex options i and m (but not the s or x options) */ + public readonly Regex|ResolvesToString|string $regex; + + /** @var Optional|non-empty-string $options */ + public readonly Optional|string $options; + + /** + * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|non-empty-string $options + */ + public function __construct( + ResolvesToString|string $input, + Regex|ResolvesToString|string $regex, + Optional|string $options = Optional::Undefined, + ) { + $this->input = $input; + $this->regex = $regex; + $this->options = $options; + } + + public function getOperator(): string + { + return '$regexFindAll'; + } +} diff --git a/src/Builder/Expression/RegexFindOperator.php b/src/Builder/Expression/RegexFindOperator.php new file mode 100644 index 000000000..315a4460d --- /dev/null +++ b/src/Builder/Expression/RegexFindOperator.php @@ -0,0 +1,54 @@ +/. When using the regex //, you can also specify the regex options i and m (but not the s or x options) */ + public readonly Regex|ResolvesToString|string $regex; + + /** @var Optional|non-empty-string $options */ + public readonly Optional|string $options; + + /** + * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|non-empty-string $options + */ + public function __construct( + ResolvesToString|string $input, + Regex|ResolvesToString|string $regex, + Optional|string $options = Optional::Undefined, + ) { + $this->input = $input; + $this->regex = $regex; + $this->options = $options; + } + + public function getOperator(): string + { + return '$regexFind'; + } +} diff --git a/src/Builder/Expression/RegexMatchOperator.php b/src/Builder/Expression/RegexMatchOperator.php new file mode 100644 index 000000000..c5acfd66d --- /dev/null +++ b/src/Builder/Expression/RegexMatchOperator.php @@ -0,0 +1,54 @@ +/. When using the regex //, you can also specify the regex options i and m (but not the s or x options) */ + public readonly Regex|ResolvesToString|string $regex; + + /** @var Optional|non-empty-string $options */ + public readonly Optional|string $options; + + /** + * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|non-empty-string $options + */ + public function __construct( + ResolvesToString|string $input, + Regex|ResolvesToString|string $regex, + Optional|string $options = Optional::Undefined, + ) { + $this->input = $input; + $this->regex = $regex; + $this->options = $options; + } + + public function getOperator(): string + { + return '$regexMatch'; + } +} diff --git a/src/Builder/Expression/ReplaceAllOperator.php b/src/Builder/Expression/ReplaceAllOperator.php new file mode 100644 index 000000000..f38b4c259 --- /dev/null +++ b/src/Builder/Expression/ReplaceAllOperator.php @@ -0,0 +1,53 @@ +input = $input; + $this->find = $find; + $this->replacement = $replacement; + } + + public function getOperator(): string + { + return '$replaceAll'; + } +} diff --git a/src/Builder/Expression/ReplaceOneOperator.php b/src/Builder/Expression/ReplaceOneOperator.php new file mode 100644 index 000000000..8d005ba70 --- /dev/null +++ b/src/Builder/Expression/ReplaceOneOperator.php @@ -0,0 +1,52 @@ +input = $input; + $this->find = $find; + $this->replacement = $replacement; + } + + public function getOperator(): string + { + return '$replaceOne'; + } +} diff --git a/src/Builder/Expression/ResolvesToAny.php b/src/Builder/Expression/ResolvesToAny.php new file mode 100644 index 000000000..665564887 --- /dev/null +++ b/src/Builder/Expression/ResolvesToAny.php @@ -0,0 +1,13 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$reverseArray'; + } +} diff --git a/src/Builder/Expression/RoundOperator.php b/src/Builder/Expression/RoundOperator.php new file mode 100644 index 000000000..7f3cad53e --- /dev/null +++ b/src/Builder/Expression/RoundOperator.php @@ -0,0 +1,52 @@ +number = $number; + $this->place = $place; + } + + public function getOperator(): string + { + return '$round'; + } +} diff --git a/src/Builder/Expression/RtrimOperator.php b/src/Builder/Expression/RtrimOperator.php new file mode 100644 index 000000000..039f791c3 --- /dev/null +++ b/src/Builder/Expression/RtrimOperator.php @@ -0,0 +1,52 @@ +input = $input; + $this->chars = $chars; + } + + public function getOperator(): string + { + return '$rtrim'; + } +} diff --git a/src/Builder/Expression/SampleRateOperator.php b/src/Builder/Expression/SampleRateOperator.php new file mode 100644 index 000000000..37ca3a0a3 --- /dev/null +++ b/src/Builder/Expression/SampleRateOperator.php @@ -0,0 +1,43 @@ +rate = $rate; + } + + public function getOperator(): string + { + return '$sampleRate'; + } +} diff --git a/src/Builder/Expression/SecondOperator.php b/src/Builder/Expression/SecondOperator.php new file mode 100644 index 000000000..08587bb75 --- /dev/null +++ b/src/Builder/Expression/SecondOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$second'; + } +} diff --git a/src/Builder/Expression/SetDifferenceOperator.php b/src/Builder/Expression/SetDifferenceOperator.php new file mode 100644 index 000000000..ec7e4ff60 --- /dev/null +++ b/src/Builder/Expression/SetDifferenceOperator.php @@ -0,0 +1,59 @@ +expression1 = $expression1; + if (is_array($expression2) && ! array_is_list($expression2)) { + throw new InvalidArgumentException('Expected $expression2 argument to be a list, got an associative array.'); + } + + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$setDifference'; + } +} diff --git a/src/Builder/Expression/SetEqualsOperator.php b/src/Builder/Expression/SetEqualsOperator.php new file mode 100644 index 000000000..2c5684c54 --- /dev/null +++ b/src/Builder/Expression/SetEqualsOperator.php @@ -0,0 +1,50 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression + * @no-named-arguments + */ + public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$setEquals'; + } +} diff --git a/src/Builder/Expression/SetFieldOperator.php b/src/Builder/Expression/SetFieldOperator.php new file mode 100644 index 000000000..635d61082 --- /dev/null +++ b/src/Builder/Expression/SetFieldOperator.php @@ -0,0 +1,61 @@ +field = $field; + $this->input = $input; + $this->value = $value; + } + + public function getOperator(): string + { + return '$setField'; + } +} diff --git a/src/Builder/Expression/SetIntersectionOperator.php b/src/Builder/Expression/SetIntersectionOperator.php new file mode 100644 index 000000000..9ca5c0732 --- /dev/null +++ b/src/Builder/Expression/SetIntersectionOperator.php @@ -0,0 +1,50 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression + * @no-named-arguments + */ + public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$setIntersection'; + } +} diff --git a/src/Builder/Expression/SetIsSubsetOperator.php b/src/Builder/Expression/SetIsSubsetOperator.php new file mode 100644 index 000000000..cf2011a0e --- /dev/null +++ b/src/Builder/Expression/SetIsSubsetOperator.php @@ -0,0 +1,59 @@ +expression1 = $expression1; + if (is_array($expression2) && ! array_is_list($expression2)) { + throw new InvalidArgumentException('Expected $expression2 argument to be a list, got an associative array.'); + } + + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$setIsSubset'; + } +} diff --git a/src/Builder/Expression/SetUnionOperator.php b/src/Builder/Expression/SetUnionOperator.php new file mode 100644 index 000000000..011a93350 --- /dev/null +++ b/src/Builder/Expression/SetUnionOperator.php @@ -0,0 +1,50 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression + * @no-named-arguments + */ + public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$setUnion'; + } +} diff --git a/src/Builder/Expression/SinOperator.php b/src/Builder/Expression/SinOperator.php new file mode 100644 index 000000000..1976065f1 --- /dev/null +++ b/src/Builder/Expression/SinOperator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$sin'; + } +} diff --git a/src/Builder/Expression/SinhOperator.php b/src/Builder/Expression/SinhOperator.php new file mode 100644 index 000000000..de97f2c8c --- /dev/null +++ b/src/Builder/Expression/SinhOperator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$sinh'; + } +} diff --git a/src/Builder/Expression/SizeOperator.php b/src/Builder/Expression/SizeOperator.php new file mode 100644 index 000000000..5ea541226 --- /dev/null +++ b/src/Builder/Expression/SizeOperator.php @@ -0,0 +1,48 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$size'; + } +} diff --git a/src/Builder/Expression/SliceOperator.php b/src/Builder/Expression/SliceOperator.php new file mode 100644 index 000000000..2f4a70905 --- /dev/null +++ b/src/Builder/Expression/SliceOperator.php @@ -0,0 +1,75 @@ + is specified. + */ + public readonly ResolvesToInt|int $n; + + /** + * @var Optional|ResolvesToInt|int $position Any valid expression as long as it resolves to an integer. + * If positive, $slice determines the starting position from the start of the array. If position is greater than the number of elements, the $slice returns an empty array. + * If negative, $slice determines the starting position from the end of the array. If the absolute value of the is greater than the number of elements, the starting position is the start of the array. + */ + public readonly Optional|ResolvesToInt|int $position; + + /** + * @param BSONArray|PackedArray|ResolvesToArray|array $expression Any valid expression as long as it resolves to an array. + * @param ResolvesToInt|int $n Any valid expression as long as it resolves to an integer. If position is specified, n must resolve to a positive integer. + * If positive, $slice returns up to the first n elements in the array. If the position is specified, $slice returns the first n elements starting from the position. + * If negative, $slice returns up to the last n elements in the array. n cannot resolve to a negative number if is specified. + * @param Optional|ResolvesToInt|int $position Any valid expression as long as it resolves to an integer. + * If positive, $slice determines the starting position from the start of the array. If position is greater than the number of elements, the $slice returns an empty array. + * If negative, $slice determines the starting position from the end of the array. If the absolute value of the is greater than the number of elements, the starting position is the start of the array. + */ + public function __construct( + PackedArray|ResolvesToArray|BSONArray|array $expression, + ResolvesToInt|int $n, + Optional|ResolvesToInt|int $position = Optional::Undefined, + ) { + if (is_array($expression) && ! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression argument to be a list, got an associative array.'); + } + + $this->expression = $expression; + $this->n = $n; + $this->position = $position; + } + + public function getOperator(): string + { + return '$slice'; + } +} diff --git a/src/Builder/Expression/SortArrayOperator.php b/src/Builder/Expression/SortArrayOperator.php new file mode 100644 index 000000000..eadf4d06b --- /dev/null +++ b/src/Builder/Expression/SortArrayOperator.php @@ -0,0 +1,64 @@ +input = $input; + $this->sortBy = $sortBy; + } + + public function getOperator(): string + { + return '$sortArray'; + } +} diff --git a/src/Builder/Expression/SplitOperator.php b/src/Builder/Expression/SplitOperator.php new file mode 100644 index 000000000..b5aa192e4 --- /dev/null +++ b/src/Builder/Expression/SplitOperator.php @@ -0,0 +1,43 @@ +string = $string; + $this->delimiter = $delimiter; + } + + public function getOperator(): string + { + return '$split'; + } +} diff --git a/src/Builder/Expression/SqrtOperator.php b/src/Builder/Expression/SqrtOperator.php new file mode 100644 index 000000000..e396c986c --- /dev/null +++ b/src/Builder/Expression/SqrtOperator.php @@ -0,0 +1,40 @@ +number = $number; + } + + public function getOperator(): string + { + return '$sqrt'; + } +} diff --git a/src/Builder/Expression/StdDevPopOperator.php b/src/Builder/Expression/StdDevPopOperator.php new file mode 100644 index 000000000..2df167aa4 --- /dev/null +++ b/src/Builder/Expression/StdDevPopOperator.php @@ -0,0 +1,52 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression + * @no-named-arguments + */ + public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$stdDevPop'; + } +} diff --git a/src/Builder/Expression/StdDevSampOperator.php b/src/Builder/Expression/StdDevSampOperator.php new file mode 100644 index 000000000..af22c7219 --- /dev/null +++ b/src/Builder/Expression/StdDevSampOperator.php @@ -0,0 +1,51 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression + * @no-named-arguments + */ + public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$stdDevSamp'; + } +} diff --git a/src/Builder/Expression/StrLenBytesOperator.php b/src/Builder/Expression/StrLenBytesOperator.php new file mode 100644 index 000000000..e12308161 --- /dev/null +++ b/src/Builder/Expression/StrLenBytesOperator.php @@ -0,0 +1,38 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$strLenBytes'; + } +} diff --git a/src/Builder/Expression/StrLenCPOperator.php b/src/Builder/Expression/StrLenCPOperator.php new file mode 100644 index 000000000..6dec4b7ae --- /dev/null +++ b/src/Builder/Expression/StrLenCPOperator.php @@ -0,0 +1,38 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$strLenCP'; + } +} diff --git a/src/Builder/Expression/StrcasecmpOperator.php b/src/Builder/Expression/StrcasecmpOperator.php new file mode 100644 index 000000000..013f7cb79 --- /dev/null +++ b/src/Builder/Expression/StrcasecmpOperator.php @@ -0,0 +1,43 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$strcasecmp'; + } +} diff --git a/src/Builder/Expression/StringFieldPath.php b/src/Builder/Expression/StringFieldPath.php new file mode 100644 index 000000000..137aab741 --- /dev/null +++ b/src/Builder/Expression/StringFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/SubstrBytesOperator.php b/src/Builder/Expression/SubstrBytesOperator.php new file mode 100644 index 000000000..7b8803ab1 --- /dev/null +++ b/src/Builder/Expression/SubstrBytesOperator.php @@ -0,0 +1,48 @@ +string = $string; + $this->start = $start; + $this->length = $length; + } + + public function getOperator(): string + { + return '$substrBytes'; + } +} diff --git a/src/Builder/Expression/SubstrCPOperator.php b/src/Builder/Expression/SubstrCPOperator.php new file mode 100644 index 000000000..6f32d3d26 --- /dev/null +++ b/src/Builder/Expression/SubstrCPOperator.php @@ -0,0 +1,48 @@ +string = $string; + $this->start = $start; + $this->length = $length; + } + + public function getOperator(): string + { + return '$substrCP'; + } +} diff --git a/src/Builder/Expression/SubstrOperator.php b/src/Builder/Expression/SubstrOperator.php new file mode 100644 index 000000000..c432f4aaf --- /dev/null +++ b/src/Builder/Expression/SubstrOperator.php @@ -0,0 +1,48 @@ +string = $string; + $this->start = $start; + $this->length = $length; + } + + public function getOperator(): string + { + return '$substr'; + } +} diff --git a/src/Builder/Expression/SubtractOperator.php b/src/Builder/Expression/SubtractOperator.php new file mode 100644 index 000000000..d707c8153 --- /dev/null +++ b/src/Builder/Expression/SubtractOperator.php @@ -0,0 +1,48 @@ +expression1 = $expression1; + $this->expression2 = $expression2; + } + + public function getOperator(): string + { + return '$subtract'; + } +} diff --git a/src/Builder/Expression/SumOperator.php b/src/Builder/Expression/SumOperator.php new file mode 100644 index 000000000..2666e0ce8 --- /dev/null +++ b/src/Builder/Expression/SumOperator.php @@ -0,0 +1,51 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression + * @no-named-arguments + */ + public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$sum'; + } +} diff --git a/src/Builder/Expression/SwitchOperator.php b/src/Builder/Expression/SwitchOperator.php new file mode 100644 index 000000000..93e942dfb --- /dev/null +++ b/src/Builder/Expression/SwitchOperator.php @@ -0,0 +1,71 @@ +branches = $branches; + $this->default = $default; + } + + public function getOperator(): string + { + return '$switch'; + } +} diff --git a/src/Builder/Expression/TanOperator.php b/src/Builder/Expression/TanOperator.php new file mode 100644 index 000000000..75bc2e00a --- /dev/null +++ b/src/Builder/Expression/TanOperator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$tan'; + } +} diff --git a/src/Builder/Expression/TanhOperator.php b/src/Builder/Expression/TanhOperator.php new file mode 100644 index 000000000..d78a3d42b --- /dev/null +++ b/src/Builder/Expression/TanhOperator.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$tanh'; + } +} diff --git a/src/Builder/Expression/TimestampFieldPath.php b/src/Builder/Expression/TimestampFieldPath.php new file mode 100644 index 000000000..310d78a4b --- /dev/null +++ b/src/Builder/Expression/TimestampFieldPath.php @@ -0,0 +1,21 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/ToBoolOperator.php b/src/Builder/Expression/ToBoolOperator.php new file mode 100644 index 000000000..3aaa7089a --- /dev/null +++ b/src/Builder/Expression/ToBoolOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$toBool'; + } +} diff --git a/src/Builder/Expression/ToDateOperator.php b/src/Builder/Expression/ToDateOperator.php new file mode 100644 index 000000000..605f41b4a --- /dev/null +++ b/src/Builder/Expression/ToDateOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$toDate'; + } +} diff --git a/src/Builder/Expression/ToDecimalOperator.php b/src/Builder/Expression/ToDecimalOperator.php new file mode 100644 index 000000000..7af799af0 --- /dev/null +++ b/src/Builder/Expression/ToDecimalOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$toDecimal'; + } +} diff --git a/src/Builder/Expression/ToDoubleOperator.php b/src/Builder/Expression/ToDoubleOperator.php new file mode 100644 index 000000000..2e78b0707 --- /dev/null +++ b/src/Builder/Expression/ToDoubleOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$toDouble'; + } +} diff --git a/src/Builder/Expression/ToIntOperator.php b/src/Builder/Expression/ToIntOperator.php new file mode 100644 index 000000000..d74106a57 --- /dev/null +++ b/src/Builder/Expression/ToIntOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$toInt'; + } +} diff --git a/src/Builder/Expression/ToLongOperator.php b/src/Builder/Expression/ToLongOperator.php new file mode 100644 index 000000000..5eadcc6ae --- /dev/null +++ b/src/Builder/Expression/ToLongOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$toLong'; + } +} diff --git a/src/Builder/Expression/ToLowerOperator.php b/src/Builder/Expression/ToLowerOperator.php new file mode 100644 index 000000000..1ab21491c --- /dev/null +++ b/src/Builder/Expression/ToLowerOperator.php @@ -0,0 +1,38 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$toLower'; + } +} diff --git a/src/Builder/Expression/ToObjectIdOperator.php b/src/Builder/Expression/ToObjectIdOperator.php new file mode 100644 index 000000000..57dbae3c6 --- /dev/null +++ b/src/Builder/Expression/ToObjectIdOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$toObjectId'; + } +} diff --git a/src/Builder/Expression/ToStringOperator.php b/src/Builder/Expression/ToStringOperator.php new file mode 100644 index 000000000..f7ec7c077 --- /dev/null +++ b/src/Builder/Expression/ToStringOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$toString'; + } +} diff --git a/src/Builder/Expression/ToUpperOperator.php b/src/Builder/Expression/ToUpperOperator.php new file mode 100644 index 000000000..e943a61b0 --- /dev/null +++ b/src/Builder/Expression/ToUpperOperator.php @@ -0,0 +1,38 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$toUpper'; + } +} diff --git a/src/Builder/Expression/TrimOperator.php b/src/Builder/Expression/TrimOperator.php new file mode 100644 index 000000000..6c6ca574a --- /dev/null +++ b/src/Builder/Expression/TrimOperator.php @@ -0,0 +1,53 @@ +input = $input; + $this->chars = $chars; + } + + public function getOperator(): string + { + return '$trim'; + } +} diff --git a/src/Builder/Expression/TruncOperator.php b/src/Builder/Expression/TruncOperator.php new file mode 100644 index 000000000..926d2e11a --- /dev/null +++ b/src/Builder/Expression/TruncOperator.php @@ -0,0 +1,52 @@ +number = $number; + $this->place = $place; + } + + public function getOperator(): string + { + return '$trunc'; + } +} diff --git a/src/Builder/Expression/TsIncrementOperator.php b/src/Builder/Expression/TsIncrementOperator.php new file mode 100644 index 000000000..3bf5a933a --- /dev/null +++ b/src/Builder/Expression/TsIncrementOperator.php @@ -0,0 +1,40 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$tsIncrement'; + } +} diff --git a/src/Builder/Expression/TsSecondOperator.php b/src/Builder/Expression/TsSecondOperator.php new file mode 100644 index 000000000..c0dcbe631 --- /dev/null +++ b/src/Builder/Expression/TsSecondOperator.php @@ -0,0 +1,40 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$tsSecond'; + } +} diff --git a/src/Builder/Expression/TypeOperator.php b/src/Builder/Expression/TypeOperator.php new file mode 100644 index 000000000..d1e134b6e --- /dev/null +++ b/src/Builder/Expression/TypeOperator.php @@ -0,0 +1,41 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$type'; + } +} diff --git a/src/Builder/Expression/UnsetFieldOperator.php b/src/Builder/Expression/UnsetFieldOperator.php new file mode 100644 index 000000000..df1a20d5b --- /dev/null +++ b/src/Builder/Expression/UnsetFieldOperator.php @@ -0,0 +1,49 @@ +field = $field; + $this->input = $input; + } + + public function getOperator(): string + { + return '$unsetField'; + } +} diff --git a/src/Builder/Expression/Variable.php b/src/Builder/Expression/Variable.php new file mode 100644 index 000000000..99fff30cb --- /dev/null +++ b/src/Builder/Expression/Variable.php @@ -0,0 +1,19 @@ +name = $name; + } +} diff --git a/src/Builder/Expression/WeekOperator.php b/src/Builder/Expression/WeekOperator.php new file mode 100644 index 000000000..96ba4cc57 --- /dev/null +++ b/src/Builder/Expression/WeekOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$week'; + } +} diff --git a/src/Builder/Expression/YearOperator.php b/src/Builder/Expression/YearOperator.php new file mode 100644 index 000000000..191975c28 --- /dev/null +++ b/src/Builder/Expression/YearOperator.php @@ -0,0 +1,49 @@ +date = $date; + $this->timezone = $timezone; + } + + public function getOperator(): string + { + return '$year'; + } +} diff --git a/src/Builder/Expression/ZipOperator.php b/src/Builder/Expression/ZipOperator.php new file mode 100644 index 000000000..4333e0c75 --- /dev/null +++ b/src/Builder/Expression/ZipOperator.php @@ -0,0 +1,81 @@ +inputs = $inputs; + $this->useLongestLength = $useLongestLength; + if (is_array($defaults) && ! array_is_list($defaults)) { + throw new InvalidArgumentException('Expected $defaults argument to be a list, got an associative array.'); + } + + $this->defaults = $defaults; + } + + public function getOperator(): string + { + return '$zip'; + } +} diff --git a/src/Builder/Pipeline.php b/src/Builder/Pipeline.php new file mode 100644 index 000000000..ff748304a --- /dev/null +++ b/src/Builder/Pipeline.php @@ -0,0 +1,54 @@ + + */ +class Pipeline implements IteratorAggregate +{ + /** @var StageInterface[] */ + private readonly array $stages; + + /** @no-named-arguments */ + public function __construct(StageInterface|Pipeline ...$stagesOrPipelines) + { + if (! array_is_list($stagesOrPipelines)) { + throw new InvalidArgumentException('Named arguments are not supported for pipelines'); + } + + $stages = []; + + foreach ($stagesOrPipelines as $stageOrPipeline) { + if ($stageOrPipeline instanceof Pipeline) { + $stages = array_merge($stages, $stageOrPipeline->stages); + } else { + $stages[] = $stageOrPipeline; + } + } + + $this->stages = $stages; + } + + /** @return Traversable */ + public function getIterator(): Traversable + { + return new ArrayIterator($this->stages); + } +} diff --git a/src/Builder/Projection.php b/src/Builder/Projection.php new file mode 100644 index 000000000..65430e385 --- /dev/null +++ b/src/Builder/Projection.php @@ -0,0 +1,17 @@ +query = $query; + } + + public function getOperator(): string + { + return '$elemMatch'; + } +} diff --git a/src/Builder/Projection/FactoryTrait.php b/src/Builder/Projection/FactoryTrait.php new file mode 100644 index 000000000..b25f7ffa7 --- /dev/null +++ b/src/Builder/Projection/FactoryTrait.php @@ -0,0 +1,69 @@ +input = $input; + $this->cond = $cond; + $this->as = $as; + $this->limit = $limit; + } + + public function getOperator(): string + { + return '$filter'; + } +} diff --git a/src/Builder/Projection/SliceOperator.php b/src/Builder/Projection/SliceOperator.php new file mode 100644 index 000000000..5f62076f3 --- /dev/null +++ b/src/Builder/Projection/SliceOperator.php @@ -0,0 +1,44 @@ +limit = $limit; + $this->skip = $skip; + } + + public function getOperator(): string + { + return '$slice'; + } +} diff --git a/src/Builder/Query.php b/src/Builder/Query.php new file mode 100644 index 000000000..ffba4b887 --- /dev/null +++ b/src/Builder/Query.php @@ -0,0 +1,47 @@ + ...$value */ + public readonly array $value; + + /** + * @param Type|array|bool|float|int|non-empty-string|null|stdClass ...$value + * @no-named-arguments + */ + public function __construct(Type|stdClass|array|bool|float|int|null|string ...$value) + { + if (\count($value) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $value, got %d.', 1, \count($value))); + } + if (! array_is_list($value)) { + throw new InvalidArgumentException('Expected $value arguments to be a list (array), named arguments are not supported'); + } + $this->value = $value; + } + + public function getOperator(): string + { + return '$all'; + } +} diff --git a/src/Builder/Query/AndOperator.php b/src/Builder/Query/AndOperator.php new file mode 100644 index 000000000..a58b84d58 --- /dev/null +++ b/src/Builder/Query/AndOperator.php @@ -0,0 +1,52 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Document|QueryInterface|Serializable|array|stdClass ...$expression + * @no-named-arguments + */ + public function __construct(Document|Serializable|QueryInterface|stdClass|array ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$and'; + } +} diff --git a/src/Builder/Query/BitsAllClearOperator.php b/src/Builder/Query/BitsAllClearOperator.php new file mode 100644 index 000000000..61221fac3 --- /dev/null +++ b/src/Builder/Query/BitsAllClearOperator.php @@ -0,0 +1,50 @@ +bitmask = $bitmask; + } + + public function getOperator(): string + { + return '$bitsAllClear'; + } +} diff --git a/src/Builder/Query/BitsAllSetOperator.php b/src/Builder/Query/BitsAllSetOperator.php new file mode 100644 index 000000000..9d2e1d972 --- /dev/null +++ b/src/Builder/Query/BitsAllSetOperator.php @@ -0,0 +1,50 @@ +bitmask = $bitmask; + } + + public function getOperator(): string + { + return '$bitsAllSet'; + } +} diff --git a/src/Builder/Query/BitsAnyClearOperator.php b/src/Builder/Query/BitsAnyClearOperator.php new file mode 100644 index 000000000..de3a52a89 --- /dev/null +++ b/src/Builder/Query/BitsAnyClearOperator.php @@ -0,0 +1,50 @@ +bitmask = $bitmask; + } + + public function getOperator(): string + { + return '$bitsAnyClear'; + } +} diff --git a/src/Builder/Query/BitsAnySetOperator.php b/src/Builder/Query/BitsAnySetOperator.php new file mode 100644 index 000000000..b5a5b701e --- /dev/null +++ b/src/Builder/Query/BitsAnySetOperator.php @@ -0,0 +1,50 @@ +bitmask = $bitmask; + } + + public function getOperator(): string + { + return '$bitsAnySet'; + } +} diff --git a/src/Builder/Query/BoxOperator.php b/src/Builder/Query/BoxOperator.php new file mode 100644 index 000000000..30a55d1c8 --- /dev/null +++ b/src/Builder/Query/BoxOperator.php @@ -0,0 +1,49 @@ +value = $value; + } + + public function getOperator(): string + { + return '$box'; + } +} diff --git a/src/Builder/Query/CenterOperator.php b/src/Builder/Query/CenterOperator.php new file mode 100644 index 000000000..d5bc395f0 --- /dev/null +++ b/src/Builder/Query/CenterOperator.php @@ -0,0 +1,49 @@ +value = $value; + } + + public function getOperator(): string + { + return '$center'; + } +} diff --git a/src/Builder/Query/CenterSphereOperator.php b/src/Builder/Query/CenterSphereOperator.php new file mode 100644 index 000000000..0975417a9 --- /dev/null +++ b/src/Builder/Query/CenterSphereOperator.php @@ -0,0 +1,49 @@ +value = $value; + } + + public function getOperator(): string + { + return '$centerSphere'; + } +} diff --git a/src/Builder/Query/CommentOperator.php b/src/Builder/Query/CommentOperator.php new file mode 100644 index 000000000..c99b096d9 --- /dev/null +++ b/src/Builder/Query/CommentOperator.php @@ -0,0 +1,39 @@ +comment = $comment; + } + + public function getOperator(): string + { + return '$comment'; + } +} diff --git a/src/Builder/Query/ElemMatchOperator.php b/src/Builder/Query/ElemMatchOperator.php new file mode 100644 index 000000000..e1d665375 --- /dev/null +++ b/src/Builder/Query/ElemMatchOperator.php @@ -0,0 +1,51 @@ +query = $query; + } + + public function getOperator(): string + { + return '$elemMatch'; + } +} diff --git a/src/Builder/Query/EqOperator.php b/src/Builder/Query/EqOperator.php new file mode 100644 index 000000000..1ed738f2f --- /dev/null +++ b/src/Builder/Query/EqOperator.php @@ -0,0 +1,41 @@ +value = $value; + } + + public function getOperator(): string + { + return '$eq'; + } +} diff --git a/src/Builder/Query/ExistsOperator.php b/src/Builder/Query/ExistsOperator.php new file mode 100644 index 000000000..050d1b0ed --- /dev/null +++ b/src/Builder/Query/ExistsOperator.php @@ -0,0 +1,39 @@ +exists = $exists; + } + + public function getOperator(): string + { + return '$exists'; + } +} diff --git a/src/Builder/Query/ExprOperator.php b/src/Builder/Query/ExprOperator.php new file mode 100644 index 000000000..0710b1404 --- /dev/null +++ b/src/Builder/Query/ExprOperator.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$expr'; + } +} diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php new file mode 100644 index 000000000..2d6b0862d --- /dev/null +++ b/src/Builder/Query/FactoryTrait.php @@ -0,0 +1,514 @@ +geometry = $geometry; + } + + public function getOperator(): string + { + return '$geoIntersects'; + } +} diff --git a/src/Builder/Query/GeoWithinOperator.php b/src/Builder/Query/GeoWithinOperator.php new file mode 100644 index 000000000..939e13d4a --- /dev/null +++ b/src/Builder/Query/GeoWithinOperator.php @@ -0,0 +1,43 @@ +geometry = $geometry; + } + + public function getOperator(): string + { + return '$geoWithin'; + } +} diff --git a/src/Builder/Query/GeometryOperator.php b/src/Builder/Query/GeometryOperator.php new file mode 100644 index 000000000..a1bd4bc3c --- /dev/null +++ b/src/Builder/Query/GeometryOperator.php @@ -0,0 +1,65 @@ +type = $type; + if (is_array($coordinates) && ! array_is_list($coordinates)) { + throw new InvalidArgumentException('Expected $coordinates argument to be a list, got an associative array.'); + } + + $this->coordinates = $coordinates; + $this->crs = $crs; + } + + public function getOperator(): string + { + return '$geometry'; + } +} diff --git a/src/Builder/Query/GtOperator.php b/src/Builder/Query/GtOperator.php new file mode 100644 index 000000000..515c0abdd --- /dev/null +++ b/src/Builder/Query/GtOperator.php @@ -0,0 +1,41 @@ +value = $value; + } + + public function getOperator(): string + { + return '$gt'; + } +} diff --git a/src/Builder/Query/GteOperator.php b/src/Builder/Query/GteOperator.php new file mode 100644 index 000000000..b8e06f860 --- /dev/null +++ b/src/Builder/Query/GteOperator.php @@ -0,0 +1,41 @@ +value = $value; + } + + public function getOperator(): string + { + return '$gte'; + } +} diff --git a/src/Builder/Query/InOperator.php b/src/Builder/Query/InOperator.php new file mode 100644 index 000000000..605a90b92 --- /dev/null +++ b/src/Builder/Query/InOperator.php @@ -0,0 +1,49 @@ +value = $value; + } + + public function getOperator(): string + { + return '$in'; + } +} diff --git a/src/Builder/Query/JsonSchemaOperator.php b/src/Builder/Query/JsonSchemaOperator.php new file mode 100644 index 000000000..cce8438be --- /dev/null +++ b/src/Builder/Query/JsonSchemaOperator.php @@ -0,0 +1,42 @@ +schema = $schema; + } + + public function getOperator(): string + { + return '$jsonSchema'; + } +} diff --git a/src/Builder/Query/LtOperator.php b/src/Builder/Query/LtOperator.php new file mode 100644 index 000000000..aab7dc8b8 --- /dev/null +++ b/src/Builder/Query/LtOperator.php @@ -0,0 +1,41 @@ +value = $value; + } + + public function getOperator(): string + { + return '$lt'; + } +} diff --git a/src/Builder/Query/LteOperator.php b/src/Builder/Query/LteOperator.php new file mode 100644 index 000000000..2fc005269 --- /dev/null +++ b/src/Builder/Query/LteOperator.php @@ -0,0 +1,41 @@ +value = $value; + } + + public function getOperator(): string + { + return '$lte'; + } +} diff --git a/src/Builder/Query/MaxDistanceOperator.php b/src/Builder/Query/MaxDistanceOperator.php new file mode 100644 index 000000000..864f1b60f --- /dev/null +++ b/src/Builder/Query/MaxDistanceOperator.php @@ -0,0 +1,41 @@ +value = $value; + } + + public function getOperator(): string + { + return '$maxDistance'; + } +} diff --git a/src/Builder/Query/MinDistanceOperator.php b/src/Builder/Query/MinDistanceOperator.php new file mode 100644 index 000000000..ea57b8e3b --- /dev/null +++ b/src/Builder/Query/MinDistanceOperator.php @@ -0,0 +1,40 @@ +value = $value; + } + + public function getOperator(): string + { + return '$minDistance'; + } +} diff --git a/src/Builder/Query/ModOperator.php b/src/Builder/Query/ModOperator.php new file mode 100644 index 000000000..752c9aa4c --- /dev/null +++ b/src/Builder/Query/ModOperator.php @@ -0,0 +1,44 @@ +divisor = $divisor; + $this->remainder = $remainder; + } + + public function getOperator(): string + { + return '$mod'; + } +} diff --git a/src/Builder/Query/NaturalOperator.php b/src/Builder/Query/NaturalOperator.php new file mode 100644 index 000000000..4a820a272 --- /dev/null +++ b/src/Builder/Query/NaturalOperator.php @@ -0,0 +1,32 @@ +value = $value; + } + + public function getOperator(): string + { + return '$ne'; + } +} diff --git a/src/Builder/Query/NearOperator.php b/src/Builder/Query/NearOperator.php new file mode 100644 index 000000000..b204f0ad7 --- /dev/null +++ b/src/Builder/Query/NearOperator.php @@ -0,0 +1,57 @@ +geometry = $geometry; + $this->maxDistance = $maxDistance; + $this->minDistance = $minDistance; + } + + public function getOperator(): string + { + return '$near'; + } +} diff --git a/src/Builder/Query/NearSphereOperator.php b/src/Builder/Query/NearSphereOperator.php new file mode 100644 index 000000000..1df48f6b1 --- /dev/null +++ b/src/Builder/Query/NearSphereOperator.php @@ -0,0 +1,57 @@ +geometry = $geometry; + $this->maxDistance = $maxDistance; + $this->minDistance = $minDistance; + } + + public function getOperator(): string + { + return '$nearSphere'; + } +} diff --git a/src/Builder/Query/NinOperator.php b/src/Builder/Query/NinOperator.php new file mode 100644 index 000000000..8b348b733 --- /dev/null +++ b/src/Builder/Query/NinOperator.php @@ -0,0 +1,49 @@ +value = $value; + } + + public function getOperator(): string + { + return '$nin'; + } +} diff --git a/src/Builder/Query/NorOperator.php b/src/Builder/Query/NorOperator.php new file mode 100644 index 000000000..5a8c97f6a --- /dev/null +++ b/src/Builder/Query/NorOperator.php @@ -0,0 +1,52 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Document|QueryInterface|Serializable|array|stdClass ...$expression + * @no-named-arguments + */ + public function __construct(Document|Serializable|QueryInterface|stdClass|array ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$nor'; + } +} diff --git a/src/Builder/Query/NotOperator.php b/src/Builder/Query/NotOperator.php new file mode 100644 index 000000000..37dfc0b6e --- /dev/null +++ b/src/Builder/Query/NotOperator.php @@ -0,0 +1,52 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$not'; + } +} diff --git a/src/Builder/Query/OrOperator.php b/src/Builder/Query/OrOperator.php new file mode 100644 index 000000000..decb58f1c --- /dev/null +++ b/src/Builder/Query/OrOperator.php @@ -0,0 +1,52 @@ + ...$expression */ + public readonly array $expression; + + /** + * @param Document|QueryInterface|Serializable|array|stdClass ...$expression + * @no-named-arguments + */ + public function __construct(Document|Serializable|QueryInterface|stdClass|array ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + if (! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + } + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$or'; + } +} diff --git a/src/Builder/Query/PolygonOperator.php b/src/Builder/Query/PolygonOperator.php new file mode 100644 index 000000000..6c829e076 --- /dev/null +++ b/src/Builder/Query/PolygonOperator.php @@ -0,0 +1,49 @@ +points = $points; + } + + public function getOperator(): string + { + return '$polygon'; + } +} diff --git a/src/Builder/Query/RandOperator.php b/src/Builder/Query/RandOperator.php new file mode 100644 index 000000000..468348ef8 --- /dev/null +++ b/src/Builder/Query/RandOperator.php @@ -0,0 +1,32 @@ +regex = $regex; + } + + public function getOperator(): string + { + return '$regex'; + } +} diff --git a/src/Builder/Query/SizeOperator.php b/src/Builder/Query/SizeOperator.php new file mode 100644 index 000000000..db9176c82 --- /dev/null +++ b/src/Builder/Query/SizeOperator.php @@ -0,0 +1,39 @@ +value = $value; + } + + public function getOperator(): string + { + return '$size'; + } +} diff --git a/src/Builder/Query/TextOperator.php b/src/Builder/Query/TextOperator.php new file mode 100644 index 000000000..de6bb6cb9 --- /dev/null +++ b/src/Builder/Query/TextOperator.php @@ -0,0 +1,67 @@ +search = $search; + $this->language = $language; + $this->caseSensitive = $caseSensitive; + $this->diacriticSensitive = $diacriticSensitive; + } + + public function getOperator(): string + { + return '$text'; + } +} diff --git a/src/Builder/Query/TypeOperator.php b/src/Builder/Query/TypeOperator.php new file mode 100644 index 000000000..f9b2cb03d --- /dev/null +++ b/src/Builder/Query/TypeOperator.php @@ -0,0 +1,49 @@ +type = $type; + } + + public function getOperator(): string + { + return '$type'; + } +} diff --git a/src/Builder/Query/WhereOperator.php b/src/Builder/Query/WhereOperator.php new file mode 100644 index 000000000..2a4b22056 --- /dev/null +++ b/src/Builder/Query/WhereOperator.php @@ -0,0 +1,39 @@ +function = $function; + } + + public function getOperator(): string + { + return '$where'; + } +} diff --git a/src/Builder/Stage.php b/src/Builder/Stage.php new file mode 100644 index 000000000..5565a0d81 --- /dev/null +++ b/src/Builder/Stage.php @@ -0,0 +1,32 @@ + ...$expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. */ + public readonly stdClass $expression; + + /** + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. + */ + public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) + { + if (\count($expression) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + } + foreach($expression as $key => $value) { + if (! is_string($key)) { + throw new InvalidArgumentException('Expected $expression arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); + } + } + $expression = (object) $expression; + $this->expression = $expression; + } + + public function getOperator(): string + { + return '$addFields'; + } +} diff --git a/src/Builder/Stage/BucketAutoStage.php b/src/Builder/Stage/BucketAutoStage.php new file mode 100644 index 000000000..40dd3fd91 --- /dev/null +++ b/src/Builder/Stage/BucketAutoStage.php @@ -0,0 +1,72 @@ +groupBy = $groupBy; + $this->buckets = $buckets; + $this->output = $output; + $this->granularity = $granularity; + } + + public function getOperator(): string + { + return '$bucketAuto'; + } +} diff --git a/src/Builder/Stage/BucketStage.php b/src/Builder/Stage/BucketStage.php new file mode 100644 index 000000000..53955c734 --- /dev/null +++ b/src/Builder/Stage/BucketStage.php @@ -0,0 +1,96 @@ +groupBy = $groupBy; + if (is_array($boundaries) && ! array_is_list($boundaries)) { + throw new InvalidArgumentException('Expected $boundaries argument to be a list, got an associative array.'); + } + + $this->boundaries = $boundaries; + $this->default = $default; + $this->output = $output; + } + + public function getOperator(): string + { + return '$bucket'; + } +} diff --git a/src/Builder/Stage/ChangeStreamSplitLargeEventStage.php b/src/Builder/Stage/ChangeStreamSplitLargeEventStage.php new file mode 100644 index 000000000..eb0c8ce76 --- /dev/null +++ b/src/Builder/Stage/ChangeStreamSplitLargeEventStage.php @@ -0,0 +1,33 @@ +allChangesForCluster = $allChangesForCluster; + $this->fullDocument = $fullDocument; + $this->fullDocumentBeforeChange = $fullDocumentBeforeChange; + $this->resumeAfter = $resumeAfter; + $this->showExpandedEvents = $showExpandedEvents; + $this->startAfter = $startAfter; + $this->startAtOperationTime = $startAtOperationTime; + } + + public function getOperator(): string + { + return '$changeStream'; + } +} diff --git a/src/Builder/Stage/CollStatsStage.php b/src/Builder/Stage/CollStatsStage.php new file mode 100644 index 000000000..18ea6ecfe --- /dev/null +++ b/src/Builder/Stage/CollStatsStage.php @@ -0,0 +1,42 @@ +config = $config; + } + + public function getOperator(): string + { + return '$collStats'; + } +} diff --git a/src/Builder/Stage/CountStage.php b/src/Builder/Stage/CountStage.php new file mode 100644 index 000000000..fec4a28da --- /dev/null +++ b/src/Builder/Stage/CountStage.php @@ -0,0 +1,40 @@ +field = $field; + } + + public function getOperator(): string + { + return '$count'; + } +} diff --git a/src/Builder/Stage/CurrentOpStage.php b/src/Builder/Stage/CurrentOpStage.php new file mode 100644 index 000000000..b3cbe81b0 --- /dev/null +++ b/src/Builder/Stage/CurrentOpStage.php @@ -0,0 +1,32 @@ + in an embedded document or in an array, use dot notation. + */ + public readonly string $field; + + /** @var Document|Serializable|array|stdClass $range Specification for range based densification. */ + public readonly Document|Serializable|stdClass|array $range; + + /** @var Optional|BSONArray|PackedArray|array $partitionByFields The field(s) that will be used as the partition keys. */ + public readonly Optional|PackedArray|BSONArray|array $partitionByFields; + + /** + * @param non-empty-string $field The field to densify. The values of the specified field must either be all numeric values or all dates. + * Documents that do not contain the specified field continue through the pipeline unmodified. + * To specify a in an embedded document or in an array, use dot notation. + * @param Document|Serializable|array|stdClass $range Specification for range based densification. + * @param Optional|BSONArray|PackedArray|array $partitionByFields The field(s) that will be used as the partition keys. + */ + public function __construct( + string $field, + Document|Serializable|stdClass|array $range, + Optional|PackedArray|BSONArray|array $partitionByFields = Optional::Undefined, + ) { + $this->field = $field; + $this->range = $range; + if (is_array($partitionByFields) && ! array_is_list($partitionByFields)) { + throw new InvalidArgumentException('Expected $partitionByFields argument to be a list, got an associative array.'); + } + + $this->partitionByFields = $partitionByFields; + } + + public function getOperator(): string + { + return '$densify'; + } +} diff --git a/src/Builder/Stage/DocumentsStage.php b/src/Builder/Stage/DocumentsStage.php new file mode 100644 index 000000000..1ca0a3f9c --- /dev/null +++ b/src/Builder/Stage/DocumentsStage.php @@ -0,0 +1,60 @@ +documents = $documents; + } + + public function getOperator(): string + { + return '$documents'; + } +} diff --git a/src/Builder/Stage/FacetStage.php b/src/Builder/Stage/FacetStage.php new file mode 100644 index 000000000..ee0bcf52f --- /dev/null +++ b/src/Builder/Stage/FacetStage.php @@ -0,0 +1,55 @@ + ...$facet */ + public readonly stdClass $facet; + + /** + * @param BSONArray|PackedArray|Pipeline|array ...$facet + */ + public function __construct(PackedArray|Pipeline|BSONArray|array ...$facet) + { + if (\count($facet) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $facet, got %d.', 1, \count($facet))); + } + foreach($facet as $key => $value) { + if (! is_string($key)) { + throw new InvalidArgumentException('Expected $facet arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); + } + } + $facet = (object) $facet; + $this->facet = $facet; + } + + public function getOperator(): string + { + return '$facet'; + } +} diff --git a/src/Builder/Stage/FactoryTrait.php b/src/Builder/Stage/FactoryTrait.php new file mode 100644 index 000000000..c23c1385f --- /dev/null +++ b/src/Builder/Stage/FactoryTrait.php @@ -0,0 +1,694 @@ + in an embedded document or in an array, use dot notation. + * @param Document|Serializable|array|stdClass $range Specification for range based densification. + * @param Optional|BSONArray|PackedArray|array $partitionByFields The field(s) that will be used as the partition keys. + */ + public static function densify( + string $field, + Document|Serializable|stdClass|array $range, + Optional|PackedArray|BSONArray|array $partitionByFields = Optional::Undefined, + ): DensifyStage + { + return new DensifyStage($field, $range, $partitionByFields); + } + + /** + * Returns literal documents from input values. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/documents/ + * @param BSONArray|PackedArray|ResolvesToArray|array $documents $documents accepts any valid expression that resolves to an array of objects. This includes: + * - system variables, such as $$NOW or $$SEARCH_META + * - $let expressions + * - variables in scope from $lookup expressions + * Expressions that do not resolve to a current document, like $myField or $$ROOT, will result in an error. + */ + public static function documents(PackedArray|ResolvesToArray|BSONArray|array $documents): DocumentsStage + { + return new DocumentsStage($documents); + } + + /** + * Processes multiple aggregation pipelines within a single stage on the same set of input documents. Enables the creation of multi-faceted aggregations capable of characterizing data across multiple dimensions, or facets, in a single stage. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/facet/ + * @param BSONArray|PackedArray|Pipeline|array ...$facet + */ + public static function facet(PackedArray|Pipeline|BSONArray|array ...$facet): FacetStage + { + return new FacetStage(...$facet); + } + + /** + * Populates null and missing field values within documents. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/ + * @param Document|Serializable|array|stdClass $output Specifies an object containing each field for which to fill missing values. You can specify multiple fields in the output object. + * The object name is the name of the field to fill. The object value specifies how the field is filled. + * @param Optional|Document|Serializable|array|non-empty-string|stdClass $partitionBy Specifies an expression to group the documents. In the $fill stage, a group of documents is known as a partition. + * If you omit partitionBy and partitionByFields, $fill uses one partition for the entire collection. + * partitionBy and partitionByFields are mutually exclusive. + * @param Optional|BSONArray|PackedArray|array $partitionByFields Specifies an array of fields as the compound key to group the documents. In the $fill stage, each group of documents is known as a partition. + * If you omit partitionBy and partitionByFields, $fill uses one partition for the entire collection. + * partitionBy and partitionByFields are mutually exclusive. + * @param Optional|Document|Serializable|array|stdClass $sortBy Specifies the field or fields to sort the documents within each partition. Uses the same syntax as the $sort stage. + */ + public static function fill( + Document|Serializable|stdClass|array $output, + Optional|Document|Serializable|stdClass|array|string $partitionBy = Optional::Undefined, + Optional|PackedArray|BSONArray|array $partitionByFields = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $sortBy = Optional::Undefined, + ): FillStage + { + return new FillStage($output, $partitionBy, $partitionByFields, $sortBy); + } + + /** + * Returns an ordered stream of documents based on the proximity to a geospatial point. Incorporates the functionality of $match, $sort, and $limit for geospatial data. The output documents include an additional distance field and can include a location identifier field. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/ + * @param non-empty-string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. + * @param Document|Serializable|array|stdClass $near The point for which to find the closest documents. + * @param Optional|Decimal128|Int64|float|int $distanceMultiplier The factor to multiply all distances returned by the query. For example, use the distanceMultiplier to convert radians, as returned by a spherical query, to kilometers by multiplying by the radius of the Earth. + * @param Optional|non-empty-string $includeLocs This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. + * @param Optional|non-empty-string $key Specify the geospatial indexed field to use when calculating the distance. + * @param Optional|Decimal128|Int64|float|int $maxDistance The maximum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall within the specified distance from the center point. + * Specify the distance in meters if the specified point is GeoJSON and in radians if the specified point is legacy coordinate pairs. + * @param Optional|Decimal128|Int64|float|int $minDistance The minimum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall outside the specified distance from the center point. + * Specify the distance in meters for GeoJSON data and in radians for legacy coordinate pairs. + * @param Optional|Document|QueryInterface|Serializable|array|stdClass $query Limits the results to the documents that match the query. The query syntax is the usual MongoDB read operation query syntax. + * You cannot specify a $near predicate in the query field of the $geoNear stage. + * @param Optional|bool $spherical Determines how MongoDB calculates the distance between two points: + * - When true, MongoDB uses $nearSphere semantics and calculates distances using spherical geometry. + * - When false, MongoDB uses $near semantics: spherical geometry for 2dsphere indexes and planar geometry for 2d indexes. + * Default: false. + */ + public static function geoNear( + string $distanceField, + Document|Serializable|stdClass|array $near, + Optional|Decimal128|Int64|float|int $distanceMultiplier = Optional::Undefined, + Optional|string $includeLocs = Optional::Undefined, + Optional|string $key = Optional::Undefined, + Optional|Decimal128|Int64|float|int $maxDistance = Optional::Undefined, + Optional|Decimal128|Int64|float|int $minDistance = Optional::Undefined, + Optional|Document|Serializable|QueryInterface|stdClass|array $query = Optional::Undefined, + Optional|bool $spherical = Optional::Undefined, + ): GeoNearStage + { + return new GeoNearStage($distanceField, $near, $distanceMultiplier, $includeLocs, $key, $maxDistance, $minDistance, $query, $spherical); + } + + /** + * Performs a recursive search on a collection. To each output document, adds a new array field that contains the traversal results of the recursive search for that document. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/ + * @param non-empty-string $from Target collection for the $graphLookup operation to search, recursively matching the connectFromField to the connectToField. The from collection must be in the same database as any other collections used in the operation. + * Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. + * @param BSONArray|ExpressionInterface|PackedArray|Type|array|bool|float|int|non-empty-string|null|stdClass $startWith Expression that specifies the value of the connectFromField with which to start the recursive search. Optionally, startWith may be array of values, each of which is individually followed through the traversal process. + * @param non-empty-string $connectFromField Field name whose value $graphLookup uses to recursively match against the connectToField of other documents in the collection. If the value is an array, each element is individually followed through the traversal process. + * @param non-empty-string $connectToField Field name in other documents against which to match the value of the field specified by the connectFromField parameter. + * @param non-empty-string $as Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. + * @param Optional|int $maxDepth Non-negative integral number specifying the maximum recursion depth. + * @param Optional|non-empty-string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. + * @param Optional|Document|QueryInterface|Serializable|array|stdClass $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. + */ + public static function graphLookup( + string $from, + PackedArray|Type|ExpressionInterface|BSONArray|stdClass|array|bool|float|int|null|string $startWith, + string $connectFromField, + string $connectToField, + string $as, + Optional|int $maxDepth = Optional::Undefined, + Optional|string $depthField = Optional::Undefined, + Optional|Document|Serializable|QueryInterface|stdClass|array $restrictSearchWithMatch = Optional::Undefined, + ): GraphLookupStage + { + return new GraphLookupStage($from, $startWith, $connectFromField, $connectToField, $as, $maxDepth, $depthField, $restrictSearchWithMatch); + } + + /** + * Groups input documents by a specified identifier expression and applies the accumulator expression(s), if specified, to each group. Consumes all input documents and outputs one document per each distinct group. The output documents only contain the identifier field and, if specified, accumulated fields. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $_id The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. + * @param AccumulatorInterface|Document|Serializable|array|stdClass ...$field Computed using the accumulator operators. + */ + public static function group( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $_id, + Document|Serializable|AccumulatorInterface|stdClass|array ...$field, + ): GroupStage + { + return new GroupStage($_id, ...$field); + } + + /** + * Returns statistics regarding the use of each index for the collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexStats/ + */ + public static function indexStats(): IndexStatsStage + { + return new IndexStatsStage(); + } + + /** + * Passes the first n documents unmodified to the pipeline where n is the specified limit. For each input document, outputs either one document (for the first n documents) or zero documents (after the first n documents). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/limit/ + * @param int $limit + */ + public static function limit(int $limit): LimitStage + { + return new LimitStage($limit); + } + + /** + * Lists all active sessions recently in use on the currently connected mongos or mongod instance. These sessions may have not yet propagated to the system.sessions collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listLocalSessions/ + * @param Optional|BSONArray|PackedArray|array $users Returns all sessions for the specified users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster to list sessions for other users. + * @param Optional|bool $allUsers Returns all sessions for all users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster. + */ + public static function listLocalSessions( + Optional|PackedArray|BSONArray|array $users = Optional::Undefined, + Optional|bool $allUsers = Optional::Undefined, + ): ListLocalSessionsStage + { + return new ListLocalSessionsStage($users, $allUsers); + } + + /** + * Lists sampled queries for all collections or a specific collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSampledQueries/ + * @param Optional|non-empty-string $namespace + */ + public static function listSampledQueries( + Optional|string $namespace = Optional::Undefined, + ): ListSampledQueriesStage + { + return new ListSampledQueriesStage($namespace); + } + + /** + * Returns information about existing Atlas Search indexes on a specified collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/ + * @param Optional|non-empty-string $id The id of the index to return information about. + * @param Optional|non-empty-string $name The name of the index to return information about. + */ + public static function listSearchIndexes( + Optional|string $id = Optional::Undefined, + Optional|string $name = Optional::Undefined, + ): ListSearchIndexesStage + { + return new ListSearchIndexesStage($id, $name); + } + + /** + * Lists all sessions that have been active long enough to propagate to the system.sessions collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSessions/ + * @param Optional|BSONArray|PackedArray|array $users Returns all sessions for the specified users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster to list sessions for other users. + * @param Optional|bool $allUsers Returns all sessions for all users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster. + */ + public static function listSessions( + Optional|PackedArray|BSONArray|array $users = Optional::Undefined, + Optional|bool $allUsers = Optional::Undefined, + ): ListSessionsStage + { + return new ListSessionsStage($users, $allUsers); + } + + /** + * Performs a left outer join to another collection in the same database to filter in documents from the "joined" collection for processing. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/ + * @param non-empty-string $as Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten. + * @param Optional|non-empty-string $from Specifies the collection in the same database to perform the join with. + * from is optional, you can use a $documents stage in a $lookup stage instead. For an example, see Use a $documents Stage in a $lookup Stage. + * Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. + * @param Optional|non-empty-string $localField Specifies the field from the documents input to the $lookup stage. $lookup performs an equality match on the localField to the foreignField from the documents of the from collection. If an input document does not contain the localField, the $lookup treats the field as having a value of null for matching purposes. + * @param Optional|non-empty-string $foreignField Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. + * @param Optional|Document|Serializable|array|stdClass $let Specifies variables to use in the pipeline stages. Use the variable expressions to access the fields from the joined collection's documents that are input to the pipeline. + * @param Optional|BSONArray|PackedArray|Pipeline|array $pipeline Specifies the pipeline to run on the joined collection. The pipeline determines the resulting documents from the joined collection. To return all documents, specify an empty pipeline []. + * The pipeline cannot include the $out stage or the $mergestage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. + * The pipeline cannot directly access the joined document fields. Instead, define variables for the joined document fields using the let option and then reference the variables in the pipeline stages. + */ + public static function lookup( + string $as, + Optional|string $from = Optional::Undefined, + Optional|string $localField = Optional::Undefined, + Optional|string $foreignField = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $let = Optional::Undefined, + Optional|PackedArray|Pipeline|BSONArray|array $pipeline = Optional::Undefined, + ): LookupStage + { + return new LookupStage($as, $from, $localField, $foreignField, $let, $pipeline); + } + + /** + * Filters the document stream to allow only matching documents to pass unmodified into the next pipeline stage. $match uses standard MongoDB queries. For each input document, outputs either one document (a match) or zero documents (no match). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/ + * @param Document|QueryInterface|Serializable|array|stdClass $query + */ + public static function match(Document|Serializable|QueryInterface|stdClass|array $query): MatchStage + { + return new MatchStage($query); + } + + /** + * Writes the resulting documents of the aggregation pipeline to a collection. The stage can incorporate (insert new documents, merge documents, replace documents, keep existing documents, fail the operation, process documents with a custom update pipeline) the results into an output collection. To use the $merge stage, it must be the last stage in the pipeline. + * New in MongoDB 4.2. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/ + * @param non-empty-string $into The output collection. + * @param Optional|BSONArray|PackedArray|array|non-empty-string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. + * @param Optional|Document|Serializable|array|stdClass $let Specifies variables for use in the whenMatched pipeline. + * @param Optional|non-empty-string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). + * @param Optional|non-empty-string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. + */ + public static function merge( + string $into, + Optional|PackedArray|BSONArray|array|string $on = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $let = Optional::Undefined, + Optional|string $whenMatched = Optional::Undefined, + Optional|string $whenNotMatched = Optional::Undefined, + ): MergeStage + { + return new MergeStage($into, $on, $let, $whenMatched, $whenNotMatched); + } + + /** + * Writes the resulting documents of the aggregation pipeline to a collection. To use the $out stage, it must be the last stage in the pipeline. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/ + * @param non-empty-string $db Target collection name to write documents from $out to. + * @param non-empty-string $coll Target database name to write documents from $out to. + * @param Optional|Document|Serializable|array|stdClass $timeseries If set, the aggregation stage will use these options to create or replace a time-series collection in the given namespace. + */ + public static function out( + string $db, + string $coll, + Optional|Document|Serializable|stdClass|array $timeseries = Optional::Undefined, + ): OutStage + { + return new OutStage($db, $coll, $timeseries); + } + + /** + * Returns plan cache information for a collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/planCacheStats/ + */ + public static function planCacheStats(): PlanCacheStatsStage + { + return new PlanCacheStatsStage(); + } + + /** + * Reshapes each document in the stream, such as by adding new fields or removing existing fields. For each input document, outputs one document. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/ + * @param Document|ProjectionInterface|ResolvesToBool|Serializable|array|bool|int|stdClass ...$specification + */ + public static function project( + Document|Serializable|ResolvesToBool|ProjectionInterface|stdClass|array|bool|int ...$specification, + ): ProjectStage + { + return new ProjectStage(...$specification); + } + + /** + * Reshapes each document in the stream by restricting the content for each document based on information stored in the documents themselves. Incorporates the functionality of $project and $match. Can be used to implement field level redaction. For each input document, outputs either one or zero documents. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function redact( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): RedactStage + { + return new RedactStage($expression); + } + + /** + * Replaces a document with the specified embedded document. The operation replaces all existing fields in the input document, including the _id field. Specify a document embedded in the input document to promote the embedded document to the top level. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/ + * @param Document|ResolvesToObject|Serializable|array|stdClass $newRoot + */ + public static function replaceRoot( + Document|Serializable|ResolvesToObject|stdClass|array $newRoot, + ): ReplaceRootStage + { + return new ReplaceRootStage($newRoot); + } + + /** + * Replaces a document with the specified embedded document. The operation replaces all existing fields in the input document, including the _id field. Specify a document embedded in the input document to promote the embedded document to the top level. + * Alias for $replaceRoot. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/ + * @param Document|ResolvesToObject|Serializable|array|stdClass $expression + */ + public static function replaceWith( + Document|Serializable|ResolvesToObject|stdClass|array $expression, + ): ReplaceWithStage + { + return new ReplaceWithStage($expression); + } + + /** + * Randomly selects the specified number of documents from its input. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/ + * @param int $size The number of documents to randomly select. + */ + public static function sample(int $size): SampleStage + { + return new SampleStage($size); + } + + /** + * Performs a full-text search of the field or fields in an Atlas collection. + * NOTE: $search is only available for MongoDB Atlas clusters, and is not available for self-managed deployments. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/search/ + * @param Document|Serializable|array|stdClass $search + */ + public static function search(Document|Serializable|stdClass|array $search): SearchStage + { + return new SearchStage($search); + } + + /** + * Returns different types of metadata result documents for the Atlas Search query against an Atlas collection. + * NOTE: $searchMeta is only available for MongoDB Atlas clusters running MongoDB v4.4.9 or higher, and is not available for self-managed deployments. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/searchMeta/ + * @param Document|Serializable|array|stdClass $meta + */ + public static function searchMeta(Document|Serializable|stdClass|array $meta): SearchMetaStage + { + return new SearchMetaStage($meta); + } + + /** + * Adds new fields to documents. Outputs documents that contain all existing fields from the input documents and newly added fields. + * Alias for $addFields. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$field + */ + public static function set(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$field): SetStage + { + return new SetStage(...$field); + } + + /** + * Groups documents into windows and applies one or more operators to the documents in each window. + * New in MongoDB 5.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. + * @param Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. + * @param Document|Serializable|array|stdClass $output Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. + * A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. + * @param Optional|Document|Serializable|array|stdClass $window Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. + */ + public static function setWindowFields( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy, + Document|Serializable|stdClass|array $sortBy, + Document|Serializable|stdClass|array $output, + Optional|Document|Serializable|stdClass|array $window = Optional::Undefined, + ): SetWindowFieldsStage + { + return new SetWindowFieldsStage($partitionBy, $sortBy, $output, $window); + } + + /** + * Provides data and size distribution information on sharded collections. + * New in MongoDB 6.0.3. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/shardedDataDistribution/ + */ + public static function shardedDataDistribution(): ShardedDataDistributionStage + { + return new ShardedDataDistributionStage(); + } + + /** + * Skips the first n documents where n is the specified skip number and passes the remaining documents unmodified to the pipeline. For each input document, outputs either zero documents (for the first n documents) or one document (if after the first n documents). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/skip/ + * @param int $skip + */ + public static function skip(int $skip): SkipStage + { + return new SkipStage($skip); + } + + /** + * Reorders the document stream by a specified sort key. Only the order changes; the documents remain unmodified. For each input document, outputs one document. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/ + * @param Document|Serializable|array|stdClass $sort + */ + public static function sort(Document|Serializable|stdClass|array $sort): SortStage + { + return new SortStage($sort); + } + + /** + * Groups incoming documents based on the value of a specified expression, then computes the count of documents in each distinct group. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortByCount/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + */ + public static function sortByCount( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): SortByCountStage + { + return new SortByCountStage($expression); + } + + /** + * Performs a union of two collections; i.e. combines pipeline results from two collections into a single result set. + * New in MongoDB 4.4. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unionWith/ + * @param non-empty-string $coll The collection or view whose pipeline results you wish to include in the result set. + * @param Optional|BSONArray|PackedArray|Pipeline|array $pipeline An aggregation pipeline to apply to the specified coll. + * The pipeline cannot include the $out and $merge stages. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. + */ + public static function unionWith( + string $coll, + Optional|PackedArray|Pipeline|BSONArray|array $pipeline = Optional::Undefined, + ): UnionWithStage + { + return new UnionWithStage($coll, $pipeline); + } + + /** + * Removes or excludes fields from documents. + * Alias for $project stage that removes or excludes fields. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/ + * @param FieldPath|non-empty-string ...$field + */ + public static function unset(FieldPath|string ...$field): UnsetStage + { + return new UnsetStage(...$field); + } + + /** + * Deconstructs an array field from the input documents to output a document for each element. Each output document replaces the array with an element value. For each input document, outputs n documents where n is the number of array elements and can be zero for an empty array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/ + * @param ArrayFieldPath|non-empty-string $path Field path to an array field. + * @param Optional|non-empty-string $includeArrayIndex The name of a new field to hold the array index of the element. The name cannot start with a dollar sign $. + * @param Optional|bool $preserveNullAndEmptyArrays If true, if the path is null, missing, or an empty array, $unwind outputs the document. + * If false, if path is null, missing, or an empty array, $unwind does not output a document. + * The default value is false. + */ + public static function unwind( + ArrayFieldPath|string $path, + Optional|string $includeArrayIndex = Optional::Undefined, + Optional|bool $preserveNullAndEmptyArrays = Optional::Undefined, + ): UnwindStage + { + return new UnwindStage($path, $includeArrayIndex, $preserveNullAndEmptyArrays); + } +} diff --git a/src/Builder/Stage/FillStage.php b/src/Builder/Stage/FillStage.php new file mode 100644 index 000000000..00b47ea83 --- /dev/null +++ b/src/Builder/Stage/FillStage.php @@ -0,0 +1,88 @@ +output = $output; + $this->partitionBy = $partitionBy; + if (is_array($partitionByFields) && ! array_is_list($partitionByFields)) { + throw new InvalidArgumentException('Expected $partitionByFields argument to be a list, got an associative array.'); + } + + $this->partitionByFields = $partitionByFields; + $this->sortBy = $sortBy; + } + + public function getOperator(): string + { + return '$fill'; + } +} diff --git a/src/Builder/Stage/GeoNearStage.php b/src/Builder/Stage/GeoNearStage.php new file mode 100644 index 000000000..fd31577f4 --- /dev/null +++ b/src/Builder/Stage/GeoNearStage.php @@ -0,0 +1,123 @@ +distanceField = $distanceField; + $this->near = $near; + $this->distanceMultiplier = $distanceMultiplier; + $this->includeLocs = $includeLocs; + $this->key = $key; + $this->maxDistance = $maxDistance; + $this->minDistance = $minDistance; + if (is_array($query) || is_object($query)) { + $query = QueryObject::create(...$query); + } + + $this->query = $query; + $this->spherical = $spherical; + } + + public function getOperator(): string + { + return '$geoNear'; + } +} diff --git a/src/Builder/Stage/GraphLookupStage.php b/src/Builder/Stage/GraphLookupStage.php new file mode 100644 index 000000000..9ebb01d1f --- /dev/null +++ b/src/Builder/Stage/GraphLookupStage.php @@ -0,0 +1,109 @@ +from = $from; + if (is_array($startWith) && ! array_is_list($startWith)) { + throw new InvalidArgumentException('Expected $startWith argument to be a list, got an associative array.'); + } + + $this->startWith = $startWith; + $this->connectFromField = $connectFromField; + $this->connectToField = $connectToField; + $this->as = $as; + $this->maxDepth = $maxDepth; + $this->depthField = $depthField; + if (is_array($restrictSearchWithMatch) || is_object($restrictSearchWithMatch)) { + $restrictSearchWithMatch = QueryObject::create(...$restrictSearchWithMatch); + } + + $this->restrictSearchWithMatch = $restrictSearchWithMatch; + } + + public function getOperator(): string + { + return '$graphLookup'; + } +} diff --git a/src/Builder/Stage/GroupStage.php b/src/Builder/Stage/GroupStage.php new file mode 100644 index 000000000..b6c6a11ba --- /dev/null +++ b/src/Builder/Stage/GroupStage.php @@ -0,0 +1,64 @@ + ...$field Computed using the accumulator operators. */ + public readonly stdClass $field; + + /** + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $_id The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. + * @param AccumulatorInterface|Document|Serializable|array|stdClass ...$field Computed using the accumulator operators. + */ + public function __construct( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $_id, + Document|Serializable|AccumulatorInterface|stdClass|array ...$field, + ) { + $this->_id = $_id; + if (\count($field) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); + } + foreach($field as $key => $value) { + if (! is_string($key)) { + throw new InvalidArgumentException('Expected $field arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); + } + } + $field = (object) $field; + $this->field = $field; + } + + public function getOperator(): string + { + return '$group'; + } +} diff --git a/src/Builder/Stage/IndexStatsStage.php b/src/Builder/Stage/IndexStatsStage.php new file mode 100644 index 000000000..4fca140bb --- /dev/null +++ b/src/Builder/Stage/IndexStatsStage.php @@ -0,0 +1,32 @@ +limit = $limit; + } + + public function getOperator(): string + { + return '$limit'; + } +} diff --git a/src/Builder/Stage/ListLocalSessionsStage.php b/src/Builder/Stage/ListLocalSessionsStage.php new file mode 100644 index 000000000..de60e49f4 --- /dev/null +++ b/src/Builder/Stage/ListLocalSessionsStage.php @@ -0,0 +1,57 @@ +users = $users; + $this->allUsers = $allUsers; + } + + public function getOperator(): string + { + return '$listLocalSessions'; + } +} diff --git a/src/Builder/Stage/ListSampledQueriesStage.php b/src/Builder/Stage/ListSampledQueriesStage.php new file mode 100644 index 000000000..945a21e3b --- /dev/null +++ b/src/Builder/Stage/ListSampledQueriesStage.php @@ -0,0 +1,40 @@ +namespace = $namespace; + } + + public function getOperator(): string + { + return '$listSampledQueries'; + } +} diff --git a/src/Builder/Stage/ListSearchIndexesStage.php b/src/Builder/Stage/ListSearchIndexesStage.php new file mode 100644 index 000000000..053a37ae0 --- /dev/null +++ b/src/Builder/Stage/ListSearchIndexesStage.php @@ -0,0 +1,47 @@ +id = $id; + $this->name = $name; + } + + public function getOperator(): string + { + return '$listSearchIndexes'; + } +} diff --git a/src/Builder/Stage/ListSessionsStage.php b/src/Builder/Stage/ListSessionsStage.php new file mode 100644 index 000000000..f299874db --- /dev/null +++ b/src/Builder/Stage/ListSessionsStage.php @@ -0,0 +1,57 @@ +users = $users; + $this->allUsers = $allUsers; + } + + public function getOperator(): string + { + return '$listSessions'; + } +} diff --git a/src/Builder/Stage/LookupStage.php b/src/Builder/Stage/LookupStage.php new file mode 100644 index 000000000..de759aad2 --- /dev/null +++ b/src/Builder/Stage/LookupStage.php @@ -0,0 +1,97 @@ +as = $as; + $this->from = $from; + $this->localField = $localField; + $this->foreignField = $foreignField; + $this->let = $let; + if (is_array($pipeline) && ! array_is_list($pipeline)) { + throw new InvalidArgumentException('Expected $pipeline argument to be a list, got an associative array.'); + } + + $this->pipeline = $pipeline; + } + + public function getOperator(): string + { + return '$lookup'; + } +} diff --git a/src/Builder/Stage/MatchStage.php b/src/Builder/Stage/MatchStage.php new file mode 100644 index 000000000..189451d94 --- /dev/null +++ b/src/Builder/Stage/MatchStage.php @@ -0,0 +1,51 @@ +query = $query; + } + + public function getOperator(): string + { + return '$match'; + } +} diff --git a/src/Builder/Stage/MergeStage.php b/src/Builder/Stage/MergeStage.php new file mode 100644 index 000000000..0b2aff165 --- /dev/null +++ b/src/Builder/Stage/MergeStage.php @@ -0,0 +1,79 @@ +into = $into; + if (is_array($on) && ! array_is_list($on)) { + throw new InvalidArgumentException('Expected $on argument to be a list, got an associative array.'); + } + + $this->on = $on; + $this->let = $let; + $this->whenMatched = $whenMatched; + $this->whenNotMatched = $whenNotMatched; + } + + public function getOperator(): string + { + return '$merge'; + } +} diff --git a/src/Builder/Stage/OutStage.php b/src/Builder/Stage/OutStage.php new file mode 100644 index 000000000..8c48a1281 --- /dev/null +++ b/src/Builder/Stage/OutStage.php @@ -0,0 +1,56 @@ +db = $db; + $this->coll = $coll; + $this->timeseries = $timeseries; + } + + public function getOperator(): string + { + return '$out'; + } +} diff --git a/src/Builder/Stage/PlanCacheStatsStage.php b/src/Builder/Stage/PlanCacheStatsStage.php new file mode 100644 index 000000000..98755095f --- /dev/null +++ b/src/Builder/Stage/PlanCacheStatsStage.php @@ -0,0 +1,32 @@ + ...$specification */ + public readonly stdClass $specification; + + /** + * @param Document|ProjectionInterface|ResolvesToBool|Serializable|array|bool|int|stdClass ...$specification + */ + public function __construct( + Document|Serializable|ResolvesToBool|ProjectionInterface|stdClass|array|bool|int ...$specification, + ) { + if (\count($specification) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $specification, got %d.', 1, \count($specification))); + } + foreach($specification as $key => $value) { + if (! is_string($key)) { + throw new InvalidArgumentException('Expected $specification arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); + } + } + $specification = (object) $specification; + $this->specification = $specification; + } + + public function getOperator(): string + { + return '$project'; + } +} diff --git a/src/Builder/Stage/RedactStage.php b/src/Builder/Stage/RedactStage.php new file mode 100644 index 000000000..28b1bac41 --- /dev/null +++ b/src/Builder/Stage/RedactStage.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$redact'; + } +} diff --git a/src/Builder/Stage/ReplaceRootStage.php b/src/Builder/Stage/ReplaceRootStage.php new file mode 100644 index 000000000..5c97b1f15 --- /dev/null +++ b/src/Builder/Stage/ReplaceRootStage.php @@ -0,0 +1,43 @@ +newRoot = $newRoot; + } + + public function getOperator(): string + { + return '$replaceRoot'; + } +} diff --git a/src/Builder/Stage/ReplaceWithStage.php b/src/Builder/Stage/ReplaceWithStage.php new file mode 100644 index 000000000..6741a38fc --- /dev/null +++ b/src/Builder/Stage/ReplaceWithStage.php @@ -0,0 +1,44 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$replaceWith'; + } +} diff --git a/src/Builder/Stage/SampleStage.php b/src/Builder/Stage/SampleStage.php new file mode 100644 index 000000000..9cf433eb5 --- /dev/null +++ b/src/Builder/Stage/SampleStage.php @@ -0,0 +1,39 @@ +size = $size; + } + + public function getOperator(): string + { + return '$sample'; + } +} diff --git a/src/Builder/Stage/SearchMetaStage.php b/src/Builder/Stage/SearchMetaStage.php new file mode 100644 index 000000000..c27b230eb --- /dev/null +++ b/src/Builder/Stage/SearchMetaStage.php @@ -0,0 +1,43 @@ +meta = $meta; + } + + public function getOperator(): string + { + return '$searchMeta'; + } +} diff --git a/src/Builder/Stage/SearchStage.php b/src/Builder/Stage/SearchStage.php new file mode 100644 index 000000000..3eed9c17d --- /dev/null +++ b/src/Builder/Stage/SearchStage.php @@ -0,0 +1,43 @@ +search = $search; + } + + public function getOperator(): string + { + return '$search'; + } +} diff --git a/src/Builder/Stage/SetStage.php b/src/Builder/Stage/SetStage.php new file mode 100644 index 000000000..be18af019 --- /dev/null +++ b/src/Builder/Stage/SetStage.php @@ -0,0 +1,55 @@ + ...$field */ + public readonly stdClass $field; + + /** + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$field + */ + public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$field) + { + if (\count($field) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); + } + foreach($field as $key => $value) { + if (! is_string($key)) { + throw new InvalidArgumentException('Expected $field arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); + } + } + $field = (object) $field; + $this->field = $field; + } + + public function getOperator(): string + { + return '$set'; + } +} diff --git a/src/Builder/Stage/SetWindowFieldsStage.php b/src/Builder/Stage/SetWindowFieldsStage.php new file mode 100644 index 000000000..68ac0ab9a --- /dev/null +++ b/src/Builder/Stage/SetWindowFieldsStage.php @@ -0,0 +1,69 @@ +partitionBy = $partitionBy; + $this->sortBy = $sortBy; + $this->output = $output; + $this->window = $window; + } + + public function getOperator(): string + { + return '$setWindowFields'; + } +} diff --git a/src/Builder/Stage/ShardedDataDistributionStage.php b/src/Builder/Stage/ShardedDataDistributionStage.php new file mode 100644 index 000000000..de3c3f3a0 --- /dev/null +++ b/src/Builder/Stage/ShardedDataDistributionStage.php @@ -0,0 +1,33 @@ +skip = $skip; + } + + public function getOperator(): string + { + return '$skip'; + } +} diff --git a/src/Builder/Stage/SortByCountStage.php b/src/Builder/Stage/SortByCountStage.php new file mode 100644 index 000000000..0ff9a0184 --- /dev/null +++ b/src/Builder/Stage/SortByCountStage.php @@ -0,0 +1,42 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$sortByCount'; + } +} diff --git a/src/Builder/Stage/SortStage.php b/src/Builder/Stage/SortStage.php new file mode 100644 index 000000000..fca4d5d43 --- /dev/null +++ b/src/Builder/Stage/SortStage.php @@ -0,0 +1,42 @@ +sort = $sort; + } + + public function getOperator(): string + { + return '$sort'; + } +} diff --git a/src/Builder/Stage/UnionWithStage.php b/src/Builder/Stage/UnionWithStage.php new file mode 100644 index 000000000..f5fc7a153 --- /dev/null +++ b/src/Builder/Stage/UnionWithStage.php @@ -0,0 +1,63 @@ +coll = $coll; + if (is_array($pipeline) && ! array_is_list($pipeline)) { + throw new InvalidArgumentException('Expected $pipeline argument to be a list, got an associative array.'); + } + + $this->pipeline = $pipeline; + } + + public function getOperator(): string + { + return '$unionWith'; + } +} diff --git a/src/Builder/Stage/UnsetStage.php b/src/Builder/Stage/UnsetStage.php new file mode 100644 index 000000000..5f60a3cd3 --- /dev/null +++ b/src/Builder/Stage/UnsetStage.php @@ -0,0 +1,51 @@ + ...$field */ + public readonly array $field; + + /** + * @param FieldPath|non-empty-string ...$field + * @no-named-arguments + */ + public function __construct(FieldPath|string ...$field) + { + if (\count($field) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); + } + if (! array_is_list($field)) { + throw new InvalidArgumentException('Expected $field arguments to be a list (array), named arguments are not supported'); + } + $this->field = $field; + } + + public function getOperator(): string + { + return '$unset'; + } +} diff --git a/src/Builder/Stage/UnwindStage.php b/src/Builder/Stage/UnwindStage.php new file mode 100644 index 000000000..bb7dded0c --- /dev/null +++ b/src/Builder/Stage/UnwindStage.php @@ -0,0 +1,60 @@ +path = $path; + $this->includeArrayIndex = $includeArrayIndex; + $this->preserveNullAndEmptyArrays = $preserveNullAndEmptyArrays; + } + + public function getOperator(): string + { + return '$unwind'; + } +} diff --git a/src/Builder/Type/AccumulatorInterface.php b/src/Builder/Type/AccumulatorInterface.php new file mode 100644 index 000000000..c3aaad2af --- /dev/null +++ b/src/Builder/Type/AccumulatorInterface.php @@ -0,0 +1,14 @@ + $fieldQueries */ + public readonly array $fieldQueries, + ) { + foreach ($fieldQueries as $fieldQuery) { + if (! $fieldQuery instanceof FieldQueryInterface && ! $fieldQuery instanceof Serializable && ! is_array($fieldQuery) && ! $fieldQuery instanceof stdClass) { + throw new InvalidArgumentException(sprintf('Expected filters to be a list of %s, %s, array or stdClass, %s given.', FieldQueryInterface::class, Document::class, get_debug_type($fieldQuery))); + } + } + } +} diff --git a/src/Builder/Type/Encode.php b/src/Builder/Type/Encode.php new file mode 100644 index 000000000..76a2e06b9 --- /dev/null +++ b/src/Builder/Type/Encode.php @@ -0,0 +1,19 @@ +operator = $operator; + + $window = null; + if ($documents !== Optional::Undefined) { + if (! array_is_list($documents) || ! count($documents) === 2) { + throw new InvalidArgumentException('Expected $documents argument to be a list of 2 string or int.'); + } + + $window ??= new stdClass(); + $window->documents = $documents; + } + + if ($range !== Optional::Undefined) { + if (! array_is_list($range) || ! count($range) === 2) { + throw new InvalidArgumentException('Expected $range argument to be a list of 2 string or numeric.'); + } + + $window ??= new stdClass(); + $window->range = $range; + } + + if ($unit !== Optional::Undefined) { + $window ??= new stdClass(); + $window->unit = $unit; + } + + $this->window = $window ?? Optional::Undefined; + } +} diff --git a/src/Builder/Type/ProjectionInterface.php b/src/Builder/Type/ProjectionInterface.php new file mode 100644 index 000000000..e411dde60 --- /dev/null +++ b/src/Builder/Type/ProjectionInterface.php @@ -0,0 +1,14 @@ + $query) { + if ($query instanceof QueryInterface) { + if ($query instanceof OperatorInterface) { + if (isset($seenQueryOperators[$query->getOperator()])) { + throw new InvalidArgumentException(sprintf('Query operator "%s" cannot be used multiple times in the same query.', $query->getOperator())); + } + + $seenQueryOperators[$query->getOperator()] = true; + } + + $queries[] = $query; + continue; + } + + // Convert list of filters into $and + if (self::isListOfFilters($query)) { + if (count($query) === 1) { + $query = $query[0]; + } else { + $query = new CombinedFieldQuery($query); + } + } + + // Numbers are valid field paths, nothing to validate. + $queries[$fieldPath] = $query; + } + + $this->queries = $queries; + } + + private static function isListOfFilters(mixed $values): bool + { + if (! is_array($values) || ! array_is_list($values)) { + return false; + } + + foreach ($values as $value) { + if ($value instanceof FieldQueryInterface) { + return true; + } + } + + return false; + } +} diff --git a/src/Builder/Type/StageInterface.php b/src/Builder/Type/StageInterface.php new file mode 100644 index 000000000..2e4d4dd66 --- /dev/null +++ b/src/Builder/Type/StageInterface.php @@ -0,0 +1,14 @@ + 1, 'array.$' => 1 ] ) + * + * @psalm-suppress MoreSpecificReturnType + * @psalm-suppress LessSpecificReturnStatement + */ +function object(mixed ...$values): stdClass +{ + return (object) $values; +} diff --git a/tests/Builder/BuilderEncoderTest.php b/tests/Builder/BuilderEncoderTest.php new file mode 100644 index 000000000..fcbd55d87 --- /dev/null +++ b/tests/Builder/BuilderEncoderTest.php @@ -0,0 +1,303 @@ + ['author' => 'dave']], + ['$limit' => 1], + ]; + + $this->assertSamePipeline($expected, $pipeline); + } + + /** @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/#ascending-descending-sort */ + public function testSort(): void + { + $pipeline = new Pipeline( + Stage::sort(object(age: -1, posts: 1)), + ); + + $expected = [ + ['$sort' => ['age' => -1, 'posts' => 1]], + ]; + + $this->assertSamePipeline($expected, $pipeline); + } + + /** @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/#perform-a-count */ + public function testPerformCount(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::or( + Query::query(score: [Query::gt(70), Query::lt(90)]), + Query::query(views: Query::gte(1000)), + ), + ), + Stage::group( + _id: null, + count: Accumulator::sum(1), + ), + ); + + $expected = [ + [ + '$match' => [ + '$or' => [ + ['score' => ['$gt' => 70, '$lt' => 90]], + ['views' => ['$gte' => 1000]], + ], + ], + ], + [ + '$group' => [ + '_id' => null, + 'count' => ['$sum' => 1], + ], + ], + ]; + + $this->assertSamePipeline($expected, $pipeline); + } + + /** + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/#examples + * + * @dataProvider provideExpressionFilterLimit + */ + public function testExpressionFilter(array $limit, array $expectedLimit): void + { + $pipeline = new Pipeline( + Stage::project( + items: Projection::filter( + Expression::arrayFieldPath('items'), + Expression::gte(Expression::variable('item.price'), 100), + 'item', + ...$limit, + ), + ), + ); + + $expected = [ + [ + '$project' => [ + 'items' => [ + '$filter' => array_merge([ + 'input' => '$items', + 'as' => 'item', + 'cond' => ['$gte' => ['$$item.price', 100]], + ], $expectedLimit), + ], + ], + ], + ]; + + $this->assertSamePipeline($expected, $pipeline); + } + + public static function provideExpressionFilterLimit(): Generator + { + yield 'unspecified limit' => [ + [], + [], + ]; + + yield 'int limit' => [ + [1], + ['limit' => 1], + ]; + } + + /** @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/slice/#example */ + public function testSlice(): void + { + $pipeline = new Pipeline( + Stage::project( + name: 1, + threeFavorites: Expression::slice( + Expression::arrayFieldPath('items'), + n: 3, + ), + ), + ); + + $expected = [ + [ + '$project' => [ + 'name' => 1, + 'threeFavorites' => [ + '$slice' => ['$items', 3], + ], + ], + ], + ]; + + $this->assertSamePipeline($expected, $pipeline); + } + + /** @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-documents-window-to-obtain-cumulative-and-maximum-quantity-for-each-year */ + public function testSetWindowFields(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::year(Expression::dateFieldPath('orderDate')), + sortBy: object(orderDate: 1), + output: object( + cumulativeQuantityForYear: Accumulator::outputWindow( + Accumulator::sum(Expression::intFieldPath('quantity')), + documents: ['unbounded', 'current'], + ), + maximumQuantityForYear: Accumulator::outputWindow( + Accumulator::max(Expression::intFieldPath('quantity')), + documents: ['unbounded', 'unbounded'], + ), + ), + ), + ); + + $expected = [ + [ + '$setWindowFields' => [ + // "date" key is optional for $year, but we always add it for consistency + 'partitionBy' => ['$year' => ['date' => '$orderDate']], + 'sortBy' => ['orderDate' => 1], + 'output' => [ + 'cumulativeQuantityForYear' => [ + '$sum' => '$quantity', + 'window' => ['documents' => ['unbounded', 'current']], + ], + 'maximumQuantityForYear' => [ + '$max' => '$quantity', + 'window' => ['documents' => ['unbounded', 'unbounded']], + ], + ], + ], + ], + ]; + + $this->assertSamePipeline($expected, $pipeline); + } + + public function testUnionWith(): void + { + $pipeline = new Pipeline( + Stage::unionWith( + coll: 'orders', + pipeline: new Pipeline( + Stage::match(status: 'A'), + Stage::project( + item: 1, + status: 1, + ), + ), + ), + ); + + $expected = [ + [ + '$unionWith' => [ + 'coll' => 'orders', + 'pipeline' => [ + [ + '$match' => ['status' => 'A'], + ], + [ + '$project' => [ + 'item' => 1, + 'status' => 1, + ], + ], + ], + ], + ], + ]; + + $this->assertSamePipeline($expected, $pipeline); + } + + /** @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/ */ + public function testRedactStage(): void + { + $pipeline = new Pipeline( + Stage::match(status: 'A'), + Stage::redact( + Expression::cond( + if: Expression::eq(Expression::fieldPath('level'), 5), + then: Expression::variable('PRUNE'), + else: Expression::variable('DESCEND'), + ), + ), + ); + $expected = [ + [ + '$match' => ['status' => 'A'], + ], + [ + '$redact' => [ + '$cond' => [ + 'if' => ['$eq' => ['$level', 5]], + 'then' => '$$PRUNE', + 'else' => '$$DESCEND', + ], + ], + ], + ]; + + $this->assertSamePipeline($expected, $pipeline); + } + + private static function assertSamePipeline(array $expected, Pipeline $pipeline): void + { + $codec = new BuilderEncoder(); + $actual = $codec->encode($pipeline); + + self::objectify($expected); + + self::assertEquals($expected, $actual, var_export($actual, true)); + } + + /** + * Recursively convert associative arrays to objects. + */ + private static function objectify(array &$array): void + { + array_walk($array, function (&$value): void { + if (is_array($value)) { + self::objectify($value); + + if (! array_is_list($value)) { + $value = (object) $value; + } + } + }); + } +} diff --git a/tests/Builder/PipelineTest.php b/tests/Builder/PipelineTest.php new file mode 100644 index 000000000..02c9d4254 --- /dev/null +++ b/tests/Builder/PipelineTest.php @@ -0,0 +1,53 @@ +assertSame([], iterator_to_array($pipeline)); + } + + public function testMergingPipeline(): void + { + $stages = array_map( + fn (int $i) => $this->createMock(StageInterface::class), + range(0, 5), + ); + + $pipeline = new Pipeline( + $stages[0], + $stages[1], + new Pipeline($stages[2], $stages[3]), + $stages[4], + new Pipeline($stages[5]), + ); + + $this->assertSame($stages, iterator_to_array($pipeline)); + } + + public function testRejectNamedArguments(): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Named arguments are not supported for pipelines'); + + new Pipeline( + $this->createMock(StageInterface::class), + foo: $this->createMock(StageInterface::class), + ); + } +} diff --git a/tests/Builder/Type/CombinedFieldQueryTest.php b/tests/Builder/Type/CombinedFieldQueryTest.php new file mode 100644 index 000000000..d9b088264 --- /dev/null +++ b/tests/Builder/Type/CombinedFieldQueryTest.php @@ -0,0 +1,48 @@ +assertSame([], $fieldQueries->fieldQueries); + } + + public function testFieldQueries(): void + { + $fieldQueries = new CombinedFieldQuery([ + $this->createMock(CombinedFieldQuery::class), + ['$gt' => 1], + new CombinedFieldQuery([]), + ]); + + $this->assertCount(3, $fieldQueries->fieldQueries); + } + + /** @dataProvider provideInvalidFieldQuery */ + public function testRejectInvalidFieldQueries($invalidQuery): void + { + $this->expectException(InvalidArgumentException::class); + + new CombinedFieldQuery([$invalidQuery]); + } + + public static function provideInvalidFieldQuery(): Generator + { + yield 'int' => [1]; + yield 'float' => [1.1]; + yield 'string' => ['foo']; + yield 'bool' => [true]; + yield 'null' => [null]; + } +} diff --git a/tests/Builder/Type/QueryObjectTest.php b/tests/Builder/Type/QueryObjectTest.php new file mode 100644 index 000000000..be9a46ed5 --- /dev/null +++ b/tests/Builder/Type/QueryObjectTest.php @@ -0,0 +1,76 @@ +assertSame([], $queryObject->queries); + } + + public function testShortCutQueryObject(): void + { + $query = $this->createMock(QueryInterface::class); + $queryObject = QueryObject::create($query); + + $this->assertSame($query, $queryObject); + } + + /** + * @param array $value + * + * @dataProvider provideQueryObjectValue + */ + public function testCreateQueryObject(array $value, int $expectedCount = 1): void + { + $queryObject = QueryObject::create(...$value); + + $this->assertCount($expectedCount, $queryObject->queries); + } + + public function provideQueryObjectValue(): array + { + return [ + 'int' => [['foo' => 1]], + 'float' => [['foo' => 1.1]], + 'string' => [['foo' => 'bar']], + 'bool' => [['foo' => true]], + 'null' => [['foo' => null]], + 'regex' => [['foo' => new Regex('foo')]], + 'object' => [['foo' => (object) ['bar' => 'baz']]], + 'list' => [['foo' => ['bar', 'baz']]], + 'operator as array' => [['foo' => ['$eq' => 1]]], + 'operator as object' => [['foo' => (object) ['$eq' => 1]]], + 'field query operator' => [['foo' => new EqOperator(1)]], + 'query operator' => [[new CommentOperator('foo'), 'foo' => 1], 2], + ]; + } + + public function testFieldQueryList(): void + { + $queryObject = QueryObject::create( + foo: [new GtOperator(1), new LtOperator(5)], + ); + + $this->assertArrayHasKey('foo', $queryObject->queries); + $this->assertInstanceOf(CombinedFieldQuery::class, $queryObject->queries['foo']); + $this->assertCount(2, $queryObject->queries['foo']->fieldQueries); + } +} From 9d46af7b99fe84b4d732a3f05843f77d5f3ab344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 18 Oct 2023 13:52:11 +0200 Subject: [PATCH 06/95] PHPLIB-1282 Add factories for system variables (#5) --- src/Builder/Variable.php | 153 +++++++++++++++++++++++++++ tests/Builder/BuilderEncoderTest.php | 5 +- tests/Builder/VariableTest.php | 51 +++++++++ 3 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 src/Builder/Variable.php create mode 100644 tests/Builder/VariableTest.php diff --git a/src/Builder/Variable.php b/src/Builder/Variable.php new file mode 100644 index 000000000..0eaec8b7f --- /dev/null +++ b/src/Builder/Variable.php @@ -0,0 +1,153 @@ + is equivalent to $$CURRENT., rebinding + * CURRENT changes the meaning of $ accesses. + */ + public static function current(string $fieldPath = ''): ResolvesToAny + { + return new Expression\Variable('CURRENT' . ($fieldPath ? '.' . $fieldPath : '')); + } + + /** + * A variable which evaluates to the missing value. Allows for the conditional exclusion of fields. In a $project, + * a field set to the variable REMOVE is excluded from the output. + * Can be used with $cond operator for conditionally exclude fields. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#std-label-remove-example + */ + public static function remove(): ResolvesToAny + { + return new Expression\Variable('REMOVE'); + } + + /** + * One of the allowed results of a $redact expression. + * + * $redact returns the fields at the current document level, excluding embedded documents. To include embedded + * documents and embedded documents within arrays, apply the $cond expression to the embedded documents to determine + * access for these embedded documents. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/#mongodb-pipeline-pipe.-redact + */ + public static function descend(): ExpressionInterface + { + return new Expression\Variable('DESCEND'); + } + + /** + * One of the allowed results of a $redact expression. + * + * $redact excludes all fields at this current document/embedded document level, without further inspection of any + * of the excluded fields. This applies even if the excluded field contains embedded documents that may have + * different access levels. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/#mongodb-pipeline-pipe.-redact + */ + public static function prune(): ExpressionInterface + { + return new Expression\Variable('PRUNE'); + } + + /** + * One of the allowed results of a $redact expression. + * + * $redact returns or keeps all fields at this current document/embedded document level, without further inspection + * of the fields at this level. This applies even if the included field contains embedded documents that may have + * different access levels. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/#mongodb-pipeline-pipe.-redact + */ + public static function keep(): ExpressionInterface + { + return new Expression\Variable('KEEP'); + } + + /** + * A variable that stores the metadata results of an Atlas Search query. In all supported aggregation pipeline + * stages, a field set to the variable $$SEARCH_META returns the metadata results for the query. + * For an example of its usage, see Atlas Search facet and count. + * + * @see https://www.mongodb.com/docs/atlas/atlas-search/query-syntax/#metadata-result-types + */ + public static function searchMeta(): ResolvesToObject + { + return new Expression\Variable('SEARCH_META'); + } + + /** + * Returns the roles assigned to the current user. + * For use cases that include USER_ROLES, see the find, aggregation, view, updateOne, updateMany, and findAndModify + * examples. + * + * New in MongoDB 7.0. + */ + public static function userRoles(): ResolvesToArray + { + return new Expression\Variable('USER_ROLES'); + } + + /** + * User-defined variable that can be used to store any BSON type. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/let/ + */ + public static function variable(string $name): Expression\Variable + { + return new Expression\Variable($name); + } +} diff --git a/tests/Builder/BuilderEncoderTest.php b/tests/Builder/BuilderEncoderTest.php index fcbd55d87..85eb46b1b 100644 --- a/tests/Builder/BuilderEncoderTest.php +++ b/tests/Builder/BuilderEncoderTest.php @@ -12,6 +12,7 @@ use MongoDB\Builder\Projection; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; +use MongoDB\Builder\Variable; use PHPUnit\Framework\TestCase; use function array_is_list; @@ -252,8 +253,8 @@ public function testRedactStage(): void Stage::redact( Expression::cond( if: Expression::eq(Expression::fieldPath('level'), 5), - then: Expression::variable('PRUNE'), - else: Expression::variable('DESCEND'), + then: Variable::prune(), + else: Variable::descend(), ), ), ); diff --git a/tests/Builder/VariableTest.php b/tests/Builder/VariableTest.php new file mode 100644 index 000000000..48f677206 --- /dev/null +++ b/tests/Builder/VariableTest.php @@ -0,0 +1,51 @@ +assertInstanceOf(Expression\Variable::class, $variable); + $this->assertStringStartsNotWith('$$', $variable->name); + } + + public function provideVariableBuilders(): Generator + { + yield 'now' => [fn () => Variable::now()]; + yield 'clusterTime' => [fn () => Variable::clusterTime()]; + yield 'root' => [fn () => Variable::root()]; + yield 'current' => [fn () => Variable::current()]; + yield 'remove' => [fn () => Variable::remove()]; + yield 'descend' => [fn () => Variable::descend()]; + yield 'prune' => [fn () => Variable::prune()]; + yield 'keep' => [fn () => Variable::keep()]; + yield 'searchMeta' => [fn () => Variable::searchMeta()]; + yield 'userRoles' => [fn () => Variable::userRoles()]; + } + + public function testCurrent(): void + { + $variable = Variable::current(); + $this->assertInstanceOf(Expression\Variable::class, $variable); + $this->assertSame('CURRENT', $variable->name); + + $variable = Variable::current('foo'); + $this->assertInstanceOf(Expression\Variable::class, $variable); + $this->assertSame('CURRENT.foo', $variable->name); + } + + public function testCustomVariable(): void + { + $this->assertInstanceOf(Expression\Variable::class, Variable::variable('foo')); + } +} From d526ab83c038581c87677ee2274ef6617e6b6983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 18 Oct 2023 22:12:50 +0200 Subject: [PATCH 07/95] Fix Javascript type & other PHP types (#4) --- generator/config/expression/bsonSize.yaml | 2 +- generator/config/expression/function.yaml | 6 +- generator/config/query/natural.yaml | 4 +- generator/config/query/rand.yaml | 2 +- generator/config/query/where.yaml | 2 +- generator/config/schema.json | 13 ++- generator/config/stage/changeStream.yaml | 4 +- generator/config/stage/lookup.yaml | 2 +- generator/config/stage/merge.yaml | 2 +- generator/config/stage/setWindowFields.yaml | 4 +- generator/config/stage/sort.yaml | 2 +- .../src/Definition/ArgumentDefinition.php | 4 +- .../src/Definition/OperatorDefinition.php | 1 + generator/src/ExpressionFactoryGenerator.php | 2 +- generator/src/OperatorClassGenerator.php | 8 +- generator/src/OperatorGenerator.php | 1 + phpcs.xml.dist | 3 + psalm-baseline.xml | 3 + psalm.xml.dist | 1 - src/Builder/Accumulator.php | 6 + .../Accumulator/MergeObjectsAccumulator.php | 2 +- src/Builder/BuilderEncoder.php | 2 +- src/Builder/Expression/AddOperator.php | 2 +- .../Expression/AllElementsTrueOperator.php | 2 +- src/Builder/Expression/AndOperator.php | 2 +- src/Builder/Expression/AvgOperator.php | 2 +- src/Builder/Expression/BitAndOperator.php | 2 +- src/Builder/Expression/BitOrOperator.php | 2 +- src/Builder/Expression/BitXorOperator.php | 2 +- src/Builder/Expression/BsonSizeOperator.php | 2 +- .../Expression/ConcatArraysOperator.php | 2 +- src/Builder/Expression/ConcatOperator.php | 2 +- src/Builder/Expression/FactoryTrait.php | 12 +- src/Builder/Expression/FunctionOperator.php | 13 ++- src/Builder/Expression/IfNullOperator.php | 2 +- src/Builder/Expression/IsArrayOperator.php | 2 +- src/Builder/Expression/IsNumberOperator.php | 2 +- src/Builder/Expression/MaxOperator.php | 2 +- src/Builder/Expression/MinOperator.php | 2 +- src/Builder/Expression/MultiplyOperator.php | 2 +- src/Builder/Expression/OrOperator.php | 2 +- src/Builder/Expression/SetEqualsOperator.php | 2 +- .../Expression/SetIntersectionOperator.php | 2 +- src/Builder/Expression/SetUnionOperator.php | 2 +- src/Builder/Expression/StdDevPopOperator.php | 2 +- src/Builder/Expression/StdDevSampOperator.php | 2 +- src/Builder/Expression/SumOperator.php | 2 +- src/Builder/Query/AllOperator.php | 2 +- src/Builder/Query/AndOperator.php | 2 +- src/Builder/Query/FactoryTrait.php | 7 +- src/Builder/Query/NaturalOperator.php | 6 +- src/Builder/Query/NorOperator.php | 2 +- src/Builder/Query/OrOperator.php | 2 +- src/Builder/Query/RandOperator.php | 4 +- src/Builder/Query/WhereOperator.php | 9 +- src/Builder/Stage.php | 3 +- src/Builder/Stage/AddFieldsStage.php | 2 +- src/Builder/Stage/FacetStage.php | 2 +- src/Builder/Stage/FactoryTrait.php | 7 +- src/Builder/Stage/GroupStage.php | 2 +- src/Builder/Stage/LookupStage.php | 4 +- src/Builder/Stage/ProjectStage.php | 2 +- src/Builder/Stage/SetStage.php | 2 +- src/Builder/Stage/SetWindowFieldsStage.php | 9 +- src/Builder/Stage/UnsetStage.php | 2 +- src/Builder/Type/Encode.php | 18 ++- src/Builder/Type/OutputWindow.php | 37 ++++-- src/Builder/Type/QueryObject.php | 16 +-- tests/Builder/BuilderEncoderTest.php | 14 ++- tests/Builder/Type/OutputWindowTest.php | 107 ++++++++++++++++++ tests/Builder/Type/QueryObjectTest.php | 31 +++-- 71 files changed, 314 insertions(+), 125 deletions(-) create mode 100644 psalm-baseline.xml create mode 100644 tests/Builder/Type/OutputWindowTest.php diff --git a/generator/config/expression/bsonSize.yaml b/generator/config/expression/bsonSize.yaml index 69fbd4122..0009a27a3 100644 --- a/generator/config/expression/bsonSize.yaml +++ b/generator/config/expression/bsonSize.yaml @@ -5,7 +5,7 @@ type: - resolvesToInt encode: single description: | - Returns the size in bytes of a given document (i.e. bsontype Object) when encoded as BSON. + Returns the size in bytes of a given document (i.e. BSON type Object) when encoded as BSON. arguments: - name: object diff --git a/generator/config/expression/function.yaml b/generator/config/expression/function.yaml index 2e8fd411b..4b39a0e81 100644 --- a/generator/config/expression/function.yaml +++ b/generator/config/expression/function.yaml @@ -11,9 +11,10 @@ arguments: - name: body type: - - string + - javascript description: | - The function definition. You can specify the function definition as either BSON type Code or String. + The function definition. You can specify the function definition as either BSON\JavaScript or string. + function(arg1, arg2, ...) { ... } - name: args type: @@ -24,3 +25,4 @@ arguments: name: lang type: - string + default: js diff --git a/generator/config/query/natural.yaml b/generator/config/query/natural.yaml index f3759972f..0d81e9063 100644 --- a/generator/config/query/natural.yaml +++ b/generator/config/query/natural.yaml @@ -1,8 +1,8 @@ # $schema: ../schema.json name: $natural -link: 'https://www.mongodb.com/docs/v7.0/reference/operator/meta/natural/' +link: 'https://www.mongodb.com/docs/manual/reference/operator/meta/natural/' type: - - expression + - projection # @todo: used in sort encode: object description: | A special hint that can be provided via the sort() or hint() methods that can be used to force either a forward or reverse collection scan. diff --git a/generator/config/query/rand.yaml b/generator/config/query/rand.yaml index b7a0ce107..809cb6b65 100644 --- a/generator/config/query/rand.yaml +++ b/generator/config/query/rand.yaml @@ -2,7 +2,7 @@ name: $rand link: 'https://www.mongodb.com/docs/manual/reference/operator/query/rand/' type: - - expression + - resolvesToDouble encode: object description: | Generates a random float between 0 and 1. diff --git a/generator/config/query/where.yaml b/generator/config/query/where.yaml index 8845715d1..5f353283c 100644 --- a/generator/config/query/where.yaml +++ b/generator/config/query/where.yaml @@ -10,4 +10,4 @@ arguments: - name: function type: - - string + - javascript diff --git a/generator/config/schema.json b/generator/config/schema.json index ca61931bb..253832e8a 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -9,7 +9,7 @@ "name": { "$comment": "The name of the operator. Must start with a $", "type": "string", - "pattern": "^\\$[a-z][a-zA-Z]+$" + "pattern": "^\\$[a-z0-9][a-zA-Z0-9]*$" }, "link": { "$comment": "The link to the operator's documentation on MongoDB's website.", @@ -28,6 +28,7 @@ "projection", "stage", "query", + "fieldQuery", "filter", "window", "geometry", @@ -92,7 +93,7 @@ "properties": { "name": { "type": "string", - "pattern": "^([a-z][a-zA-Z0-9]*|N)$" + "pattern": "^(_?[a-z][a-zA-Z0-9]*|N)$" }, "type": { "type": "array", @@ -105,6 +106,8 @@ "window", "expression", "geometry", + "projection", + "fieldPath", "any", "resolvesToNumber", "numberFieldPath", "number", "resolvesToDouble", "doubleFieldPath", "double", @@ -120,7 +123,7 @@ "resolvesToJavascript", "javascriptFieldPath", "javascript", "resolvesToInt", "intFieldPath", "int", "resolvesToTimestamp", "timestampFieldPath", "timestamp", - "resolvesTolong", "longFieldPath", "long", + "resolvesToLong", "longFieldPath", "long", "resolvesToDecimal", "decimalFieldPath", "decimal" ] } @@ -153,6 +156,10 @@ "$comment": "The minimum number of arguments for a variadic parameter.", "type": "integer", "minimum": 0 + }, + "default": { + "$comment": "The default value for the argument.", + "type": ["string", "number", "boolean"] } }, "required": [ diff --git a/generator/config/stage/changeStream.yaml b/generator/config/stage/changeStream.yaml index 46a6e6d24..6567af2b4 100644 --- a/generator/config/stage/changeStream.yaml +++ b/generator/config/stage/changeStream.yaml @@ -17,14 +17,14 @@ arguments: - name: fullDocument type: - - FullDocument + - string # FullDocument optional: true description: | Specifies whether change notifications include a copy of the full document when modified by update operations. - name: fullDocumentBeforeChange type: - - FullDocumentBeforeChange + - string # FullDocumentBeforeChange optional: true description: | Valid values are "off", "whenAvailable", or "required". If set to "off", the "fullDocumentBeforeChange" field of the output document is always omitted. If set to "whenAvailable", the "fullDocumentBeforeChange" field will be populated with the pre-image of the document modified by the current change event if such a pre-image is available, and will be omitted otherwise. If set to "required", then the "fullDocumentBeforeChange" field is always populated and an exception is thrown if the pre-image is not available. diff --git a/generator/config/stage/lookup.yaml b/generator/config/stage/lookup.yaml index 5a1b407fb..c1e591d6d 100644 --- a/generator/config/stage/lookup.yaml +++ b/generator/config/stage/lookup.yaml @@ -44,7 +44,7 @@ arguments: optional: true description: | Specifies the pipeline to run on the joined collection. The pipeline determines the resulting documents from the joined collection. To return all documents, specify an empty pipeline []. - The pipeline cannot include the $out stage or the $mergestage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. + The pipeline cannot include the $out stage or the $merge stage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. The pipeline cannot directly access the joined document fields. Instead, define variables for the joined document fields using the let option and then reference the variables in the pipeline stages. - name: as diff --git a/generator/config/stage/merge.yaml b/generator/config/stage/merge.yaml index d54aa02d3..486f873de 100644 --- a/generator/config/stage/merge.yaml +++ b/generator/config/stage/merge.yaml @@ -40,7 +40,7 @@ arguments: - name: whenNotMatched type: - - WhenNotMatched + - string # WhenNotMatched optional: true description: | The behavior of $merge if a result document does not match an existing document in the out collection. diff --git a/generator/config/stage/setWindowFields.yaml b/generator/config/stage/setWindowFields.yaml index df8bb52bd..730b2edc7 100644 --- a/generator/config/stage/setWindowFields.yaml +++ b/generator/config/stage/setWindowFields.yaml @@ -17,7 +17,7 @@ arguments: - name: sortBy type: - - SortSpec + - object # SortSpec description: | Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. - @@ -30,7 +30,7 @@ arguments: - name: window type: - - Window + - window optional: true description: | Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. diff --git a/generator/config/stage/sort.yaml b/generator/config/stage/sort.yaml index d0700ff22..9c26f0a2f 100644 --- a/generator/config/stage/sort.yaml +++ b/generator/config/stage/sort.yaml @@ -10,4 +10,4 @@ arguments: - name: sort type: - - SortSpec + - object # SortSpec diff --git a/generator/src/Definition/ArgumentDefinition.php b/generator/src/Definition/ArgumentDefinition.php index 4b03ba966..e45ea0da3 100644 --- a/generator/src/Definition/ArgumentDefinition.php +++ b/generator/src/Definition/ArgumentDefinition.php @@ -18,13 +18,15 @@ final class ArgumentDefinition public function __construct( public string $name, - /** @psalm-assert list $type */ + /** @var list */ public array $type, public string|null $description = null, public bool $optional = false, string|null $variadic = null, int|null $variadicMin = null, + public mixed $default = null, ) { + assert($this->optional === false || $this->default === null, 'Optional arguments cannot have a default value'); if (is_array($type)) { assert(array_is_list($type), 'Type must be a list or a single string'); foreach ($type as $t) { diff --git a/generator/src/Definition/OperatorDefinition.php b/generator/src/Definition/OperatorDefinition.php index 50ed595df..acbcc97e4 100644 --- a/generator/src/Definition/OperatorDefinition.php +++ b/generator/src/Definition/OperatorDefinition.php @@ -23,6 +23,7 @@ public function __construct( public string $name, public string $link, string $encode, + /** @var list */ public array $type, public string|null $description = null, array $arguments = [], diff --git a/generator/src/ExpressionFactoryGenerator.php b/generator/src/ExpressionFactoryGenerator.php index b9ccdc698..b1e84c5dd 100644 --- a/generator/src/ExpressionFactoryGenerator.php +++ b/generator/src/ExpressionFactoryGenerator.php @@ -13,7 +13,7 @@ final class ExpressionFactoryGenerator extends AbstractGenerator { - /** @param array $definitions */ + /** @param array $expressions */ public function generate(array $expressions): void { $this->writeFile($this->createFactoryClass($expressions)); diff --git a/generator/src/OperatorClassGenerator.php b/generator/src/OperatorClassGenerator.php index 42aa6d109..f1f336396 100644 --- a/generator/src/OperatorClassGenerator.php +++ b/generator/src/OperatorClassGenerator.php @@ -86,7 +86,7 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition if ($argument->variadic === VariadicType::Array) { $property->setType('array'); - $property->addComment('@var list<' . $type->doc . '> ...$' . $argument->name . rtrim(' ' . $argument->description)); + $property->addComment('@var list<' . $type->doc . '> $' . $argument->name . rtrim(' ' . $argument->description)); // Warn that named arguments are not supported // @see https://psalm.dev/docs/running_psalm/issues/NamedArgumentNotAllowed/ $constuctor->addComment('@no-named-arguments'); @@ -100,7 +100,7 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition } elseif ($argument->variadic === VariadicType::Object) { $namespace->addUse(stdClass::class); $property->setType(stdClass::class); - $property->addComment('@var stdClass<' . $type->doc . '> ...$' . $argument->name . rtrim(' ' . $argument->description)); + $property->addComment('@var stdClass<' . $type->doc . '> $' . $argument->name . rtrim(' ' . $argument->description)); $namespace->addUseFunction('is_string'); $namespace->addUse(InvalidArgumentException::class); $constuctor->addBody(<<optional) { // We use a special Optional::Undefined type to differentiate between null and undefined $constuctorParam->setDefaultValue(new Literal('Optional::Undefined')); + } elseif ($argument->default !== null) { + $constuctorParam->setDefaultValue($argument->default); } // List type must be validated with array_is_list() @@ -162,6 +164,8 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition /** * Operator classes interfaces are defined by their return type as a MongoDB expression. + * + * @return list */ private function getInterfaces(OperatorDefinition $definition): array { diff --git a/generator/src/OperatorGenerator.php b/generator/src/OperatorGenerator.php index 5ce32d9e7..fbab6023f 100644 --- a/generator/src/OperatorGenerator.php +++ b/generator/src/OperatorGenerator.php @@ -45,6 +45,7 @@ final public function __construct( abstract public function generate(GeneratorDefinition $definition): void; + /** @return list> */ final protected function getOperators(GeneratorDefinition $definition): array { // Remove unsupported operators diff --git a/phpcs.xml.dist b/phpcs.xml.dist index e5f818658..74f7c0be6 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -24,6 +24,9 @@ + + + diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 000000000..f4acb63c7 --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,3 @@ + + + diff --git a/psalm.xml.dist b/psalm.xml.dist index c039b023a..ff5451291 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -12,7 +12,6 @@ > - diff --git a/src/Builder/Accumulator.php b/src/Builder/Accumulator.php index 4748d824c..487a9168d 100644 --- a/src/Builder/Accumulator.php +++ b/src/Builder/Accumulator.php @@ -21,6 +21,12 @@ enum Accumulator { use Accumulator\FactoryTrait; + /** + * @param Document|Serializable|WindowInterface|array|stdClass $operator Window operator to use in the $setWindowFields stage. + * @param Optional|array{string|int,string|int} $documents A window where the lower and upper boundaries are specified relative to the position of the current document read from the collection. + * @param Optional|array{string|numeric,string|numeric} $range Arguments passed to the init function. + * @param Optional|non-empty-string $unit Specifies the units for time range window boundaries. If omitted, default numeric range window boundaries are used. + */ public static function outputWindow( Document|Serializable|WindowInterface|stdClass|array $operator, Optional|array $documents = Optional::Undefined, diff --git a/src/Builder/Accumulator/MergeObjectsAccumulator.php b/src/Builder/Accumulator/MergeObjectsAccumulator.php index e42a3e93c..648098bfd 100644 --- a/src/Builder/Accumulator/MergeObjectsAccumulator.php +++ b/src/Builder/Accumulator/MergeObjectsAccumulator.php @@ -28,7 +28,7 @@ class MergeObjectsAccumulator implements AccumulatorInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$document Any valid expression that resolves to a document. */ + /** @var list $document Any valid expression that resolves to a document. */ public readonly array $document; /** diff --git a/src/Builder/BuilderEncoder.php b/src/Builder/BuilderEncoder.php index 3704462cd..93e1a113a 100644 --- a/src/Builder/BuilderEncoder.php +++ b/src/Builder/BuilderEncoder.php @@ -244,7 +244,7 @@ private function encodeOutputWindow(OutputWindow $outputWindow): stdClass // Transform the result into an stdClass if a document is provided if (! $outputWindow->operator instanceof WindowInterface && (is_array($result) || is_object($result))) { if (! is_first_key_operator($result)) { - throw new LogicException(sprintf('Expected OutputWindow::$operator to be an operator. Got "%s"', array_key_first($result))); + throw new LogicException(sprintf('Expected OutputWindow::$operator to be an operator. Got "%s"', array_key_first((array) $result))); } $result = (object) $result; diff --git a/src/Builder/Expression/AddOperator.php b/src/Builder/Expression/AddOperator.php index 3e18334c7..e75bd1e40 100644 --- a/src/Builder/Expression/AddOperator.php +++ b/src/Builder/Expression/AddOperator.php @@ -26,7 +26,7 @@ class AddOperator implements ResolvesToNumber, ResolvesToDate, OperatorInterface { public const ENCODE = Encode::Array; - /** @var list ...$expression The arguments can be any valid expression as long as they resolve to either all numbers or to numbers and a date. */ + /** @var list $expression The arguments can be any valid expression as long as they resolve to either all numbers or to numbers and a date. */ public readonly array $expression; /** diff --git a/src/Builder/Expression/AllElementsTrueOperator.php b/src/Builder/Expression/AllElementsTrueOperator.php index 36aa996a2..7fa3eb35d 100644 --- a/src/Builder/Expression/AllElementsTrueOperator.php +++ b/src/Builder/Expression/AllElementsTrueOperator.php @@ -25,7 +25,7 @@ class AllElementsTrueOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/AndOperator.php b/src/Builder/Expression/AndOperator.php index ab874f9da..0cc9d1700 100644 --- a/src/Builder/Expression/AndOperator.php +++ b/src/Builder/Expression/AndOperator.php @@ -28,7 +28,7 @@ class AndOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/AvgOperator.php b/src/Builder/Expression/AvgOperator.php index efbe4254a..069ac1846 100644 --- a/src/Builder/Expression/AvgOperator.php +++ b/src/Builder/Expression/AvgOperator.php @@ -26,7 +26,7 @@ class AvgOperator implements ResolvesToNumber, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/BitAndOperator.php b/src/Builder/Expression/BitAndOperator.php index 221237aac..1391a6063 100644 --- a/src/Builder/Expression/BitAndOperator.php +++ b/src/Builder/Expression/BitAndOperator.php @@ -25,7 +25,7 @@ class BitAndOperator implements ResolvesToInt, ResolvesToLong, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/BitOrOperator.php b/src/Builder/Expression/BitOrOperator.php index 85962655d..f73d277e9 100644 --- a/src/Builder/Expression/BitOrOperator.php +++ b/src/Builder/Expression/BitOrOperator.php @@ -25,7 +25,7 @@ class BitOrOperator implements ResolvesToInt, ResolvesToLong, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/BitXorOperator.php b/src/Builder/Expression/BitXorOperator.php index e95f4f8f9..677b8c628 100644 --- a/src/Builder/Expression/BitXorOperator.php +++ b/src/Builder/Expression/BitXorOperator.php @@ -25,7 +25,7 @@ class BitXorOperator implements ResolvesToInt, ResolvesToLong, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/BsonSizeOperator.php b/src/Builder/Expression/BsonSizeOperator.php index a243f2b06..bdfb6f694 100644 --- a/src/Builder/Expression/BsonSizeOperator.php +++ b/src/Builder/Expression/BsonSizeOperator.php @@ -15,7 +15,7 @@ use stdClass; /** - * Returns the size in bytes of a given document (i.e. bsontype Object) when encoded as BSON. + * Returns the size in bytes of a given document (i.e. BSON type Object) when encoded as BSON. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bsonSize/ */ diff --git a/src/Builder/Expression/ConcatArraysOperator.php b/src/Builder/Expression/ConcatArraysOperator.php index 95c0aae66..ca107df50 100644 --- a/src/Builder/Expression/ConcatArraysOperator.php +++ b/src/Builder/Expression/ConcatArraysOperator.php @@ -25,7 +25,7 @@ class ConcatArraysOperator implements ResolvesToArray, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$array */ + /** @var list $array */ public readonly array $array; /** diff --git a/src/Builder/Expression/ConcatOperator.php b/src/Builder/Expression/ConcatOperator.php index 6b401f95c..75527a193 100644 --- a/src/Builder/Expression/ConcatOperator.php +++ b/src/Builder/Expression/ConcatOperator.php @@ -23,7 +23,7 @@ class ConcatOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 1bc98cda6..a3205bc7a 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -12,6 +12,7 @@ use MongoDB\BSON\Decimal128; use MongoDB\BSON\Document; use MongoDB\BSON\Int64; +use MongoDB\BSON\Javascript; use MongoDB\BSON\ObjectId; use MongoDB\BSON\PackedArray; use MongoDB\BSON\Regex; @@ -287,7 +288,7 @@ public static function bitXor(Int64|ResolvesToInt|ResolvesToLong|int ...$express } /** - * Returns the size in bytes of a given document (i.e. bsontype Object) when encoded as BSON. + * Returns the size in bytes of a given document (i.e. BSON type Object) when encoded as BSON. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bsonSize/ * @param Document|ResolvesToNull|ResolvesToObject|Serializable|array|null|stdClass $object @@ -724,11 +725,16 @@ public static function floor(Decimal128|Int64|ResolvesToNumber|float|int $expres * New in MongoDB 4.4. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/ - * @param non-empty-string $body The function definition. You can specify the function definition as either BSON type Code or String. + * @param Javascript|non-empty-string $body The function definition. You can specify the function definition as either BSON\JavaScript or string. + * function(arg1, arg2, ...) { ... } * @param BSONArray|PackedArray|array $args Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ]. * @param non-empty-string $lang */ - public static function function(string $body, PackedArray|BSONArray|array $args, string $lang): FunctionOperator + public static function function( + Javascript|string $body, + PackedArray|BSONArray|array $args, + string $lang, + ): FunctionOperator { return new FunctionOperator($body, $args, $lang); } diff --git a/src/Builder/Expression/FunctionOperator.php b/src/Builder/Expression/FunctionOperator.php index 68a671d7c..e99876192 100644 --- a/src/Builder/Expression/FunctionOperator.php +++ b/src/Builder/Expression/FunctionOperator.php @@ -8,6 +8,7 @@ namespace MongoDB\Builder\Expression; +use MongoDB\BSON\Javascript; use MongoDB\BSON\PackedArray; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; @@ -27,8 +28,11 @@ class FunctionOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Object; - /** @var non-empty-string $body The function definition. You can specify the function definition as either BSON type Code or String. */ - public readonly string $body; + /** + * @var Javascript|non-empty-string $body The function definition. You can specify the function definition as either BSON\JavaScript or string. + * function(arg1, arg2, ...) { ... } + */ + public readonly Javascript|string $body; /** @var BSONArray|PackedArray|array $args Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ]. */ public readonly PackedArray|BSONArray|array $args; @@ -37,11 +41,12 @@ class FunctionOperator implements ResolvesToAny, OperatorInterface public readonly string $lang; /** - * @param non-empty-string $body The function definition. You can specify the function definition as either BSON type Code or String. + * @param Javascript|non-empty-string $body The function definition. You can specify the function definition as either BSON\JavaScript or string. + * function(arg1, arg2, ...) { ... } * @param BSONArray|PackedArray|array $args Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ]. * @param non-empty-string $lang */ - public function __construct(string $body, PackedArray|BSONArray|array $args, string $lang) + public function __construct(Javascript|string $body, PackedArray|BSONArray|array $args, string $lang = 'js') { $this->body = $body; if (is_array($args) && ! array_is_list($args)) { diff --git a/src/Builder/Expression/IfNullOperator.php b/src/Builder/Expression/IfNullOperator.php index 7d70602d1..8cbd02845 100644 --- a/src/Builder/Expression/IfNullOperator.php +++ b/src/Builder/Expression/IfNullOperator.php @@ -26,7 +26,7 @@ class IfNullOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/IsArrayOperator.php b/src/Builder/Expression/IsArrayOperator.php index bcde5c71a..0d499e65b 100644 --- a/src/Builder/Expression/IsArrayOperator.php +++ b/src/Builder/Expression/IsArrayOperator.php @@ -26,7 +26,7 @@ class IsArrayOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/IsNumberOperator.php b/src/Builder/Expression/IsNumberOperator.php index 281a1a304..70a0b12dc 100644 --- a/src/Builder/Expression/IsNumberOperator.php +++ b/src/Builder/Expression/IsNumberOperator.php @@ -28,7 +28,7 @@ class IsNumberOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/MaxOperator.php b/src/Builder/Expression/MaxOperator.php index 1d1c49f8f..6befee674 100644 --- a/src/Builder/Expression/MaxOperator.php +++ b/src/Builder/Expression/MaxOperator.php @@ -27,7 +27,7 @@ class MaxOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/MinOperator.php b/src/Builder/Expression/MinOperator.php index 0321bdb00..4152fc193 100644 --- a/src/Builder/Expression/MinOperator.php +++ b/src/Builder/Expression/MinOperator.php @@ -27,7 +27,7 @@ class MinOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/MultiplyOperator.php b/src/Builder/Expression/MultiplyOperator.php index d6ee6802b..4c76adae2 100644 --- a/src/Builder/Expression/MultiplyOperator.php +++ b/src/Builder/Expression/MultiplyOperator.php @@ -26,7 +26,7 @@ class MultiplyOperator implements ResolvesToDecimal, OperatorInterface public const ENCODE = Encode::Single; /** - * @var list ...$expression The arguments can be any valid expression as long as they resolve to numbers. + * @var list $expression The arguments can be any valid expression as long as they resolve to numbers. * Starting in MongoDB 6.1 you can optimize the $multiply operation. To improve performance, group references at the end of the argument list. */ public readonly array $expression; diff --git a/src/Builder/Expression/OrOperator.php b/src/Builder/Expression/OrOperator.php index 2523eda44..46f216ad5 100644 --- a/src/Builder/Expression/OrOperator.php +++ b/src/Builder/Expression/OrOperator.php @@ -26,7 +26,7 @@ class OrOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/SetEqualsOperator.php b/src/Builder/Expression/SetEqualsOperator.php index 2c5684c54..d50a6c290 100644 --- a/src/Builder/Expression/SetEqualsOperator.php +++ b/src/Builder/Expression/SetEqualsOperator.php @@ -25,7 +25,7 @@ class SetEqualsOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/SetIntersectionOperator.php b/src/Builder/Expression/SetIntersectionOperator.php index 9ca5c0732..a58a931b8 100644 --- a/src/Builder/Expression/SetIntersectionOperator.php +++ b/src/Builder/Expression/SetIntersectionOperator.php @@ -25,7 +25,7 @@ class SetIntersectionOperator implements ResolvesToArray, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/SetUnionOperator.php b/src/Builder/Expression/SetUnionOperator.php index 011a93350..ec3bf68ca 100644 --- a/src/Builder/Expression/SetUnionOperator.php +++ b/src/Builder/Expression/SetUnionOperator.php @@ -25,7 +25,7 @@ class SetUnionOperator implements ResolvesToArray, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/StdDevPopOperator.php b/src/Builder/Expression/StdDevPopOperator.php index 2df167aa4..a6666d757 100644 --- a/src/Builder/Expression/StdDevPopOperator.php +++ b/src/Builder/Expression/StdDevPopOperator.php @@ -27,7 +27,7 @@ class StdDevPopOperator implements ResolvesToDouble, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/StdDevSampOperator.php b/src/Builder/Expression/StdDevSampOperator.php index af22c7219..81fe62e95 100644 --- a/src/Builder/Expression/StdDevSampOperator.php +++ b/src/Builder/Expression/StdDevSampOperator.php @@ -26,7 +26,7 @@ class StdDevSampOperator implements ResolvesToDouble, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Expression/SumOperator.php b/src/Builder/Expression/SumOperator.php index 2666e0ce8..3d314c0f6 100644 --- a/src/Builder/Expression/SumOperator.php +++ b/src/Builder/Expression/SumOperator.php @@ -26,7 +26,7 @@ class SumOperator implements ResolvesToNumber, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Query/AllOperator.php b/src/Builder/Query/AllOperator.php index 5e86ae58f..7cd57dd26 100644 --- a/src/Builder/Query/AllOperator.php +++ b/src/Builder/Query/AllOperator.php @@ -26,7 +26,7 @@ class AllOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$value */ + /** @var list $value */ public readonly array $value; /** diff --git a/src/Builder/Query/AndOperator.php b/src/Builder/Query/AndOperator.php index a58b84d58..dcbe6766b 100644 --- a/src/Builder/Query/AndOperator.php +++ b/src/Builder/Query/AndOperator.php @@ -27,7 +27,7 @@ class AndOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index 2d6b0862d..55098728b 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -12,6 +12,7 @@ use MongoDB\BSON\Decimal128; use MongoDB\BSON\Document; use MongoDB\BSON\Int64; +use MongoDB\BSON\Javascript; use MongoDB\BSON\PackedArray; use MongoDB\BSON\Regex; use MongoDB\BSON\Serializable; @@ -330,7 +331,7 @@ public static function mod(int $divisor, int $remainder): ModOperator /** * A special hint that can be provided via the sort() or hint() methods that can be used to force either a forward or reverse collection scan. * - * @see https://www.mongodb.com/docs/v7.0/reference/operator/meta/natural/ + * @see https://www.mongodb.com/docs/manual/reference/operator/meta/natural/ */ public static function natural(): NaturalOperator { @@ -505,9 +506,9 @@ public static function type(PackedArray|BSONArray|array|int|string $type): TypeO * Matches documents that satisfy a JavaScript expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/where/ - * @param non-empty-string $function + * @param Javascript|non-empty-string $function */ - public static function where(string $function): WhereOperator + public static function where(Javascript|string $function): WhereOperator { return new WhereOperator($function); } diff --git a/src/Builder/Query/NaturalOperator.php b/src/Builder/Query/NaturalOperator.php index 4a820a272..409920903 100644 --- a/src/Builder/Query/NaturalOperator.php +++ b/src/Builder/Query/NaturalOperator.php @@ -9,15 +9,15 @@ namespace MongoDB\Builder\Query; use MongoDB\Builder\Type\Encode; -use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\ProjectionInterface; /** * A special hint that can be provided via the sort() or hint() methods that can be used to force either a forward or reverse collection scan. * - * @see https://www.mongodb.com/docs/v7.0/reference/operator/meta/natural/ + * @see https://www.mongodb.com/docs/manual/reference/operator/meta/natural/ */ -class NaturalOperator implements ExpressionInterface, OperatorInterface +class NaturalOperator implements ProjectionInterface, OperatorInterface { public const ENCODE = Encode::Object; diff --git a/src/Builder/Query/NorOperator.php b/src/Builder/Query/NorOperator.php index 5a8c97f6a..0c56c6f87 100644 --- a/src/Builder/Query/NorOperator.php +++ b/src/Builder/Query/NorOperator.php @@ -27,7 +27,7 @@ class NorOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Query/OrOperator.php b/src/Builder/Query/OrOperator.php index decb58f1c..d517da7ff 100644 --- a/src/Builder/Query/OrOperator.php +++ b/src/Builder/Query/OrOperator.php @@ -27,7 +27,7 @@ class OrOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$expression */ + /** @var list $expression */ public readonly array $expression; /** diff --git a/src/Builder/Query/RandOperator.php b/src/Builder/Query/RandOperator.php index 468348ef8..251f50ea1 100644 --- a/src/Builder/Query/RandOperator.php +++ b/src/Builder/Query/RandOperator.php @@ -8,8 +8,8 @@ namespace MongoDB\Builder\Query; +use MongoDB\Builder\Expression\ResolvesToDouble; use MongoDB\Builder\Type\Encode; -use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\OperatorInterface; /** @@ -17,7 +17,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/rand/ */ -class RandOperator implements ExpressionInterface, OperatorInterface +class RandOperator implements ResolvesToDouble, OperatorInterface { public const ENCODE = Encode::Object; diff --git a/src/Builder/Query/WhereOperator.php b/src/Builder/Query/WhereOperator.php index 2a4b22056..854d4d4dd 100644 --- a/src/Builder/Query/WhereOperator.php +++ b/src/Builder/Query/WhereOperator.php @@ -8,6 +8,7 @@ namespace MongoDB\Builder\Query; +use MongoDB\BSON\Javascript; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\QueryInterface; @@ -21,13 +22,13 @@ class WhereOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var non-empty-string $function */ - public readonly string $function; + /** @var Javascript|non-empty-string $function */ + public readonly Javascript|string $function; /** - * @param non-empty-string $function + * @param Javascript|non-empty-string $function */ - public function __construct(string $function) + public function __construct(Javascript|string $function) { $this->function = $function; } diff --git a/src/Builder/Stage.php b/src/Builder/Stage.php index 5565a0d81..2ab7c20b4 100644 --- a/src/Builder/Stage.php +++ b/src/Builder/Stage.php @@ -4,7 +4,6 @@ namespace MongoDB\Builder; -use MongoDB\BSON\Document; use MongoDB\BSON\Serializable; use MongoDB\Builder\Stage\MatchStage; use MongoDB\Builder\Type\FieldQueryInterface; @@ -22,7 +21,7 @@ enum Stage * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/ * - * @param Document|FieldQueryInterface|QueryInterface|Serializable|array|stdClass|null $queries + * @param FieldQueryInterface|QueryInterface|Serializable|array|bool|float|int|stdClass|string|null ...$queries The query predicates to match */ public static function match(FieldQueryInterface|QueryInterface|Serializable|array|bool|float|int|stdClass|string|null ...$queries): MatchStage { diff --git a/src/Builder/Stage/AddFieldsStage.php b/src/Builder/Stage/AddFieldsStage.php index 28155b314..8c9ed9741 100644 --- a/src/Builder/Stage/AddFieldsStage.php +++ b/src/Builder/Stage/AddFieldsStage.php @@ -27,7 +27,7 @@ class AddFieldsStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var stdClass ...$expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. */ + /** @var stdClass $expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. */ public readonly stdClass $expression; /** diff --git a/src/Builder/Stage/FacetStage.php b/src/Builder/Stage/FacetStage.php index ee0bcf52f..dcc5a6a26 100644 --- a/src/Builder/Stage/FacetStage.php +++ b/src/Builder/Stage/FacetStage.php @@ -28,7 +28,7 @@ class FacetStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var stdClass ...$facet */ + /** @var stdClass $facet */ public readonly stdClass $facet; /** diff --git a/src/Builder/Stage/FactoryTrait.php b/src/Builder/Stage/FactoryTrait.php index c23c1385f..7b39024f4 100644 --- a/src/Builder/Stage/FactoryTrait.php +++ b/src/Builder/Stage/FactoryTrait.php @@ -26,6 +26,7 @@ use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\ProjectionInterface; use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\WindowInterface; use MongoDB\Model\BSONArray; use stdClass; @@ -402,7 +403,7 @@ public static function listSessions( * @param Optional|non-empty-string $foreignField Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. * @param Optional|Document|Serializable|array|stdClass $let Specifies variables to use in the pipeline stages. Use the variable expressions to access the fields from the joined collection's documents that are input to the pipeline. * @param Optional|BSONArray|PackedArray|Pipeline|array $pipeline Specifies the pipeline to run on the joined collection. The pipeline determines the resulting documents from the joined collection. To return all documents, specify an empty pipeline []. - * The pipeline cannot include the $out stage or the $mergestage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. + * The pipeline cannot include the $out stage or the $merge stage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. * The pipeline cannot directly access the joined document fields. Instead, define variables for the joined document fields using the let option and then reference the variables in the pipeline stages. */ public static function lookup( @@ -586,13 +587,13 @@ public static function set(Type|ExpressionInterface|stdClass|array|bool|float|in * @param Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. * @param Document|Serializable|array|stdClass $output Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. * A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. - * @param Optional|Document|Serializable|array|stdClass $window Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. + * @param Optional|Document|Serializable|WindowInterface|array|stdClass $window Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. */ public static function setWindowFields( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy, Document|Serializable|stdClass|array $sortBy, Document|Serializable|stdClass|array $output, - Optional|Document|Serializable|stdClass|array $window = Optional::Undefined, + Optional|Document|Serializable|WindowInterface|stdClass|array $window = Optional::Undefined, ): SetWindowFieldsStage { return new SetWindowFieldsStage($partitionBy, $sortBy, $output, $window); diff --git a/src/Builder/Stage/GroupStage.php b/src/Builder/Stage/GroupStage.php index b6c6a11ba..2fc613fe8 100644 --- a/src/Builder/Stage/GroupStage.php +++ b/src/Builder/Stage/GroupStage.php @@ -33,7 +33,7 @@ class GroupStage implements StageInterface, OperatorInterface /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $_id The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $_id; - /** @var stdClass ...$field Computed using the accumulator operators. */ + /** @var stdClass $field Computed using the accumulator operators. */ public readonly stdClass $field; /** diff --git a/src/Builder/Stage/LookupStage.php b/src/Builder/Stage/LookupStage.php index de759aad2..a3b89619e 100644 --- a/src/Builder/Stage/LookupStage.php +++ b/src/Builder/Stage/LookupStage.php @@ -53,7 +53,7 @@ class LookupStage implements StageInterface, OperatorInterface /** * @var Optional|BSONArray|PackedArray|Pipeline|array $pipeline Specifies the pipeline to run on the joined collection. The pipeline determines the resulting documents from the joined collection. To return all documents, specify an empty pipeline []. - * The pipeline cannot include the $out stage or the $mergestage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. + * The pipeline cannot include the $out stage or the $merge stage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. * The pipeline cannot directly access the joined document fields. Instead, define variables for the joined document fields using the let option and then reference the variables in the pipeline stages. */ public readonly Optional|PackedArray|Pipeline|BSONArray|array $pipeline; @@ -67,7 +67,7 @@ class LookupStage implements StageInterface, OperatorInterface * @param Optional|non-empty-string $foreignField Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. * @param Optional|Document|Serializable|array|stdClass $let Specifies variables to use in the pipeline stages. Use the variable expressions to access the fields from the joined collection's documents that are input to the pipeline. * @param Optional|BSONArray|PackedArray|Pipeline|array $pipeline Specifies the pipeline to run on the joined collection. The pipeline determines the resulting documents from the joined collection. To return all documents, specify an empty pipeline []. - * The pipeline cannot include the $out stage or the $mergestage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. + * The pipeline cannot include the $out stage or the $merge stage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. * The pipeline cannot directly access the joined document fields. Instead, define variables for the joined document fields using the let option and then reference the variables in the pipeline stages. */ public function __construct( diff --git a/src/Builder/Stage/ProjectStage.php b/src/Builder/Stage/ProjectStage.php index faa9758bd..ba2bd2725 100644 --- a/src/Builder/Stage/ProjectStage.php +++ b/src/Builder/Stage/ProjectStage.php @@ -29,7 +29,7 @@ class ProjectStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var stdClass ...$specification */ + /** @var stdClass $specification */ public readonly stdClass $specification; /** diff --git a/src/Builder/Stage/SetStage.php b/src/Builder/Stage/SetStage.php index be18af019..904ec9f14 100644 --- a/src/Builder/Stage/SetStage.php +++ b/src/Builder/Stage/SetStage.php @@ -28,7 +28,7 @@ class SetStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var stdClass ...$field */ + /** @var stdClass $field */ public readonly stdClass $field; /** diff --git a/src/Builder/Stage/SetWindowFieldsStage.php b/src/Builder/Stage/SetWindowFieldsStage.php index 68ac0ab9a..c93020d1d 100644 --- a/src/Builder/Stage/SetWindowFieldsStage.php +++ b/src/Builder/Stage/SetWindowFieldsStage.php @@ -16,6 +16,7 @@ use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\StageInterface; +use MongoDB\Builder\Type\WindowInterface; use stdClass; /** @@ -40,21 +41,21 @@ class SetWindowFieldsStage implements StageInterface, OperatorInterface */ public readonly Document|Serializable|stdClass|array $output; - /** @var Optional|Document|Serializable|array|stdClass $window Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. */ - public readonly Optional|Document|Serializable|stdClass|array $window; + /** @var Optional|Document|Serializable|WindowInterface|array|stdClass $window Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. */ + public readonly Optional|Document|Serializable|WindowInterface|stdClass|array $window; /** * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. * @param Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. * @param Document|Serializable|array|stdClass $output Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. * A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. - * @param Optional|Document|Serializable|array|stdClass $window Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. + * @param Optional|Document|Serializable|WindowInterface|array|stdClass $window Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. */ public function __construct( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy, Document|Serializable|stdClass|array $sortBy, Document|Serializable|stdClass|array $output, - Optional|Document|Serializable|stdClass|array $window = Optional::Undefined, + Optional|Document|Serializable|WindowInterface|stdClass|array $window = Optional::Undefined, ) { $this->partitionBy = $partitionBy; $this->sortBy = $sortBy; diff --git a/src/Builder/Stage/UnsetStage.php b/src/Builder/Stage/UnsetStage.php index 5f60a3cd3..1abcb9bf2 100644 --- a/src/Builder/Stage/UnsetStage.php +++ b/src/Builder/Stage/UnsetStage.php @@ -26,7 +26,7 @@ class UnsetStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list ...$field */ + /** @var list $field */ public readonly array $field; /** diff --git a/src/Builder/Type/Encode.php b/src/Builder/Type/Encode.php index 76a2e06b9..69f1aba7d 100644 --- a/src/Builder/Type/Encode.php +++ b/src/Builder/Type/Encode.php @@ -4,6 +4,8 @@ namespace MongoDB\Builder\Type; +use MongoDB\Builder\BuilderEncoder; + /** * Defines how to encode a stage or an operator into BSON. * @@ -11,9 +13,23 @@ */ enum Encode { - // @todo add comments (see schema.json) + /** + * Parameters are encoded as an array of values in the order they are defined by the spec and declared in the object. + */ case Array; + + /** + * Parameters are encoded as an object with keys matching the parameter names + */ case Object; + + /** + * Get the single parameter value + */ case Single; + + /** + * Specific for $group stage + */ case Group; } diff --git a/src/Builder/Type/OutputWindow.php b/src/Builder/Type/OutputWindow.php index c7add0cac..2f43f25e1 100644 --- a/src/Builder/Type/OutputWindow.php +++ b/src/Builder/Type/OutputWindow.php @@ -11,6 +11,11 @@ use function array_is_list; use function count; +use function get_debug_type; +use function is_int; +use function is_numeric; +use function is_string; +use function sprintf; /** * Specifies the window boundaries and parameters. Window boundaries are inclusive. @@ -22,16 +27,22 @@ class OutputWindow implements WindowInterface { public const ENCODE = Encode::Object; - /** @param Document|Serializable|WindowInterface|array|stdClass $operator Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. */ + /** + * Function used to initialize the state. The init function receives its arguments from the initArgs array expression. + * You can specify the function definition as either BSON type Code or String. + */ public Document|Serializable|WindowInterface|stdClass|array $operator; + /** + * Specifies the window boundaries and parameters. + */ public Optional|stdClass $window; /** - * @param Document|Serializable|WindowInterface|array|stdClass $operator Window operator to use in the $setWindowFields stage. - * @param Optional|array{string|int,string|int} $documents A window where the lower and upper boundaries are specified relative to the position of the current document read from the collection. - * @param Optional|array{string|numeric,string|numeric} $range Arguments passed to the init function. - * @param Optional|non-empty-string $unit Specifies the units for time range window boundaries. If omitted, default numeric range window boundaries are used. + * @param Document|Serializable|WindowInterface|array|stdClass $operator Window operator to use in the $setWindowFields stage. + * @param Optional|array{string|int,string|int} $documents A window where the lower and upper boundaries are specified relative to the position of the current document read from the collection. + * @param Optional|array{string|numeric,string|numeric} $range Arguments passed to the init function. + * @param Optional|non-empty-string $unit Specifies the units for time range window boundaries. If omitted, default numeric range window boundaries are used. */ public function __construct( Document|Serializable|WindowInterface|stdClass|array $operator, @@ -43,8 +54,12 @@ public function __construct( $window = null; if ($documents !== Optional::Undefined) { - if (! array_is_list($documents) || ! count($documents) === 2) { - throw new InvalidArgumentException('Expected $documents argument to be a list of 2 string or int.'); + if (! array_is_list($documents) || count($documents) !== 2) { + throw new InvalidArgumentException('Expected $documents argument to be a list of 2 string or int'); + } + + if (! is_string($documents[0]) && ! is_int($documents[0]) || ! is_string($documents[1]) && ! is_int($documents[1])) { + throw new InvalidArgumentException(sprintf('Expected $documents argument to be a list of 2 string or int. Got [%s, %s]', get_debug_type($documents[0]), get_debug_type($documents[1]))); } $window ??= new stdClass(); @@ -52,8 +67,12 @@ public function __construct( } if ($range !== Optional::Undefined) { - if (! array_is_list($range) || ! count($range) === 2) { - throw new InvalidArgumentException('Expected $range argument to be a list of 2 string or numeric.'); + if (! array_is_list($range) || count($range) !== 2) { + throw new InvalidArgumentException('Expected $range argument to be a list of 2 string or numeric'); + } + + if (! is_string($range[0]) && ! is_numeric($range[0]) || ! is_string($range[1]) && ! is_numeric($range[1])) { + throw new InvalidArgumentException(sprintf('Expected $range argument to be a list of 2 string or numeric. Got [%s, %s]', get_debug_type($range[0]), get_debug_type($range[1]))); } $window ??= new stdClass(); diff --git a/src/Builder/Type/QueryObject.php b/src/Builder/Type/QueryObject.php index 396139244..f50c074e7 100644 --- a/src/Builder/Type/QueryObject.php +++ b/src/Builder/Type/QueryObject.php @@ -6,7 +6,6 @@ use MongoDB\BSON\Document; use MongoDB\BSON\Regex; -use MongoDB\BSON\Serializable; use MongoDB\Exception\InvalidArgumentException; use stdClass; @@ -18,7 +17,8 @@ /** * Helper class to validate query objects. * - * Queries are a mix of query operators ($or, $and, $nor...) and field query operators ($eq, $gt, $in...) associated to a field path. + * Queries are a mix of query operators ($or, $and, $nor, $jsonSchema...) and field query operators ($eq, $gt, $in...) + * associated to a field path. */ final class QueryObject implements QueryInterface { @@ -34,11 +34,12 @@ public static function create(QueryInterface|FieldQueryInterface|array|stdClass| return new self($queries); } - private function __construct( - Serializable|array|stdClass $queriesOrArrayOfQueries, - ) { + /** @param array $queriesOrArrayOfQueries */ + private function __construct(array $queriesOrArrayOfQueries) + { $seenQueryOperators = []; $queries = []; + foreach ($queriesOrArrayOfQueries as $fieldPath => $query) { if ($query instanceof QueryInterface) { if ($query instanceof OperatorInterface) { @@ -53,7 +54,7 @@ private function __construct( continue; } - // Convert list of filters into $and + // Convert list of filters into CombinedFieldQuery if (self::isListOfFilters($query)) { if (count($query) === 1) { $query = $query[0]; @@ -62,19 +63,20 @@ private function __construct( } } - // Numbers are valid field paths, nothing to validate. $queries[$fieldPath] = $query; } $this->queries = $queries; } + /** @psalm-assert-if-true list $values */ private static function isListOfFilters(mixed $values): bool { if (! is_array($values) || ! array_is_list($values)) { return false; } + /** @var mixed $value */ foreach ($values as $value) { if ($value instanceof FieldQueryInterface) { return true; diff --git a/tests/Builder/BuilderEncoderTest.php b/tests/Builder/BuilderEncoderTest.php index 85eb46b1b..e9471c3bb 100644 --- a/tests/Builder/BuilderEncoderTest.php +++ b/tests/Builder/BuilderEncoderTest.php @@ -93,6 +93,9 @@ public function testPerformCount(): void /** * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/#examples * + * @param list $limit + * @param array $expectedLimit + * * @dataProvider provideExpressionFilterLimit */ public function testExpressionFilter(array $limit, array $expectedLimit): void @@ -100,10 +103,10 @@ public function testExpressionFilter(array $limit, array $expectedLimit): void $pipeline = new Pipeline( Stage::project( items: Projection::filter( - Expression::arrayFieldPath('items'), - Expression::gte(Expression::variable('item.price'), 100), - 'item', ...$limit, + input: Expression::arrayFieldPath('items'), + cond: Expression::gte(Expression::variable('item.price'), 100), + as:'item', ), ), ); @@ -133,7 +136,7 @@ public static function provideExpressionFilterLimit(): Generator ]; yield 'int limit' => [ - [1], + ['limit' => 1], ['limit' => 1], ]; } @@ -276,6 +279,7 @@ public function testRedactStage(): void $this->assertSamePipeline($expected, $pipeline); } + /** @param list> $expected */ private static function assertSamePipeline(array $expected, Pipeline $pipeline): void { $codec = new BuilderEncoder(); @@ -288,6 +292,8 @@ private static function assertSamePipeline(array $expected, Pipeline $pipeline): /** * Recursively convert associative arrays to objects. + * + * @param array $array */ private static function objectify(array &$array): void { diff --git a/tests/Builder/Type/OutputWindowTest.php b/tests/Builder/Type/OutputWindowTest.php new file mode 100644 index 000000000..46245e718 --- /dev/null +++ b/tests/Builder/Type/OutputWindowTest.php @@ -0,0 +1,107 @@ +createMock(WindowInterface::class), + ); + + $this->assertSame($operator, $outputWindow->operator); + $this->assertSame(Optional::Undefined, $outputWindow->window); + } + + public function testWithDocuments(): void + { + $outputWindow = new OutputWindow( + operator: $operator = $this->createMock(WindowInterface::class), + documents: [1, 5], + ); + + $this->assertSame($operator, $outputWindow->operator); + $this->assertEquals((object) ['documents' => [1, 5]], $outputWindow->window); + } + + public function testWithRange(): void + { + $outputWindow = new OutputWindow( + operator: $operator = $this->createMock(WindowInterface::class), + range: [1.2, 5.8], + ); + + $this->assertSame($operator, $outputWindow->operator); + $this->assertEquals((object) ['range' => [1.2, 5.8]], $outputWindow->window); + } + + public function testWithUnit(): void + { + $outputWindow = new OutputWindow( + operator: $operator = $this->createMock(WindowInterface::class), + unit: 'day', + ); + + $this->assertSame($operator, $outputWindow->operator); + $this->assertEquals((object) ['unit' => 'day'], $outputWindow->window); + } + + /** + * @param array $documents + * + * @dataProvider provideInvalidDocuments + */ + public function testRejectInvalidDocuments(array $documents): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected $documents argument to be a list of 2 string or int'); + + new OutputWindow( + operator: $this->createMock(WindowInterface::class), + documents: $documents, + ); + } + + public function provideInvalidDocuments(): Generator + { + yield 'too few' => [[1]]; + yield 'too many' => [[1, 2, 3]]; + yield 'invalid boolean' => [[1, true]]; + yield 'invalid float' => [[1, 4.3]]; + yield 'not a list' => [['foo' => 1, 'bar' => 2]]; + } + + /** + * @param array $range + * + * @dataProvider provideInvalidRange + */ + public function testRejectInvalidRange(array $range): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected $range argument to be a list of 2 string or numeric'); + + new OutputWindow( + operator: $this->createMock(WindowInterface::class), + range: $range, + ); + } + + public function provideInvalidRange(): Generator + { + yield 'too few' => [[1]]; + yield 'too many' => [[1, 2, 3]]; + yield 'invalid boolean' => [[1, true]]; + yield 'not a list' => [['foo' => 1, 'bar' => 2]]; + } +} diff --git a/tests/Builder/Type/QueryObjectTest.php b/tests/Builder/Type/QueryObjectTest.php index be9a46ed5..fce20cf61 100644 --- a/tests/Builder/Type/QueryObjectTest.php +++ b/tests/Builder/Type/QueryObjectTest.php @@ -4,6 +4,7 @@ namespace MongoDB\Tests\Builder\Type; +use Generator; use MongoDB\BSON\Regex; use MongoDB\Builder\Query\CommentOperator; use MongoDB\Builder\Query\EqOperator; @@ -14,8 +15,6 @@ use MongoDB\Builder\Type\QueryObject; use PHPUnit\Framework\TestCase; -use function array_keys; - class QueryObjectTest extends TestCase { public function testEmptyQueryObject(): void @@ -45,22 +44,20 @@ public function testCreateQueryObject(array $value, int $expectedCount = 1): voi $this->assertCount($expectedCount, $queryObject->queries); } - public function provideQueryObjectValue(): array + public function provideQueryObjectValue(): Generator { - return [ - 'int' => [['foo' => 1]], - 'float' => [['foo' => 1.1]], - 'string' => [['foo' => 'bar']], - 'bool' => [['foo' => true]], - 'null' => [['foo' => null]], - 'regex' => [['foo' => new Regex('foo')]], - 'object' => [['foo' => (object) ['bar' => 'baz']]], - 'list' => [['foo' => ['bar', 'baz']]], - 'operator as array' => [['foo' => ['$eq' => 1]]], - 'operator as object' => [['foo' => (object) ['$eq' => 1]]], - 'field query operator' => [['foo' => new EqOperator(1)]], - 'query operator' => [[new CommentOperator('foo'), 'foo' => 1], 2], - ]; + yield 'int' => [['foo' => 1]]; + yield 'float' => [['foo' => 1.1]]; + yield 'string' => [['foo' => 'bar']]; + yield 'bool' => [['foo' => true]]; + yield 'null' => [['foo' => null]]; + yield 'regex' => [['foo' => new Regex('foo')]]; + yield 'object' => [['foo' => (object) ['bar' => 'baz']]]; + yield 'list' => [['foo' => ['bar', 'baz']]]; + yield 'operator as array' => [['foo' => ['$eq' => 1]]]; + yield 'operator as object' => [['foo' => (object) ['$eq' => 1]]]; + yield 'field query operator' => [['foo' => new EqOperator(1)]]; + yield 'query operator' => [[new CommentOperator('foo'), 'foo' => 1], 2]; } public function testFieldQueryList(): void From dd0a91346d403461cd473354283efee640ceb835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 30 Oct 2023 15:20:36 +0100 Subject: [PATCH 08/95] PHPLIB-1265 Reject $ in variable and field path (#7) --- generator/src/ExpressionClassGenerator.php | 12 ++++ src/Builder/Expression/ArrayFieldPath.php | 8 +++ src/Builder/Expression/BinDataFieldPath.php | 8 +++ src/Builder/Expression/BoolFieldPath.php | 8 +++ src/Builder/Expression/DateFieldPath.php | 8 +++ src/Builder/Expression/DecimalFieldPath.php | 8 +++ src/Builder/Expression/DoubleFieldPath.php | 8 +++ src/Builder/Expression/FieldPath.php | 8 +++ src/Builder/Expression/IntFieldPath.php | 8 +++ .../Expression/JavascriptFieldPath.php | 8 +++ src/Builder/Expression/LongFieldPath.php | 8 +++ src/Builder/Expression/NullFieldPath.php | 8 +++ src/Builder/Expression/NumberFieldPath.php | 8 +++ src/Builder/Expression/ObjectFieldPath.php | 8 +++ src/Builder/Expression/ObjectIdFieldPath.php | 8 +++ src/Builder/Expression/RegexFieldPath.php | 8 +++ src/Builder/Expression/StringFieldPath.php | 8 +++ src/Builder/Expression/TimestampFieldPath.php | 8 +++ src/Builder/Expression/Variable.php | 9 +++ tests/Builder/FieldPathTest.php | 58 +++++++++++++++++++ tests/Builder/VariableTest.php | 16 +++++ 21 files changed, 231 insertions(+) create mode 100644 tests/Builder/FieldPathTest.php diff --git a/generator/src/ExpressionClassGenerator.php b/generator/src/ExpressionClassGenerator.php index 44d549c14..fc9119364 100644 --- a/generator/src/ExpressionClassGenerator.php +++ b/generator/src/ExpressionClassGenerator.php @@ -7,6 +7,7 @@ use LogicException; use MongoDB\CodeGenerator\Definition\ExpressionDefinition; use MongoDB\CodeGenerator\Definition\PhpObject; +use MongoDB\Exception\InvalidArgumentException; use Nette\PhpGenerator\PhpNamespace; use Nette\PhpGenerator\Type; use RuntimeException; @@ -64,6 +65,17 @@ public function createClassOrInterface(ExpressionDefinition $definition): PhpNam $constructor = $class->addMethod('__construct'); $constructor->addParameter('name')->setType($propertyType); + + $namespace->addUse(InvalidArgumentException::class); + $namespace->addUseFunction('sprintf'); + $namespace->addUseFunction('str_starts_with'); + $constructor->addBody(<<addBody('$this->name = $name;'); } elseif ($definition->generate === PhpObject::PhpInterface) { $interface = $namespace->addInterface($className); diff --git a/src/Builder/Expression/ArrayFieldPath.php b/src/Builder/Expression/ArrayFieldPath.php index 90ab52822..f475beb1c 100644 --- a/src/Builder/Expression/ArrayFieldPath.php +++ b/src/Builder/Expression/ArrayFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class ArrayFieldPath implements FieldPathInterface, ResolvesToArray { @@ -16,6 +20,10 @@ class ArrayFieldPath implements FieldPathInterface, ResolvesToArray public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/BinDataFieldPath.php b/src/Builder/Expression/BinDataFieldPath.php index b0152472c..77c2d39ea 100644 --- a/src/Builder/Expression/BinDataFieldPath.php +++ b/src/Builder/Expression/BinDataFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class BinDataFieldPath implements FieldPathInterface, ResolvesToBinData { @@ -16,6 +20,10 @@ class BinDataFieldPath implements FieldPathInterface, ResolvesToBinData public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/BoolFieldPath.php b/src/Builder/Expression/BoolFieldPath.php index 0258c3dcd..1dff80de0 100644 --- a/src/Builder/Expression/BoolFieldPath.php +++ b/src/Builder/Expression/BoolFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class BoolFieldPath implements FieldPathInterface, ResolvesToBool { @@ -16,6 +20,10 @@ class BoolFieldPath implements FieldPathInterface, ResolvesToBool public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/DateFieldPath.php b/src/Builder/Expression/DateFieldPath.php index 231ebd676..d32988537 100644 --- a/src/Builder/Expression/DateFieldPath.php +++ b/src/Builder/Expression/DateFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class DateFieldPath implements FieldPathInterface, ResolvesToDate { @@ -16,6 +20,10 @@ class DateFieldPath implements FieldPathInterface, ResolvesToDate public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/DecimalFieldPath.php b/src/Builder/Expression/DecimalFieldPath.php index a78420cb0..2a9136675 100644 --- a/src/Builder/Expression/DecimalFieldPath.php +++ b/src/Builder/Expression/DecimalFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class DecimalFieldPath implements FieldPathInterface, ResolvesToDecimal { @@ -16,6 +20,10 @@ class DecimalFieldPath implements FieldPathInterface, ResolvesToDecimal public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/DoubleFieldPath.php b/src/Builder/Expression/DoubleFieldPath.php index 4ed86c523..2af25b87c 100644 --- a/src/Builder/Expression/DoubleFieldPath.php +++ b/src/Builder/Expression/DoubleFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class DoubleFieldPath implements FieldPathInterface, ResolvesToDouble { @@ -16,6 +20,10 @@ class DoubleFieldPath implements FieldPathInterface, ResolvesToDouble public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/FieldPath.php b/src/Builder/Expression/FieldPath.php index b58b40461..4563994b9 100644 --- a/src/Builder/Expression/FieldPath.php +++ b/src/Builder/Expression/FieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class FieldPath implements FieldPathInterface, ResolvesToAny { @@ -16,6 +20,10 @@ class FieldPath implements FieldPathInterface, ResolvesToAny public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/IntFieldPath.php b/src/Builder/Expression/IntFieldPath.php index 1e68182bf..dc9baeecb 100644 --- a/src/Builder/Expression/IntFieldPath.php +++ b/src/Builder/Expression/IntFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class IntFieldPath implements FieldPathInterface, ResolvesToInt { @@ -16,6 +20,10 @@ class IntFieldPath implements FieldPathInterface, ResolvesToInt public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/JavascriptFieldPath.php b/src/Builder/Expression/JavascriptFieldPath.php index 2866c9f8d..e371ab7a9 100644 --- a/src/Builder/Expression/JavascriptFieldPath.php +++ b/src/Builder/Expression/JavascriptFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class JavascriptFieldPath implements FieldPathInterface, ResolvesToJavascript { @@ -16,6 +20,10 @@ class JavascriptFieldPath implements FieldPathInterface, ResolvesToJavascript public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/LongFieldPath.php b/src/Builder/Expression/LongFieldPath.php index de3e98263..7d18c015b 100644 --- a/src/Builder/Expression/LongFieldPath.php +++ b/src/Builder/Expression/LongFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class LongFieldPath implements FieldPathInterface, ResolvesToLong { @@ -16,6 +20,10 @@ class LongFieldPath implements FieldPathInterface, ResolvesToLong public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/NullFieldPath.php b/src/Builder/Expression/NullFieldPath.php index 62f021260..63bf171dc 100644 --- a/src/Builder/Expression/NullFieldPath.php +++ b/src/Builder/Expression/NullFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class NullFieldPath implements FieldPathInterface, ResolvesToNull { @@ -16,6 +20,10 @@ class NullFieldPath implements FieldPathInterface, ResolvesToNull public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/NumberFieldPath.php b/src/Builder/Expression/NumberFieldPath.php index 45b399d09..d8ab23a23 100644 --- a/src/Builder/Expression/NumberFieldPath.php +++ b/src/Builder/Expression/NumberFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class NumberFieldPath implements FieldPathInterface, ResolvesToNumber { @@ -16,6 +20,10 @@ class NumberFieldPath implements FieldPathInterface, ResolvesToNumber public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/ObjectFieldPath.php b/src/Builder/Expression/ObjectFieldPath.php index 93bd17e15..082989831 100644 --- a/src/Builder/Expression/ObjectFieldPath.php +++ b/src/Builder/Expression/ObjectFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class ObjectFieldPath implements FieldPathInterface, ResolvesToObject { @@ -16,6 +20,10 @@ class ObjectFieldPath implements FieldPathInterface, ResolvesToObject public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/ObjectIdFieldPath.php b/src/Builder/Expression/ObjectIdFieldPath.php index d152e1f0e..4428c1a39 100644 --- a/src/Builder/Expression/ObjectIdFieldPath.php +++ b/src/Builder/Expression/ObjectIdFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class ObjectIdFieldPath implements FieldPathInterface, ResolvesToObjectId { @@ -16,6 +20,10 @@ class ObjectIdFieldPath implements FieldPathInterface, ResolvesToObjectId public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/RegexFieldPath.php b/src/Builder/Expression/RegexFieldPath.php index 33901a9b3..14950bee7 100644 --- a/src/Builder/Expression/RegexFieldPath.php +++ b/src/Builder/Expression/RegexFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class RegexFieldPath implements FieldPathInterface, ResolvesToRegex { @@ -16,6 +20,10 @@ class RegexFieldPath implements FieldPathInterface, ResolvesToRegex public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/StringFieldPath.php b/src/Builder/Expression/StringFieldPath.php index 137aab741..487d34782 100644 --- a/src/Builder/Expression/StringFieldPath.php +++ b/src/Builder/Expression/StringFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class StringFieldPath implements FieldPathInterface, ResolvesToString { @@ -16,6 +20,10 @@ class StringFieldPath implements FieldPathInterface, ResolvesToString public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/TimestampFieldPath.php b/src/Builder/Expression/TimestampFieldPath.php index 310d78a4b..5aac9492f 100644 --- a/src/Builder/Expression/TimestampFieldPath.php +++ b/src/Builder/Expression/TimestampFieldPath.php @@ -9,6 +9,10 @@ namespace MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; class TimestampFieldPath implements FieldPathInterface, ResolvesToTimestamp { @@ -16,6 +20,10 @@ class TimestampFieldPath implements FieldPathInterface, ResolvesToTimestamp public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/src/Builder/Expression/Variable.php b/src/Builder/Expression/Variable.php index 99fff30cb..99b0750be 100644 --- a/src/Builder/Expression/Variable.php +++ b/src/Builder/Expression/Variable.php @@ -8,12 +8,21 @@ namespace MongoDB\Builder\Expression; +use MongoDB\Exception\InvalidArgumentException; + +use function sprintf; +use function str_starts_with; + class Variable implements ResolvesToAny { public readonly string $name; public function __construct(string $name) { + if (str_starts_with($name, '$')) { + throw new InvalidArgumentException(sprintf('Name cannot start with a dollar sign: "%s"', $name)); + } + $this->name = $name; } } diff --git a/tests/Builder/FieldPathTest.php b/tests/Builder/FieldPathTest.php new file mode 100644 index 000000000..ada1cc164 --- /dev/null +++ b/tests/Builder/FieldPathTest.php @@ -0,0 +1,58 @@ +assertSame('foo', $fieldPath->name); + $this->assertInstanceOf($resolveClass, $fieldPath); + $this->assertInstanceOf(FieldPathInterface::class, $fieldPath); + + // Ensure FieldPath resolves to any type + $this->assertTrue(is_subclass_of(Expression\FieldPath::class, $resolveClass), sprintf('%s instanceof %s', Expression\FieldPath::class, $resolveClass)); + } + + /** @dataProvider provideFieldPath */ + public function testRejectDollarPrefix(string $fieldPathClass): void + { + $this->expectException(InvalidArgumentException::class); + + Expression::{$fieldPathClass}('$foo'); + } + + public function provideFieldPath(): Generator + { + yield 'double' => ['doubleFieldPath', Expression\ResolvesToDouble::class]; + yield 'string' => ['stringFieldPath', Expression\ResolvesToString::class]; + yield 'object' => ['objectFieldPath', Expression\ResolvesToObject::class]; + yield 'array' => ['arrayFieldPath', Expression\ResolvesToArray::class]; + yield 'binData' => ['binDataFieldPath', Expression\ResolvesToBinData::class]; + yield 'objectId' => ['objectIdFieldPath', Expression\ResolvesToObjectId::class]; + yield 'bool' => ['boolFieldPath', Expression\ResolvesToBool::class]; + yield 'date' => ['dateFieldPath', Expression\ResolvesToDate::class]; + yield 'null' => ['nullFieldPath', Expression\ResolvesToNull::class]; + yield 'regex' => ['regexFieldPath', Expression\ResolvesToRegex::class]; + yield 'javascript' => ['javascriptFieldPath', Expression\ResolvesToJavascript::class]; + yield 'int' => ['intFieldPath', Expression\ResolvesToInt::class]; + yield 'timestamp' => ['timestampFieldPath', Expression\ResolvesToTimestamp::class]; + yield 'long' => ['longFieldPath', Expression\ResolvesToLong::class]; + yield 'decimal' => ['decimalFieldPath', Expression\ResolvesToDecimal::class]; + yield 'number' => ['numberFieldPath', Expression\ResolvesToNumber::class]; + yield 'any' => ['fieldPath', Expression\ResolvesToAny::class]; + } +} diff --git a/tests/Builder/VariableTest.php b/tests/Builder/VariableTest.php index 48f677206..f9273bfe2 100644 --- a/tests/Builder/VariableTest.php +++ b/tests/Builder/VariableTest.php @@ -7,10 +7,26 @@ use Generator; use MongoDB\Builder\Expression; use MongoDB\Builder\Variable; +use MongoDB\Exception\InvalidArgumentException; use PHPUnit\Framework\TestCase; class VariableTest extends TestCase { + public function testVariable(): void + { + $variable = Variable::variable('foo'); + $this->assertSame('foo', $variable->name); + $this->assertInstanceOf(Expression\ResolvesToAny::class, $variable); + $this->assertInstanceOf(Expression\Variable::class, $variable); + } + + public function testVariableRejectDollarPrefix(): void + { + $this->expectException(InvalidArgumentException::class); + + new Expression\Variable('$$foo'); + } + /** @dataProvider provideVariableBuilders */ public function testSystemVariables($factory): void { From f9dd4fba16c456f60acd8114c828019877790c9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 31 Oct 2023 08:33:45 +0100 Subject: [PATCH 09/95] Use final class for factory methods (#10) --- src/Builder/Accumulator.php | 7 ++++++- src/Builder/Expression.php | 7 ++++++- src/Builder/Projection.php | 7 ++++++- src/Builder/Query.php | 7 ++++++- src/Builder/Stage.php | 7 ++++++- src/Builder/Variable.php | 7 ++++++- 6 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/Builder/Accumulator.php b/src/Builder/Accumulator.php index 487a9168d..9bdb87456 100644 --- a/src/Builder/Accumulator.php +++ b/src/Builder/Accumulator.php @@ -17,7 +17,7 @@ * @see https://www.mongodb.com/docs/v3.4/reference/operator/aggregation-group/ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/ */ -enum Accumulator +final class Accumulator { use Accumulator\FactoryTrait; @@ -35,4 +35,9 @@ public static function outputWindow( ): OutputWindow { return new OutputWindow($operator, $documents, $range, $unit); } + + private function __construct() + { + // This class cannot be instantiated + } } diff --git a/src/Builder/Expression.php b/src/Builder/Expression.php index 0ad2d5e11..9e4779145 100644 --- a/src/Builder/Expression.php +++ b/src/Builder/Expression.php @@ -9,8 +9,13 @@ * * @see https://docs.mongodb.com/manual/reference/operator/aggregation/ */ -enum Expression +final class Expression { use Expression\ExpressionFactoryTrait; use Expression\FactoryTrait; + + private function __construct() + { + // This class cannot be instantiated + } } diff --git a/src/Builder/Projection.php b/src/Builder/Projection.php index 65430e385..e8e90f7f6 100644 --- a/src/Builder/Projection.php +++ b/src/Builder/Projection.php @@ -11,7 +11,12 @@ * * @see https://docs.mongodb.com/manual/reference/operator/projection/ */ -enum Projection +final class Projection { use FactoryTrait; + + private function __construct() + { + // This class cannot be instantiated + } } diff --git a/src/Builder/Query.php b/src/Builder/Query.php index ffba4b887..1564b25cb 100644 --- a/src/Builder/Query.php +++ b/src/Builder/Query.php @@ -20,7 +20,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/ */ -enum Query +final class Query { use Query\FactoryTrait { regex as private generatedRegex; @@ -44,4 +44,9 @@ public static function query(FieldQueryInterface|QueryInterface|Serializable|arr { return QueryObject::create(...$query); } + + private function __construct() + { + // This class cannot be instantiated + } } diff --git a/src/Builder/Stage.php b/src/Builder/Stage.php index 2ab7c20b4..c3146a2fb 100644 --- a/src/Builder/Stage.php +++ b/src/Builder/Stage.php @@ -10,7 +10,7 @@ use MongoDB\Builder\Type\QueryInterface; use stdClass; -enum Stage +final class Stage { use Stage\FactoryTrait { match as private generatedMatch; @@ -28,4 +28,9 @@ public static function match(FieldQueryInterface|QueryInterface|Serializable|arr // Override the generated method to allow variadic arguments return self::generatedMatch($queries); } + + private function __construct() + { + // This class cannot be instantiated + } } diff --git a/src/Builder/Variable.php b/src/Builder/Variable.php index 0eaec8b7f..5a9473061 100644 --- a/src/Builder/Variable.php +++ b/src/Builder/Variable.php @@ -16,7 +16,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/aggregation-variables/ */ -enum Variable +final class Variable { /** * A variable that returns the current datetime value. @@ -150,4 +150,9 @@ public static function variable(string $name): Expression\Variable { return new Expression\Variable($name); } + + private function __construct() + { + // This class cannot be instantiated + } } From e5ab4903a64ee195d9ea4f63ace120e838c4de90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 31 Oct 2023 18:05:46 +0100 Subject: [PATCH 10/95] PHPLIB-1292 Accept a single array as query to support filter on numerical field names (#9) Numerical field names can't be used with named argument, because even if they comes from an array unpacking, the numerical keys are recalculated as a sequence: https://3v4l.org/Gj7jC Everywhere a query is expected, an single array can be passed instead of the variadic arguments. * Tuned the accepted types for field query * Fixed CS (newline after if) in generated code --- generator/config/expressions.php | 4 +- generator/config/query/and.yaml | 3 +- generator/config/query/nor.yaml | 3 +- generator/config/query/not.yaml | 3 +- generator/config/query/or.yaml | 3 +- generator/config/schema.json | 1 + generator/src/OperatorClassGenerator.php | 10 ++-- generator/src/OperatorFactoryGenerator.php | 7 +++ src/Builder/Accumulator/FactoryTrait.php | 1 + .../Accumulator/MergeObjectsAccumulator.php | 2 + src/Builder/BuilderEncoder.php | 4 +- src/Builder/Expression/AddOperator.php | 2 + .../Expression/AllElementsTrueOperator.php | 2 + src/Builder/Expression/AndOperator.php | 2 + src/Builder/Expression/AvgOperator.php | 2 + src/Builder/Expression/BitAndOperator.php | 2 + src/Builder/Expression/BitOrOperator.php | 2 + src/Builder/Expression/BitXorOperator.php | 2 + .../Expression/ConcatArraysOperator.php | 2 + src/Builder/Expression/ConcatOperator.php | 2 + src/Builder/Expression/FactoryTrait.php | 22 +++++++ src/Builder/Expression/IfNullOperator.php | 2 + src/Builder/Expression/IsArrayOperator.php | 2 + src/Builder/Expression/IsNumberOperator.php | 2 + src/Builder/Expression/MaxOperator.php | 2 + src/Builder/Expression/MinOperator.php | 2 + src/Builder/Expression/MultiplyOperator.php | 2 + src/Builder/Expression/OrOperator.php | 2 + src/Builder/Expression/SetEqualsOperator.php | 2 + .../Expression/SetIntersectionOperator.php | 2 + src/Builder/Expression/SetUnionOperator.php | 2 + src/Builder/Expression/StdDevPopOperator.php | 2 + src/Builder/Expression/StdDevSampOperator.php | 2 + src/Builder/Expression/SumOperator.php | 2 + src/Builder/Projection/ElemMatchOperator.php | 16 ++---- src/Builder/Projection/FactoryTrait.php | 7 +-- src/Builder/Query.php | 2 +- src/Builder/Query/AllOperator.php | 2 + src/Builder/Query/AndOperator.php | 23 ++++---- src/Builder/Query/ElemMatchOperator.php | 16 ++---- src/Builder/Query/FactoryTrait.php | 33 ++++++----- src/Builder/Query/NorOperator.php | 23 ++++---- src/Builder/Query/NotOperator.php | 24 +++----- src/Builder/Query/OrOperator.php | 23 ++++---- src/Builder/Stage.php | 8 ++- src/Builder/Stage/AddFieldsStage.php | 2 + src/Builder/Stage/FacetStage.php | 2 + src/Builder/Stage/FactoryTrait.php | 13 +++-- src/Builder/Stage/GeoNearStage.php | 13 ++--- src/Builder/Stage/GraphLookupStage.php | 15 ++--- src/Builder/Stage/GroupStage.php | 2 + src/Builder/Stage/MatchStage.php | 16 ++---- src/Builder/Stage/ProjectStage.php | 2 + src/Builder/Stage/SetStage.php | 2 + src/Builder/Stage/UnsetStage.php | 2 + src/Builder/Type/OutputWindow.php | 2 +- src/Builder/Type/QueryObject.php | 21 ++++++- tests/Builder/BuilderEncoderTest.php | 57 +++++++++++++++++++ tests/Builder/Type/QueryObjectTest.php | 27 +++++++-- 59 files changed, 313 insertions(+), 147 deletions(-) diff --git a/generator/config/expressions.php b/generator/config/expressions.php index fa88274ba..869e1e784 100644 --- a/generator/config/expressions.php +++ b/generator/config/expressions.php @@ -83,11 +83,11 @@ ], 'fieldQuery' => [ 'returnType' => Type\FieldQueryInterface::class, - 'acceptedTypes' => [Type\FieldQueryInterface::class, ...$bsonTypes['any']], + 'acceptedTypes' => [Type\FieldQueryInterface::class, BSON\Regex::class, 'bool', 'string', 'array', 'null', stdClass::class, ...$bsonTypes['number']], ], 'query' => [ 'returnType' => Type\QueryInterface::class, - 'acceptedTypes' => [Type\QueryInterface::class, ...$bsonTypes['object']], + 'acceptedTypes' => [Type\QueryInterface::class, 'array'], ], 'projection' => [ 'returnType' => Type\ProjectionInterface::class, diff --git a/generator/config/query/and.yaml b/generator/config/query/and.yaml index 14fde59a2..86d8faf12 100644 --- a/generator/config/query/and.yaml +++ b/generator/config/query/and.yaml @@ -8,7 +8,8 @@ description: | Joins query clauses with a logical AND returns all documents that match the conditions of both clauses. arguments: - - name: expression + name: queries type: - query variadic: array + variadicMin: 1 diff --git a/generator/config/query/nor.yaml b/generator/config/query/nor.yaml index c0fdf53d6..d6349b1e8 100644 --- a/generator/config/query/nor.yaml +++ b/generator/config/query/nor.yaml @@ -8,7 +8,8 @@ description: | Joins query clauses with a logical NOR returns all documents that fail to match both clauses. arguments: - - name: expression + name: queries type: - query variadic: array + variadicMin: 1 diff --git a/generator/config/query/not.yaml b/generator/config/query/not.yaml index bd7d1d0fe..a2d05312a 100644 --- a/generator/config/query/not.yaml +++ b/generator/config/query/not.yaml @@ -10,5 +10,4 @@ arguments: - name: expression type: - - query - - regex + - fieldQuery diff --git a/generator/config/query/or.yaml b/generator/config/query/or.yaml index 8d763871c..ec9cc02a6 100644 --- a/generator/config/query/or.yaml +++ b/generator/config/query/or.yaml @@ -8,7 +8,8 @@ description: | Joins query clauses with a logical OR returns all documents that match the conditions of either clause. arguments: - - name: expression + name: queries type: - query variadic: array + variadicMin: 1 diff --git a/generator/config/schema.json b/generator/config/schema.json index 253832e8a..e5ea1638b 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -102,6 +102,7 @@ "enum": [ "accumulator", "query", + "fieldQuery", "pipeline", "window", "expression", diff --git a/generator/src/OperatorClassGenerator.php b/generator/src/OperatorClassGenerator.php index f1f336396..8f7be8ec1 100644 --- a/generator/src/OperatorClassGenerator.php +++ b/generator/src/OperatorClassGenerator.php @@ -76,11 +76,12 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition $constuctor->setVariadic(); $constuctor->addComment('@param ' . $type->doc . ' ...$' . $argument->name . rtrim(' ' . $argument->description)); - if ($argument->variadicMin !== null) { + if ($argument->variadicMin > 0) { $constuctor->addBody(<<name}) < {$argument->variadicMin}) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for \${$argument->name}, got %d.', {$argument->variadicMin}, \count(\${$argument->name}))); } + PHP); } @@ -96,6 +97,7 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition if (! array_is_list(\${$argument->name})) { throw new InvalidArgumentException('Expected \${$argument->name} arguments to be a list (array), named arguments are not supported'); } + PHP); } elseif ($argument->variadic === VariadicType::Object) { $namespace->addUse(stdClass::class); @@ -109,6 +111,7 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition throw new InvalidArgumentException('Expected \${$argument->name} arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); } } + \${$argument->name} = (object) \${$argument->name}; PHP); } @@ -140,11 +143,10 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition if ($type->query) { $namespace->addUseFunction('is_array'); - $namespace->addUseFunction('is_object'); $namespace->addUse(QueryObject::class); $constuctor->addBody(<<name}) || is_object(\${$argument->name})) { - \${$argument->name} = QueryObject::create(...\${$argument->name}); + if (is_array(\${$argument->name})) { + \${$argument->name} = QueryObject::create(\${$argument->name}); } PHP); diff --git a/generator/src/OperatorFactoryGenerator.php b/generator/src/OperatorFactoryGenerator.php index f04c56c40..3e5506647 100644 --- a/generator/src/OperatorFactoryGenerator.php +++ b/generator/src/OperatorFactoryGenerator.php @@ -6,6 +6,7 @@ use MongoDB\CodeGenerator\Definition\GeneratorDefinition; use MongoDB\CodeGenerator\Definition\OperatorDefinition; +use MongoDB\CodeGenerator\Definition\VariadicType; use Nette\PhpGenerator\Literal; use Nette\PhpGenerator\PhpNamespace; use Nette\PhpGenerator\TraitType; @@ -67,6 +68,12 @@ private function addMethod(GeneratorDefinition $definition, OperatorDefinition $ $parameter = $method->addParameter($argument->name); $parameter->setType($type->native); if ($argument->variadic) { + if ($argument->variadic === VariadicType::Array) { + // Warn that named arguments are not supported + // @see https://psalm.dev/docs/running_psalm/issues/NamedArgumentNotAllowed/ + $method->addComment('@no-named-arguments'); + } + $method->setVariadic(); $method->addComment('@param ' . $type->doc . ' ...$' . $argument->name . rtrim(' ' . $argument->description)); $args[] = '...$' . $argument->name; diff --git a/src/Builder/Accumulator/FactoryTrait.php b/src/Builder/Accumulator/FactoryTrait.php index d0f5d8989..09f70c918 100644 --- a/src/Builder/Accumulator/FactoryTrait.php +++ b/src/Builder/Accumulator/FactoryTrait.php @@ -332,6 +332,7 @@ public static function median( * Combines multiple documents into a single document. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/ + * @no-named-arguments * @param Document|ResolvesToObject|Serializable|array|stdClass ...$document Any valid expression that resolves to a document. */ public static function mergeObjects( diff --git a/src/Builder/Accumulator/MergeObjectsAccumulator.php b/src/Builder/Accumulator/MergeObjectsAccumulator.php index 648098bfd..8f1ee3d5f 100644 --- a/src/Builder/Accumulator/MergeObjectsAccumulator.php +++ b/src/Builder/Accumulator/MergeObjectsAccumulator.php @@ -40,9 +40,11 @@ public function __construct(Document|Serializable|ResolvesToObject|stdClass|arra if (\count($document) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $document, got %d.', 1, \count($document))); } + if (! array_is_list($document)) { throw new InvalidArgumentException('Expected $document arguments to be a list (array), named arguments are not supported'); } + $this->document = $document; } diff --git a/src/Builder/BuilderEncoder.php b/src/Builder/BuilderEncoder.php index 93e1a113a..0b38dbf9b 100644 --- a/src/Builder/BuilderEncoder.php +++ b/src/Builder/BuilderEncoder.php @@ -213,14 +213,14 @@ private function encodeQueryObject(QueryObject $query): stdClass if ($value instanceof QueryInterface) { // The sub-objects is merged into the main object, replacing duplicate keys foreach (get_object_vars($this->recursiveEncode($value)) as $subKey => $subValue) { - if (property_exists($result, $subKey)) { + if (property_exists($result, (string) $subKey)) { throw new LogicException(sprintf('Duplicate key "%s" in query object', $subKey)); } $result->{$subKey} = $subValue; } } else { - if (property_exists($result, $key)) { + if (property_exists($result, (string) $key)) { throw new LogicException(sprintf('Duplicate key "%s" in query object', $key)); } diff --git a/src/Builder/Expression/AddOperator.php b/src/Builder/Expression/AddOperator.php index e75bd1e40..82e0e59cc 100644 --- a/src/Builder/Expression/AddOperator.php +++ b/src/Builder/Expression/AddOperator.php @@ -38,9 +38,11 @@ public function __construct(Decimal128|Int64|UTCDateTime|ResolvesToDate|Resolves if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/AllElementsTrueOperator.php b/src/Builder/Expression/AllElementsTrueOperator.php index 7fa3eb35d..5724d54d5 100644 --- a/src/Builder/Expression/AllElementsTrueOperator.php +++ b/src/Builder/Expression/AllElementsTrueOperator.php @@ -37,9 +37,11 @@ public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expr if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/AndOperator.php b/src/Builder/Expression/AndOperator.php index 0cc9d1700..e576ff71c 100644 --- a/src/Builder/Expression/AndOperator.php +++ b/src/Builder/Expression/AndOperator.php @@ -41,9 +41,11 @@ public function __construct( if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/AvgOperator.php b/src/Builder/Expression/AvgOperator.php index 069ac1846..f21219e25 100644 --- a/src/Builder/Expression/AvgOperator.php +++ b/src/Builder/Expression/AvgOperator.php @@ -38,9 +38,11 @@ public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expr if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/BitAndOperator.php b/src/Builder/Expression/BitAndOperator.php index 1391a6063..37e0cf4a5 100644 --- a/src/Builder/Expression/BitAndOperator.php +++ b/src/Builder/Expression/BitAndOperator.php @@ -37,9 +37,11 @@ public function __construct(Int64|ResolvesToInt|ResolvesToLong|int ...$expressio if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/BitOrOperator.php b/src/Builder/Expression/BitOrOperator.php index f73d277e9..9f9d6af4f 100644 --- a/src/Builder/Expression/BitOrOperator.php +++ b/src/Builder/Expression/BitOrOperator.php @@ -37,9 +37,11 @@ public function __construct(Int64|ResolvesToInt|ResolvesToLong|int ...$expressio if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/BitXorOperator.php b/src/Builder/Expression/BitXorOperator.php index 677b8c628..d0ae1ba6f 100644 --- a/src/Builder/Expression/BitXorOperator.php +++ b/src/Builder/Expression/BitXorOperator.php @@ -37,9 +37,11 @@ public function __construct(Int64|ResolvesToInt|ResolvesToLong|int ...$expressio if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/ConcatArraysOperator.php b/src/Builder/Expression/ConcatArraysOperator.php index ca107df50..45d2dabd9 100644 --- a/src/Builder/Expression/ConcatArraysOperator.php +++ b/src/Builder/Expression/ConcatArraysOperator.php @@ -37,9 +37,11 @@ public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$arra if (\count($array) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $array, got %d.', 1, \count($array))); } + if (! array_is_list($array)) { throw new InvalidArgumentException('Expected $array arguments to be a list (array), named arguments are not supported'); } + $this->array = $array; } diff --git a/src/Builder/Expression/ConcatOperator.php b/src/Builder/Expression/ConcatOperator.php index 75527a193..4bfd99ca7 100644 --- a/src/Builder/Expression/ConcatOperator.php +++ b/src/Builder/Expression/ConcatOperator.php @@ -35,9 +35,11 @@ public function __construct(ResolvesToString|string ...$expression) if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index a3205bc7a..01fbf2eef 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -71,6 +71,7 @@ public static function acosh(Decimal128|Int64|ResolvesToNumber|float|int $expres * Adds numbers to return the sum, or adds numbers and a date to return a new date. If adding numbers and a date, treats the numbers as milliseconds. Accepts any number of argument expressions, but at most, one expression can resolve to a date. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/add/ + * @no-named-arguments * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int ...$expression The arguments can be any valid expression as long as they resolve to either all numbers or to numbers and a date. */ public static function add( @@ -84,6 +85,7 @@ public static function add( * Returns true if no element of a set evaluates to false, otherwise, returns false. Accepts a single argument expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/allElementsTrue/ + * @no-named-arguments * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression */ public static function allElementsTrue( @@ -97,6 +99,7 @@ public static function allElementsTrue( * Returns true only when all its expressions evaluate to true. Accepts any number of argument expressions. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/and/ + * @no-named-arguments * @param Decimal128|ExpressionInterface|Int64|ResolvesToBool|ResolvesToNull|ResolvesToNumber|ResolvesToString|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression */ public static function and( @@ -219,6 +222,7 @@ public static function atanh(Decimal128|Int64|ResolvesToNumber|float|int $expres * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/avg/ + * @no-named-arguments * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression */ public static function avg(Decimal128|Int64|ResolvesToNumber|float|int ...$expression): AvgOperator @@ -244,6 +248,7 @@ public static function binarySize( * New in MongoDB 6.3. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitAnd/ + * @no-named-arguments * @param Int64|ResolvesToInt|ResolvesToLong|int ...$expression */ public static function bitAnd(Int64|ResolvesToInt|ResolvesToLong|int ...$expression): BitAndOperator @@ -268,6 +273,7 @@ public static function bitNot(Int64|ResolvesToInt|ResolvesToLong|int $expression * New in MongoDB 6.3. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitOr/ + * @no-named-arguments * @param Int64|ResolvesToInt|ResolvesToLong|int ...$expression */ public static function bitOr(Int64|ResolvesToInt|ResolvesToLong|int ...$expression): BitOrOperator @@ -280,6 +286,7 @@ public static function bitOr(Int64|ResolvesToInt|ResolvesToLong|int ...$expressi * New in MongoDB 6.3. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitXor/ + * @no-named-arguments * @param Int64|ResolvesToInt|ResolvesToLong|int ...$expression */ public static function bitXor(Int64|ResolvesToInt|ResolvesToLong|int ...$expression): BitXorOperator @@ -330,6 +337,7 @@ public static function cmp( * Concatenates any number of strings. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/concat/ + * @no-named-arguments * @param ResolvesToString|non-empty-string ...$expression */ public static function concat(ResolvesToString|string ...$expression): ConcatOperator @@ -341,6 +349,7 @@ public static function concat(ResolvesToString|string ...$expression): ConcatOpe * Concatenates arrays to return the concatenated array. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/concatArrays/ + * @no-named-arguments * @param BSONArray|PackedArray|ResolvesToArray|array ...$array */ public static function concatArrays(PackedArray|ResolvesToArray|BSONArray|array ...$array): ConcatArraysOperator @@ -806,6 +815,7 @@ public static function hour( * Returns either the non-null result of the first expression or the result of the second expression if the first expression results in a null result. Null result encompasses instances of undefined values or missing fields. Accepts two expressions as arguments. The result of the second expression can be null. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ifNull/ + * @no-named-arguments * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression */ public static function ifNull( @@ -920,6 +930,7 @@ public static function integral( * Determines if the operand is an array. Returns a boolean. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isArray/ + * @no-named-arguments * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression */ public static function isArray( @@ -935,6 +946,7 @@ public static function isArray( * New in MongoDB 4.4. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/ + * @no-named-arguments * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression */ public static function isNumber( @@ -1153,6 +1165,7 @@ public static function map( * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/ + * @no-named-arguments * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression */ public static function max( @@ -1225,6 +1238,7 @@ public static function millisecond( * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/ + * @no-named-arguments * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression */ public static function min( @@ -1298,6 +1312,7 @@ public static function month( * Multiplies numbers to return the product. Accepts any number of argument expressions. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/multiply/ + * @no-named-arguments * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression The arguments can be any valid expression as long as they resolve to numbers. * Starting in MongoDB 6.1 you can optimize the $multiply operation. To improve performance, group references at the end of the argument list. */ @@ -1351,6 +1366,7 @@ public static function objectToArray( * Returns true when any of its expressions evaluates to true. Accepts any number of argument expressions. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/or/ + * @no-named-arguments * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression */ public static function or( @@ -1655,6 +1671,7 @@ public static function setDifference( * Returns true if the input sets have the same distinct elements. Accepts two or more argument expressions. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setEquals/ + * @no-named-arguments * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression */ public static function setEquals(PackedArray|ResolvesToArray|BSONArray|array ...$expression): SetEqualsOperator @@ -1685,6 +1702,7 @@ public static function setField( * Returns a set with elements that appear in all of the input sets. Accepts any number of argument expressions. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/ + * @no-named-arguments * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression */ public static function setIntersection( @@ -1713,6 +1731,7 @@ public static function setIsSubset( * Returns a set with elements that appear in any of the input sets. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/ + * @no-named-arguments * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression */ public static function setUnion(PackedArray|ResolvesToArray|BSONArray|array ...$expression): SetUnionOperator @@ -1822,6 +1841,7 @@ public static function sqrt(Decimal128|Int64|ResolvesToNumber|float|int $number) * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/ + * @no-named-arguments * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression */ public static function stdDevPop(Decimal128|Int64|ResolvesToNumber|float|int ...$expression): StdDevPopOperator @@ -1834,6 +1854,7 @@ public static function stdDevPop(Decimal128|Int64|ResolvesToNumber|float|int ... * If the values represent the entire population of data or you do not wish to generalize about a larger population, use $stdDevPop instead. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevSamp/ + * @no-named-arguments * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression */ public static function stdDevSamp(Decimal128|Int64|ResolvesToNumber|float|int ...$expression): StdDevSampOperator @@ -1949,6 +1970,7 @@ public static function subtract( * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/ + * @no-named-arguments * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression */ public static function sum(Decimal128|Int64|ResolvesToNumber|float|int ...$expression): SumOperator diff --git a/src/Builder/Expression/IfNullOperator.php b/src/Builder/Expression/IfNullOperator.php index 8cbd02845..838b36a38 100644 --- a/src/Builder/Expression/IfNullOperator.php +++ b/src/Builder/Expression/IfNullOperator.php @@ -38,9 +38,11 @@ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|i if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/IsArrayOperator.php b/src/Builder/Expression/IsArrayOperator.php index 0d499e65b..157bcef5f 100644 --- a/src/Builder/Expression/IsArrayOperator.php +++ b/src/Builder/Expression/IsArrayOperator.php @@ -38,9 +38,11 @@ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|i if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/IsNumberOperator.php b/src/Builder/Expression/IsNumberOperator.php index 70a0b12dc..2eb1e1e2e 100644 --- a/src/Builder/Expression/IsNumberOperator.php +++ b/src/Builder/Expression/IsNumberOperator.php @@ -40,9 +40,11 @@ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|i if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/MaxOperator.php b/src/Builder/Expression/MaxOperator.php index 6befee674..e23940c26 100644 --- a/src/Builder/Expression/MaxOperator.php +++ b/src/Builder/Expression/MaxOperator.php @@ -39,9 +39,11 @@ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|i if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/MinOperator.php b/src/Builder/Expression/MinOperator.php index 4152fc193..6d5d89de5 100644 --- a/src/Builder/Expression/MinOperator.php +++ b/src/Builder/Expression/MinOperator.php @@ -39,9 +39,11 @@ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|i if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/MultiplyOperator.php b/src/Builder/Expression/MultiplyOperator.php index 4c76adae2..652d570e1 100644 --- a/src/Builder/Expression/MultiplyOperator.php +++ b/src/Builder/Expression/MultiplyOperator.php @@ -41,9 +41,11 @@ public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expr if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/OrOperator.php b/src/Builder/Expression/OrOperator.php index 46f216ad5..16753c635 100644 --- a/src/Builder/Expression/OrOperator.php +++ b/src/Builder/Expression/OrOperator.php @@ -39,9 +39,11 @@ public function __construct( if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/SetEqualsOperator.php b/src/Builder/Expression/SetEqualsOperator.php index d50a6c290..4e1377d77 100644 --- a/src/Builder/Expression/SetEqualsOperator.php +++ b/src/Builder/Expression/SetEqualsOperator.php @@ -37,9 +37,11 @@ public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expr if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/SetIntersectionOperator.php b/src/Builder/Expression/SetIntersectionOperator.php index a58a931b8..b3568ffc2 100644 --- a/src/Builder/Expression/SetIntersectionOperator.php +++ b/src/Builder/Expression/SetIntersectionOperator.php @@ -37,9 +37,11 @@ public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expr if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/SetUnionOperator.php b/src/Builder/Expression/SetUnionOperator.php index ec3bf68ca..9f297bb2c 100644 --- a/src/Builder/Expression/SetUnionOperator.php +++ b/src/Builder/Expression/SetUnionOperator.php @@ -37,9 +37,11 @@ public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expr if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/StdDevPopOperator.php b/src/Builder/Expression/StdDevPopOperator.php index a6666d757..41eda3df1 100644 --- a/src/Builder/Expression/StdDevPopOperator.php +++ b/src/Builder/Expression/StdDevPopOperator.php @@ -39,9 +39,11 @@ public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expr if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/StdDevSampOperator.php b/src/Builder/Expression/StdDevSampOperator.php index 81fe62e95..41d390186 100644 --- a/src/Builder/Expression/StdDevSampOperator.php +++ b/src/Builder/Expression/StdDevSampOperator.php @@ -38,9 +38,11 @@ public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expr if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Expression/SumOperator.php b/src/Builder/Expression/SumOperator.php index 3d314c0f6..aae0fdd7a 100644 --- a/src/Builder/Expression/SumOperator.php +++ b/src/Builder/Expression/SumOperator.php @@ -38,9 +38,11 @@ public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expr if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + if (! array_is_list($expression)) { throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); } + $this->expression = $expression; } diff --git a/src/Builder/Projection/ElemMatchOperator.php b/src/Builder/Projection/ElemMatchOperator.php index 22dcb55d5..03daadcc8 100644 --- a/src/Builder/Projection/ElemMatchOperator.php +++ b/src/Builder/Projection/ElemMatchOperator.php @@ -8,17 +8,13 @@ namespace MongoDB\Builder\Projection; -use MongoDB\BSON\Document; -use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\ProjectionInterface; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Builder\Type\QueryObject; -use stdClass; use function is_array; -use function is_object; /** * Projects the first element in an array that matches the specified $elemMatch condition. @@ -29,16 +25,16 @@ class ElemMatchOperator implements ProjectionInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var Document|QueryInterface|Serializable|array|stdClass $query */ - public readonly Document|Serializable|QueryInterface|stdClass|array $query; + /** @var QueryInterface|array $query */ + public readonly QueryInterface|array $query; /** - * @param Document|QueryInterface|Serializable|array|stdClass $query + * @param QueryInterface|array $query */ - public function __construct(Document|Serializable|QueryInterface|stdClass|array $query) + public function __construct(QueryInterface|array $query) { - if (is_array($query) || is_object($query)) { - $query = QueryObject::create(...$query); + if (is_array($query)) { + $query = QueryObject::create($query); } $this->query = $query; diff --git a/src/Builder/Projection/FactoryTrait.php b/src/Builder/Projection/FactoryTrait.php index b25f7ffa7..ea87adac2 100644 --- a/src/Builder/Projection/FactoryTrait.php +++ b/src/Builder/Projection/FactoryTrait.php @@ -8,16 +8,13 @@ namespace MongoDB\Builder\Projection; -use MongoDB\BSON\Document; use MongoDB\BSON\PackedArray; -use MongoDB\BSON\Serializable; use MongoDB\Builder\Expression\ResolvesToArray; use MongoDB\Builder\Expression\ResolvesToBool; use MongoDB\Builder\Expression\ResolvesToInt; use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Model\BSONArray; -use stdClass; /** * @internal @@ -28,9 +25,9 @@ trait FactoryTrait * Projects the first element in an array that matches the specified $elemMatch condition. * * @see https://www.mongodb.com/docs/manual/reference/operator/projection/elemMatch/ - * @param Document|QueryInterface|Serializable|array|stdClass $query + * @param QueryInterface|array $query */ - public static function elemMatch(Document|Serializable|QueryInterface|stdClass|array $query): ElemMatchOperator + public static function elemMatch(QueryInterface|array $query): ElemMatchOperator { return new ElemMatchOperator($query); } diff --git a/src/Builder/Query.php b/src/Builder/Query.php index 1564b25cb..a7f4ccccb 100644 --- a/src/Builder/Query.php +++ b/src/Builder/Query.php @@ -42,7 +42,7 @@ public static function regex(Regex|string $regex, string|null $flags = null): Re public static function query(FieldQueryInterface|QueryInterface|Serializable|array|bool|float|int|stdClass|string|null ...$query): QueryInterface { - return QueryObject::create(...$query); + return QueryObject::create($query); } private function __construct() diff --git a/src/Builder/Query/AllOperator.php b/src/Builder/Query/AllOperator.php index 7cd57dd26..b9a87565a 100644 --- a/src/Builder/Query/AllOperator.php +++ b/src/Builder/Query/AllOperator.php @@ -38,9 +38,11 @@ public function __construct(Type|stdClass|array|bool|float|int|null|string ...$v if (\count($value) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $value, got %d.', 1, \count($value))); } + if (! array_is_list($value)) { throw new InvalidArgumentException('Expected $value arguments to be a list (array), named arguments are not supported'); } + $this->value = $value; } diff --git a/src/Builder/Query/AndOperator.php b/src/Builder/Query/AndOperator.php index dcbe6766b..071c37097 100644 --- a/src/Builder/Query/AndOperator.php +++ b/src/Builder/Query/AndOperator.php @@ -8,13 +8,10 @@ namespace MongoDB\Builder\Query; -use MongoDB\BSON\Document; -use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Exception\InvalidArgumentException; -use stdClass; use function array_is_list; @@ -27,22 +24,24 @@ class AndOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ - public readonly array $expression; + /** @var list $queries */ + public readonly array $queries; /** - * @param Document|QueryInterface|Serializable|array|stdClass ...$expression + * @param QueryInterface|array ...$queries * @no-named-arguments */ - public function __construct(Document|Serializable|QueryInterface|stdClass|array ...$expression) + public function __construct(QueryInterface|array ...$queries) { - if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + if (\count($queries) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $queries, got %d.', 1, \count($queries))); } - if (! array_is_list($expression)) { - throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + + if (! array_is_list($queries)) { + throw new InvalidArgumentException('Expected $queries arguments to be a list (array), named arguments are not supported'); } - $this->expression = $expression; + + $this->queries = $queries; } public function getOperator(): string diff --git a/src/Builder/Query/ElemMatchOperator.php b/src/Builder/Query/ElemMatchOperator.php index e1d665375..9b0054142 100644 --- a/src/Builder/Query/ElemMatchOperator.php +++ b/src/Builder/Query/ElemMatchOperator.php @@ -8,17 +8,13 @@ namespace MongoDB\Builder\Query; -use MongoDB\BSON\Document; -use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Builder\Type\QueryObject; -use stdClass; use function is_array; -use function is_object; /** * The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria. @@ -29,16 +25,16 @@ class ElemMatchOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var Document|QueryInterface|Serializable|array|stdClass $query */ - public readonly Document|Serializable|QueryInterface|stdClass|array $query; + /** @var QueryInterface|array $query */ + public readonly QueryInterface|array $query; /** - * @param Document|QueryInterface|Serializable|array|stdClass $query + * @param QueryInterface|array $query */ - public function __construct(Document|Serializable|QueryInterface|stdClass|array $query) + public function __construct(QueryInterface|array $query) { - if (is_array($query) || is_object($query)) { - $query = QueryObject::create(...$query); + if (is_array($query)) { + $query = QueryObject::create($query); } $this->query = $query; diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index 55098728b..473e46797 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -18,6 +18,7 @@ use MongoDB\BSON\Serializable; use MongoDB\BSON\Type; use MongoDB\Builder\Type\ExpressionInterface; +use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\GeometryInterface; use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\QueryInterface; @@ -33,6 +34,7 @@ trait FactoryTrait * Matches arrays that contain all elements specified in the query. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/all/ + * @no-named-arguments * @param Type|array|bool|float|int|non-empty-string|null|stdClass ...$value */ public static function all(Type|stdClass|array|bool|float|int|null|string ...$value): AllOperator @@ -44,11 +46,12 @@ public static function all(Type|stdClass|array|bool|float|int|null|string ...$va * Joins query clauses with a logical AND returns all documents that match the conditions of both clauses. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/and/ - * @param Document|QueryInterface|Serializable|array|stdClass ...$expression + * @no-named-arguments + * @param QueryInterface|array ...$queries */ - public static function and(Document|Serializable|QueryInterface|stdClass|array ...$expression): AndOperator + public static function and(QueryInterface|array ...$queries): AndOperator { - return new AndOperator(...$expression); + return new AndOperator(...$queries); } /** @@ -143,9 +146,9 @@ public static function comment(string $comment): CommentOperator * The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/ - * @param Document|QueryInterface|Serializable|array|stdClass $query + * @param QueryInterface|array $query */ - public static function elemMatch(Document|Serializable|QueryInterface|stdClass|array $query): ElemMatchOperator + public static function elemMatch(QueryInterface|array $query): ElemMatchOperator { return new ElemMatchOperator($query); } @@ -398,20 +401,23 @@ public static function nin(PackedArray|BSONArray|array $value): NinOperator * Joins query clauses with a logical NOR returns all documents that fail to match both clauses. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/nor/ - * @param Document|QueryInterface|Serializable|array|stdClass ...$expression + * @no-named-arguments + * @param QueryInterface|array ...$queries */ - public static function nor(Document|Serializable|QueryInterface|stdClass|array ...$expression): NorOperator + public static function nor(QueryInterface|array ...$queries): NorOperator { - return new NorOperator(...$expression); + return new NorOperator(...$queries); } /** * Inverts the effect of a query expression and returns documents that do not match the query expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/not/ - * @param Document|QueryInterface|Regex|Serializable|array|stdClass $expression + * @param Decimal128|FieldQueryInterface|Int64|Regex|array|bool|float|int|non-empty-string|null|stdClass $expression */ - public static function not(Document|Regex|Serializable|QueryInterface|stdClass|array $expression): NotOperator + public static function not( + Decimal128|Int64|Regex|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression, + ): NotOperator { return new NotOperator($expression); } @@ -420,11 +426,12 @@ public static function not(Document|Regex|Serializable|QueryInterface|stdClass|a * Joins query clauses with a logical OR returns all documents that match the conditions of either clause. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/or/ - * @param Document|QueryInterface|Serializable|array|stdClass ...$expression + * @no-named-arguments + * @param QueryInterface|array ...$queries */ - public static function or(Document|Serializable|QueryInterface|stdClass|array ...$expression): OrOperator + public static function or(QueryInterface|array ...$queries): OrOperator { - return new OrOperator(...$expression); + return new OrOperator(...$queries); } /** diff --git a/src/Builder/Query/NorOperator.php b/src/Builder/Query/NorOperator.php index 0c56c6f87..415457229 100644 --- a/src/Builder/Query/NorOperator.php +++ b/src/Builder/Query/NorOperator.php @@ -8,13 +8,10 @@ namespace MongoDB\Builder\Query; -use MongoDB\BSON\Document; -use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Exception\InvalidArgumentException; -use stdClass; use function array_is_list; @@ -27,22 +24,24 @@ class NorOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ - public readonly array $expression; + /** @var list $queries */ + public readonly array $queries; /** - * @param Document|QueryInterface|Serializable|array|stdClass ...$expression + * @param QueryInterface|array ...$queries * @no-named-arguments */ - public function __construct(Document|Serializable|QueryInterface|stdClass|array ...$expression) + public function __construct(QueryInterface|array ...$queries) { - if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + if (\count($queries) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $queries, got %d.', 1, \count($queries))); } - if (! array_is_list($expression)) { - throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + + if (! array_is_list($queries)) { + throw new InvalidArgumentException('Expected $queries arguments to be a list (array), named arguments are not supported'); } - $this->expression = $expression; + + $this->queries = $queries; } public function getOperator(): string diff --git a/src/Builder/Query/NotOperator.php b/src/Builder/Query/NotOperator.php index 37dfc0b6e..5fa774043 100644 --- a/src/Builder/Query/NotOperator.php +++ b/src/Builder/Query/NotOperator.php @@ -8,19 +8,14 @@ namespace MongoDB\Builder\Query; -use MongoDB\BSON\Document; +use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; use MongoDB\BSON\Regex; -use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\OperatorInterface; -use MongoDB\Builder\Type\QueryInterface; -use MongoDB\Builder\Type\QueryObject; use stdClass; -use function is_array; -use function is_object; - /** * Inverts the effect of a query expression and returns documents that do not match the query expression. * @@ -30,18 +25,15 @@ class NotOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Document|QueryInterface|Regex|Serializable|array|stdClass $expression */ - public readonly Document|Regex|Serializable|QueryInterface|stdClass|array $expression; + /** @var Decimal128|FieldQueryInterface|Int64|Regex|array|bool|float|int|non-empty-string|null|stdClass $expression */ + public readonly Decimal128|Int64|Regex|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param Document|QueryInterface|Regex|Serializable|array|stdClass $expression + * @param Decimal128|FieldQueryInterface|Int64|Regex|array|bool|float|int|non-empty-string|null|stdClass $expression */ - public function __construct(Document|Regex|Serializable|QueryInterface|stdClass|array $expression) - { - if (is_array($expression) || is_object($expression)) { - $expression = QueryObject::create(...$expression); - } - + public function __construct( + Decimal128|Int64|Regex|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression, + ) { $this->expression = $expression; } diff --git a/src/Builder/Query/OrOperator.php b/src/Builder/Query/OrOperator.php index d517da7ff..1643b1bf1 100644 --- a/src/Builder/Query/OrOperator.php +++ b/src/Builder/Query/OrOperator.php @@ -8,13 +8,10 @@ namespace MongoDB\Builder\Query; -use MongoDB\BSON\Document; -use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Exception\InvalidArgumentException; -use stdClass; use function array_is_list; @@ -27,22 +24,24 @@ class OrOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ - public readonly array $expression; + /** @var list $queries */ + public readonly array $queries; /** - * @param Document|QueryInterface|Serializable|array|stdClass ...$expression + * @param QueryInterface|array ...$queries * @no-named-arguments */ - public function __construct(Document|Serializable|QueryInterface|stdClass|array ...$expression) + public function __construct(QueryInterface|array ...$queries) { - if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + if (\count($queries) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $queries, got %d.', 1, \count($queries))); } - if (! array_is_list($expression)) { - throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + + if (! array_is_list($queries)) { + throw new InvalidArgumentException('Expected $queries arguments to be a list (array), named arguments are not supported'); } - $this->expression = $expression; + + $this->queries = $queries; } public function getOperator(): string diff --git a/src/Builder/Stage.php b/src/Builder/Stage.php index c3146a2fb..63f091d01 100644 --- a/src/Builder/Stage.php +++ b/src/Builder/Stage.php @@ -4,7 +4,9 @@ namespace MongoDB\Builder; -use MongoDB\BSON\Serializable; +use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; +use MongoDB\BSON\Regex; use MongoDB\Builder\Stage\MatchStage; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\QueryInterface; @@ -21,9 +23,9 @@ final class Stage * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/ * - * @param FieldQueryInterface|QueryInterface|Serializable|array|bool|float|int|stdClass|string|null ...$queries The query predicates to match + * @param QueryInterface|FieldQueryInterface|Decimal128|Int64|Regex|stdClass|array|bool|float|int|string|null ...$queries The query predicates to match */ - public static function match(FieldQueryInterface|QueryInterface|Serializable|array|bool|float|int|stdClass|string|null ...$queries): MatchStage + public static function match(QueryInterface|FieldQueryInterface|Decimal128|Int64|Regex|stdClass|array|bool|float|int|string|null ...$queries): MatchStage { // Override the generated method to allow variadic arguments return self::generatedMatch($queries); diff --git a/src/Builder/Stage/AddFieldsStage.php b/src/Builder/Stage/AddFieldsStage.php index 8c9ed9741..02a8440d1 100644 --- a/src/Builder/Stage/AddFieldsStage.php +++ b/src/Builder/Stage/AddFieldsStage.php @@ -38,11 +38,13 @@ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|i if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } + foreach($expression as $key => $value) { if (! is_string($key)) { throw new InvalidArgumentException('Expected $expression arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); } } + $expression = (object) $expression; $this->expression = $expression; } diff --git a/src/Builder/Stage/FacetStage.php b/src/Builder/Stage/FacetStage.php index dcc5a6a26..cc000049f 100644 --- a/src/Builder/Stage/FacetStage.php +++ b/src/Builder/Stage/FacetStage.php @@ -39,11 +39,13 @@ public function __construct(PackedArray|Pipeline|BSONArray|array ...$facet) if (\count($facet) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $facet, got %d.', 1, \count($facet))); } + foreach($facet as $key => $value) { if (! is_string($key)) { throw new InvalidArgumentException('Expected $facet arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); } } + $facet = (object) $facet; $this->facet = $facet; } diff --git a/src/Builder/Stage/FactoryTrait.php b/src/Builder/Stage/FactoryTrait.php index 7b39024f4..e143bdeb1 100644 --- a/src/Builder/Stage/FactoryTrait.php +++ b/src/Builder/Stage/FactoryTrait.php @@ -247,7 +247,7 @@ public static function fill( * Specify the distance in meters if the specified point is GeoJSON and in radians if the specified point is legacy coordinate pairs. * @param Optional|Decimal128|Int64|float|int $minDistance The minimum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall outside the specified distance from the center point. * Specify the distance in meters for GeoJSON data and in radians for legacy coordinate pairs. - * @param Optional|Document|QueryInterface|Serializable|array|stdClass $query Limits the results to the documents that match the query. The query syntax is the usual MongoDB read operation query syntax. + * @param Optional|QueryInterface|array $query Limits the results to the documents that match the query. The query syntax is the usual MongoDB read operation query syntax. * You cannot specify a $near predicate in the query field of the $geoNear stage. * @param Optional|bool $spherical Determines how MongoDB calculates the distance between two points: * - When true, MongoDB uses $nearSphere semantics and calculates distances using spherical geometry. @@ -262,7 +262,7 @@ public static function geoNear( Optional|string $key = Optional::Undefined, Optional|Decimal128|Int64|float|int $maxDistance = Optional::Undefined, Optional|Decimal128|Int64|float|int $minDistance = Optional::Undefined, - Optional|Document|Serializable|QueryInterface|stdClass|array $query = Optional::Undefined, + Optional|QueryInterface|array $query = Optional::Undefined, Optional|bool $spherical = Optional::Undefined, ): GeoNearStage { @@ -281,7 +281,7 @@ public static function geoNear( * @param non-empty-string $as Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. * @param Optional|int $maxDepth Non-negative integral number specifying the maximum recursion depth. * @param Optional|non-empty-string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. - * @param Optional|Document|QueryInterface|Serializable|array|stdClass $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. + * @param Optional|QueryInterface|array $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. */ public static function graphLookup( string $from, @@ -291,7 +291,7 @@ public static function graphLookup( string $as, Optional|int $maxDepth = Optional::Undefined, Optional|string $depthField = Optional::Undefined, - Optional|Document|Serializable|QueryInterface|stdClass|array $restrictSearchWithMatch = Optional::Undefined, + Optional|QueryInterface|array $restrictSearchWithMatch = Optional::Undefined, ): GraphLookupStage { return new GraphLookupStage($from, $startWith, $connectFromField, $connectToField, $as, $maxDepth, $depthField, $restrictSearchWithMatch); @@ -422,9 +422,9 @@ public static function lookup( * Filters the document stream to allow only matching documents to pass unmodified into the next pipeline stage. $match uses standard MongoDB queries. For each input document, outputs either one document (a match) or zero documents (no match). * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/ - * @param Document|QueryInterface|Serializable|array|stdClass $query + * @param QueryInterface|array $query */ - public static function match(Document|Serializable|QueryInterface|stdClass|array $query): MatchStage + public static function match(QueryInterface|array $query): MatchStage { return new MatchStage($query); } @@ -667,6 +667,7 @@ public static function unionWith( * Alias for $project stage that removes or excludes fields. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/ + * @no-named-arguments * @param FieldPath|non-empty-string ...$field */ public static function unset(FieldPath|string ...$field): UnsetStage diff --git a/src/Builder/Stage/GeoNearStage.php b/src/Builder/Stage/GeoNearStage.php index fd31577f4..b7b8ec193 100644 --- a/src/Builder/Stage/GeoNearStage.php +++ b/src/Builder/Stage/GeoNearStage.php @@ -21,7 +21,6 @@ use stdClass; use function is_array; -use function is_object; /** * Returns an ordered stream of documents based on the proximity to a geospatial point. Incorporates the functionality of $match, $sort, and $limit for geospatial data. The output documents include an additional distance field and can include a location identifier field. @@ -60,10 +59,10 @@ class GeoNearStage implements StageInterface, OperatorInterface public readonly Optional|Decimal128|Int64|float|int $minDistance; /** - * @var Optional|Document|QueryInterface|Serializable|array|stdClass $query Limits the results to the documents that match the query. The query syntax is the usual MongoDB read operation query syntax. + * @var Optional|QueryInterface|array $query Limits the results to the documents that match the query. The query syntax is the usual MongoDB read operation query syntax. * You cannot specify a $near predicate in the query field of the $geoNear stage. */ - public readonly Optional|Document|Serializable|QueryInterface|stdClass|array $query; + public readonly Optional|QueryInterface|array $query; /** * @var Optional|bool $spherical Determines how MongoDB calculates the distance between two points: @@ -83,7 +82,7 @@ class GeoNearStage implements StageInterface, OperatorInterface * Specify the distance in meters if the specified point is GeoJSON and in radians if the specified point is legacy coordinate pairs. * @param Optional|Decimal128|Int64|float|int $minDistance The minimum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall outside the specified distance from the center point. * Specify the distance in meters for GeoJSON data and in radians for legacy coordinate pairs. - * @param Optional|Document|QueryInterface|Serializable|array|stdClass $query Limits the results to the documents that match the query. The query syntax is the usual MongoDB read operation query syntax. + * @param Optional|QueryInterface|array $query Limits the results to the documents that match the query. The query syntax is the usual MongoDB read operation query syntax. * You cannot specify a $near predicate in the query field of the $geoNear stage. * @param Optional|bool $spherical Determines how MongoDB calculates the distance between two points: * - When true, MongoDB uses $nearSphere semantics and calculates distances using spherical geometry. @@ -98,7 +97,7 @@ public function __construct( Optional|string $key = Optional::Undefined, Optional|Decimal128|Int64|float|int $maxDistance = Optional::Undefined, Optional|Decimal128|Int64|float|int $minDistance = Optional::Undefined, - Optional|Document|Serializable|QueryInterface|stdClass|array $query = Optional::Undefined, + Optional|QueryInterface|array $query = Optional::Undefined, Optional|bool $spherical = Optional::Undefined, ) { $this->distanceField = $distanceField; @@ -108,8 +107,8 @@ public function __construct( $this->key = $key; $this->maxDistance = $maxDistance; $this->minDistance = $minDistance; - if (is_array($query) || is_object($query)) { - $query = QueryObject::create(...$query); + if (is_array($query)) { + $query = QueryObject::create($query); } $this->query = $query; diff --git a/src/Builder/Stage/GraphLookupStage.php b/src/Builder/Stage/GraphLookupStage.php index 9ebb01d1f..93edd68a3 100644 --- a/src/Builder/Stage/GraphLookupStage.php +++ b/src/Builder/Stage/GraphLookupStage.php @@ -8,9 +8,7 @@ namespace MongoDB\Builder\Stage; -use MongoDB\BSON\Document; use MongoDB\BSON\PackedArray; -use MongoDB\BSON\Serializable; use MongoDB\BSON\Type; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\ExpressionInterface; @@ -25,7 +23,6 @@ use function array_is_list; use function is_array; -use function is_object; /** * Performs a recursive search on a collection. To each output document, adds a new array field that contains the traversal results of the recursive search for that document. @@ -60,8 +57,8 @@ class GraphLookupStage implements StageInterface, OperatorInterface /** @var Optional|non-empty-string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. */ public readonly Optional|string $depthField; - /** @var Optional|Document|QueryInterface|Serializable|array|stdClass $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. */ - public readonly Optional|Document|Serializable|QueryInterface|stdClass|array $restrictSearchWithMatch; + /** @var Optional|QueryInterface|array $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. */ + public readonly Optional|QueryInterface|array $restrictSearchWithMatch; /** * @param non-empty-string $from Target collection for the $graphLookup operation to search, recursively matching the connectFromField to the connectToField. The from collection must be in the same database as any other collections used in the operation. @@ -72,7 +69,7 @@ class GraphLookupStage implements StageInterface, OperatorInterface * @param non-empty-string $as Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. * @param Optional|int $maxDepth Non-negative integral number specifying the maximum recursion depth. * @param Optional|non-empty-string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. - * @param Optional|Document|QueryInterface|Serializable|array|stdClass $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. + * @param Optional|QueryInterface|array $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. */ public function __construct( string $from, @@ -82,7 +79,7 @@ public function __construct( string $as, Optional|int $maxDepth = Optional::Undefined, Optional|string $depthField = Optional::Undefined, - Optional|Document|Serializable|QueryInterface|stdClass|array $restrictSearchWithMatch = Optional::Undefined, + Optional|QueryInterface|array $restrictSearchWithMatch = Optional::Undefined, ) { $this->from = $from; if (is_array($startWith) && ! array_is_list($startWith)) { @@ -95,8 +92,8 @@ public function __construct( $this->as = $as; $this->maxDepth = $maxDepth; $this->depthField = $depthField; - if (is_array($restrictSearchWithMatch) || is_object($restrictSearchWithMatch)) { - $restrictSearchWithMatch = QueryObject::create(...$restrictSearchWithMatch); + if (is_array($restrictSearchWithMatch)) { + $restrictSearchWithMatch = QueryObject::create($restrictSearchWithMatch); } $this->restrictSearchWithMatch = $restrictSearchWithMatch; diff --git a/src/Builder/Stage/GroupStage.php b/src/Builder/Stage/GroupStage.php index 2fc613fe8..fc784487b 100644 --- a/src/Builder/Stage/GroupStage.php +++ b/src/Builder/Stage/GroupStage.php @@ -48,11 +48,13 @@ public function __construct( if (\count($field) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); } + foreach($field as $key => $value) { if (! is_string($key)) { throw new InvalidArgumentException('Expected $field arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); } } + $field = (object) $field; $this->field = $field; } diff --git a/src/Builder/Stage/MatchStage.php b/src/Builder/Stage/MatchStage.php index 189451d94..9590763ae 100644 --- a/src/Builder/Stage/MatchStage.php +++ b/src/Builder/Stage/MatchStage.php @@ -8,17 +8,13 @@ namespace MongoDB\Builder\Stage; -use MongoDB\BSON\Document; -use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Builder\Type\QueryObject; use MongoDB\Builder\Type\StageInterface; -use stdClass; use function is_array; -use function is_object; /** * Filters the document stream to allow only matching documents to pass unmodified into the next pipeline stage. $match uses standard MongoDB queries. For each input document, outputs either one document (a match) or zero documents (no match). @@ -29,16 +25,16 @@ class MatchStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Document|QueryInterface|Serializable|array|stdClass $query */ - public readonly Document|Serializable|QueryInterface|stdClass|array $query; + /** @var QueryInterface|array $query */ + public readonly QueryInterface|array $query; /** - * @param Document|QueryInterface|Serializable|array|stdClass $query + * @param QueryInterface|array $query */ - public function __construct(Document|Serializable|QueryInterface|stdClass|array $query) + public function __construct(QueryInterface|array $query) { - if (is_array($query) || is_object($query)) { - $query = QueryObject::create(...$query); + if (is_array($query)) { + $query = QueryObject::create($query); } $this->query = $query; diff --git a/src/Builder/Stage/ProjectStage.php b/src/Builder/Stage/ProjectStage.php index ba2bd2725..daf35d705 100644 --- a/src/Builder/Stage/ProjectStage.php +++ b/src/Builder/Stage/ProjectStage.php @@ -41,11 +41,13 @@ public function __construct( if (\count($specification) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $specification, got %d.', 1, \count($specification))); } + foreach($specification as $key => $value) { if (! is_string($key)) { throw new InvalidArgumentException('Expected $specification arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); } } + $specification = (object) $specification; $this->specification = $specification; } diff --git a/src/Builder/Stage/SetStage.php b/src/Builder/Stage/SetStage.php index 904ec9f14..2ec1dd16e 100644 --- a/src/Builder/Stage/SetStage.php +++ b/src/Builder/Stage/SetStage.php @@ -39,11 +39,13 @@ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|i if (\count($field) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); } + foreach($field as $key => $value) { if (! is_string($key)) { throw new InvalidArgumentException('Expected $field arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); } } + $field = (object) $field; $this->field = $field; } diff --git a/src/Builder/Stage/UnsetStage.php b/src/Builder/Stage/UnsetStage.php index 1abcb9bf2..83f33e340 100644 --- a/src/Builder/Stage/UnsetStage.php +++ b/src/Builder/Stage/UnsetStage.php @@ -38,9 +38,11 @@ public function __construct(FieldPath|string ...$field) if (\count($field) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); } + if (! array_is_list($field)) { throw new InvalidArgumentException('Expected $field arguments to be a list (array), named arguments are not supported'); } + $this->field = $field; } diff --git a/src/Builder/Type/OutputWindow.php b/src/Builder/Type/OutputWindow.php index 2f43f25e1..aa30f9487 100644 --- a/src/Builder/Type/OutputWindow.php +++ b/src/Builder/Type/OutputWindow.php @@ -62,7 +62,7 @@ public function __construct( throw new InvalidArgumentException(sprintf('Expected $documents argument to be a list of 2 string or int. Got [%s, %s]', get_debug_type($documents[0]), get_debug_type($documents[1]))); } - $window ??= new stdClass(); + $window = new stdClass(); $window->documents = $documents; } diff --git a/src/Builder/Type/QueryObject.php b/src/Builder/Type/QueryObject.php index f50c074e7..2fbf34c0c 100644 --- a/src/Builder/Type/QueryObject.php +++ b/src/Builder/Type/QueryObject.php @@ -4,15 +4,18 @@ namespace MongoDB\Builder\Type; -use MongoDB\BSON\Document; +use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; use MongoDB\BSON\Regex; use MongoDB\Exception\InvalidArgumentException; use stdClass; use function array_is_list; +use function array_key_first; use function count; use function is_array; use function sprintf; +use function str_starts_with; /** * Helper class to validate query objects. @@ -24,7 +27,8 @@ final class QueryObject implements QueryInterface { public readonly array $queries; - public static function create(QueryInterface|FieldQueryInterface|array|stdClass|string|int|float|bool|Regex|Document|null ...$queries): QueryInterface + /** @param array $queries */ + public static function create(array $queries): QueryInterface { // We don't wrap a single query in a QueryObject if (count($queries) === 1 && isset($queries[0]) && $queries[0] instanceof QueryInterface) { @@ -34,9 +38,20 @@ public static function create(QueryInterface|FieldQueryInterface|array|stdClass| return new self($queries); } - /** @param array $queriesOrArrayOfQueries */ + /** @param array $queriesOrArrayOfQueries */ private function __construct(array $queriesOrArrayOfQueries) { + // If the first element is an array and not an operator, we assume variadic arguments were not used + if ( + count($queriesOrArrayOfQueries) === 1 && + isset($queriesOrArrayOfQueries[0]) && + is_array($queriesOrArrayOfQueries[0]) && + count($queriesOrArrayOfQueries[0]) > 0 && + ! str_starts_with((string) array_key_first($queriesOrArrayOfQueries[0]), '$') + ) { + $queriesOrArrayOfQueries = $queriesOrArrayOfQueries[0]; + } + $seenQueryOperators = []; $queries = []; diff --git a/tests/Builder/BuilderEncoderTest.php b/tests/Builder/BuilderEncoderTest.php index e9471c3bb..e51287aac 100644 --- a/tests/Builder/BuilderEncoderTest.php +++ b/tests/Builder/BuilderEncoderTest.php @@ -40,6 +40,63 @@ public function testPipeline(): void $this->assertSamePipeline($expected, $pipeline); } + public function testMatchNumericFieldName(): void + { + $pipeline = new Pipeline( + Stage::match(['1' => Query::eq('dave')]), + Stage::match(['1' => Query::not(Query::eq('dave'))]), + Stage::match( + Query::and( + Query::query(['2' => Query::gt(3)]), + Query::query(['2' => Query::lt(4)]), + ), + ), + Stage::match( + Query::or( + Query::query(['2' => Query::gt(3)]), + Query::query(['2' => Query::lt(4)]), + ), + ), + Stage::match( + Query::nor( + Query::query(['2' => Query::gt(3)]), + Query::query(['2' => Query::lt(4)]), + ), + ), + ); + + $expected = [ + ['$match' => ['1' => ['$eq' => 'dave']]], + ['$match' => ['1' => ['$not' => ['$eq' => 'dave']]]], + [ + '$match' => [ + '$and' => [ + ['2' => ['$gt' => 3]], + ['2' => ['$lt' => 4]], + ], + ], + ], + [ + '$match' => [ + '$or' => [ + ['2' => ['$gt' => 3]], + ['2' => ['$lt' => 4]], + ], + ], + ], + [ + '$match' => [ + '$nor' => [ + ['2' => ['$gt' => 3]], + ['2' => ['$lt' => 4]], + ], + ], + ], + ]; + + $this->assertSamePipeline($expected, $pipeline); + } + /** @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/#ascending-descending-sort */ public function testSort(): void { diff --git a/tests/Builder/Type/QueryObjectTest.php b/tests/Builder/Type/QueryObjectTest.php index fce20cf61..ccebab89f 100644 --- a/tests/Builder/Type/QueryObjectTest.php +++ b/tests/Builder/Type/QueryObjectTest.php @@ -5,6 +5,8 @@ namespace MongoDB\Tests\Builder\Type; use Generator; +use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; use MongoDB\BSON\Regex; use MongoDB\Builder\Query\CommentOperator; use MongoDB\Builder\Query\EqOperator; @@ -19,7 +21,7 @@ class QueryObjectTest extends TestCase { public function testEmptyQueryObject(): void { - $queryObject = QueryObject::create(); + $queryObject = QueryObject::create([]); $this->assertSame([], $queryObject->queries); } @@ -27,7 +29,7 @@ public function testEmptyQueryObject(): void public function testShortCutQueryObject(): void { $query = $this->createMock(QueryInterface::class); - $queryObject = QueryObject::create($query); + $queryObject = QueryObject::create([$query]); $this->assertSame($query, $queryObject); } @@ -39,7 +41,20 @@ public function testShortCutQueryObject(): void */ public function testCreateQueryObject(array $value, int $expectedCount = 1): void { - $queryObject = QueryObject::create(...$value); + $queryObject = QueryObject::create($value); + + $this->assertCount($expectedCount, $queryObject->queries); + } + + /** + * @param array $value + * + * @dataProvider provideQueryObjectValue + */ + public function testCreateQueryObjectFromArray(array $value, int $expectedCount = 1): void + { + // $value is wrapped in an array as if the user used an array instead of variadic arguments + $queryObject = QueryObject::create([$value]); $this->assertCount($expectedCount, $queryObject->queries); } @@ -51,6 +66,8 @@ public function provideQueryObjectValue(): Generator yield 'string' => [['foo' => 'bar']]; yield 'bool' => [['foo' => true]]; yield 'null' => [['foo' => null]]; + yield 'decimal128' => [['foo' => new Decimal128('1.1')]]; + yield 'int64' => [[1 => new Int64(1)]]; yield 'regex' => [['foo' => new Regex('foo')]]; yield 'object' => [['foo' => (object) ['bar' => 'baz']]]; yield 'list' => [['foo' => ['bar', 'baz']]]; @@ -58,12 +75,14 @@ public function provideQueryObjectValue(): Generator yield 'operator as object' => [['foo' => (object) ['$eq' => 1]]]; yield 'field query operator' => [['foo' => new EqOperator(1)]]; yield 'query operator' => [[new CommentOperator('foo'), 'foo' => 1], 2]; + yield 'numeric field with array' => [[1 => [2, 3]]]; + yield 'numeric field with operator' => [[1 => ['$eq' => 2]]]; } public function testFieldQueryList(): void { $queryObject = QueryObject::create( - foo: [new GtOperator(1), new LtOperator(5)], + ['foo' => [new GtOperator(1), new LtOperator(5)]], ); $this->assertArrayHasKey('foo', $queryObject->queries); From 49e266d59356f6392de79dbdec298d5a184a6e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 31 Oct 2023 18:33:09 +0100 Subject: [PATCH 11/95] PHPLIB-1271 Add tests from the documentation (#6) --- .gitattributes | 1 + generator/composer.json | 3 +- generator/config/accumulator/accumulator.yaml | 54 ++++++- generator/config/accumulator/addToSet.yaml | 32 ++++ generator/config/definitions.php | 6 + generator/config/query/regex.yaml | 24 +++ generator/config/schema.json | 31 +++- generator/config/stage/addFields.yaml | 25 +++ generator/src/AbstractGenerator.php | 20 ++- .../src/Definition/OperatorDefinition.php | 12 +- generator/src/Definition/TestDefinition.php | 21 +++ generator/src/OperatorGenerator.php | 2 +- generator/src/OperatorTestGenerator.php | 114 ++++++++++++++ .../Accumulator/AccumulatorAccumulator.php | 33 ++-- src/Builder/Accumulator/FactoryTrait.php | 17 +- src/Builder/Query.php | 5 +- src/Builder/Type/QueryObject.php | 4 +- .../AccumulatorAccumulatorTest.php | 62 ++++++++ .../Accumulator/AddToSetAccumulatorTest.php | 57 +++++++ tests/Builder/Accumulator/Pipelines.php | 146 ++++++++++++++++++ tests/Builder/Expression/Pipelines.php | 13 ++ tests/Builder/PipelineTestCase.php | 32 ++++ tests/Builder/Projection/Pipelines.php | 13 ++ tests/Builder/Query/Pipelines.php | 52 +++++++ tests/Builder/Query/RegexOperatorTest.php | 40 +++++ tests/Builder/Stage/AddFieldsStageTest.php | 48 ++++++ tests/Builder/Stage/Pipelines.php | 58 +++++++ 27 files changed, 884 insertions(+), 41 deletions(-) create mode 100644 generator/src/Definition/TestDefinition.php create mode 100644 generator/src/OperatorTestGenerator.php create mode 100644 tests/Builder/Accumulator/AccumulatorAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/AddToSetAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/Pipelines.php create mode 100644 tests/Builder/Expression/Pipelines.php create mode 100644 tests/Builder/PipelineTestCase.php create mode 100644 tests/Builder/Projection/Pipelines.php create mode 100644 tests/Builder/Query/Pipelines.php create mode 100644 tests/Builder/Query/RegexOperatorTest.php create mode 100644 tests/Builder/Stage/AddFieldsStageTest.php create mode 100644 tests/Builder/Stage/Pipelines.php diff --git a/.gitattributes b/.gitattributes index 9071b99c6..0f861c4ce 100644 --- a/.gitattributes +++ b/.gitattributes @@ -14,3 +14,4 @@ psalm-baseline.xml export-ignore /src/Builder/Query/*.php linguist-generated=true /src/Builder/Projection/*.php linguist-generated=true /src/Builder/Stage/*.php linguist-generated=true +/tests/Builder/*/Pipelines.php linguist-generated=true diff --git a/generator/composer.json b/generator/composer.json index 929523725..d03eb2eac 100644 --- a/generator/composer.json +++ b/generator/composer.json @@ -15,9 +15,10 @@ "require": { "php": ">=8.1", "ext-mongodb": "^1.16.0", - "mongodb/mongodb": "^1.17.0@dev", "mongodb/builder": "@dev", + "mongodb/mongodb": "^1.17.0@dev", "nette/php-generator": "^4", + "nikic/php-parser": "^4.17", "symfony/console": "^6.3|^7.0", "symfony/finder": "^6.3|^7.0", "symfony/yaml": "^6.3|^7.0" diff --git a/generator/config/accumulator/accumulator.yaml b/generator/config/accumulator/accumulator.yaml index ed201787b..696a32173 100644 --- a/generator/config/accumulator/accumulator.yaml +++ b/generator/config/accumulator/accumulator.yaml @@ -11,7 +11,7 @@ arguments: - name: init type: - - string + - javascript description: | Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. - @@ -24,7 +24,7 @@ arguments: - name: accumulate type: - - string + - javascript description: | Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. - @@ -36,13 +36,13 @@ arguments: - name: merge type: - - string + - javascript description: | Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. - name: finalize type: - - string + - javascript optional: true description: | Function used to update the result of the accumulation. @@ -52,3 +52,49 @@ arguments: - string description: | The language used in the $accumulator code. + +tests: + - + name: 'Use $accumulator to Implement the $avg Operator' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/accumulator/#use--accumulator-to-implement-the--avg-operator' + pipeline: + - + $group: + _id: '$author' + avgCopies: + $accumulator: + init: + $code: 'function () { return { count: 0, sum: 0 } }' + accumulate: + $code: 'function (state, numCopies) { return { count: state.count + 1, sum: state.sum + numCopies } }' + accumulateArgs: [ "$copies" ], + merge: + $code: 'function (state1, state2) { return { count: state1.count + state2.count, sum: state1.sum + state2.sum } }' + finalize: + $code: 'function (state) { return (state.sum / state.count) }' + lang: 'js' + + - + name: 'Use initArgs to Vary the Initial State by Group' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/accumulator/#use-initargs-to-vary-the-initial-state-by-group' + pipeline: + - + $group: + _id: + city: '$city' + restaurants: + $accumulator: + init: + $code: 'function (city, userProfileCity) { return { max: city === userProfileCity ? 3 : 1, restaurants: [] } }' + initArgs: + - '$city' + - 'Bettles' + accumulate: + $code: 'function (state, restaurantName) { if (state.restaurants.length < state.max) { state.restaurants.push(restaurantName); } return state; }' + accumulateArgs: + - '$name' + merge: + $code: 'function (state1, state2) { return { max: state1.max, restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) } }' + finalize: + $code: 'function (state) { return state.restaurants }' + lang: 'js' diff --git a/generator/config/accumulator/addToSet.yaml b/generator/config/accumulator/addToSet.yaml index 4ea79e704..9566899eb 100644 --- a/generator/config/accumulator/addToSet.yaml +++ b/generator/config/accumulator/addToSet.yaml @@ -13,3 +13,35 @@ arguments: name: expression type: - expression + +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/addToSet/#use-in--group-stage' + pipeline: + - $group: + _id: + day: + $dayOfYear: + date: '$date' + year: + $year: + date: '$date' + itemsSold: + $addToSet: '$item' + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/addToSet/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + cakeTypesForState: + $addToSet: '$type' + window: + documents: + - 'unbounded' + - 'current' diff --git a/generator/config/definitions.php b/generator/config/definitions.php index 3581acb50..baf101e62 100644 --- a/generator/config/definitions.php +++ b/generator/config/definitions.php @@ -6,6 +6,7 @@ use MongoDB\CodeGenerator\OperatorClassGenerator; use MongoDB\CodeGenerator\OperatorFactoryGenerator; +use MongoDB\CodeGenerator\OperatorTestGenerator; return [ // Aggregation Pipeline Stages @@ -16,6 +17,7 @@ 'generators' => [ OperatorClassGenerator::class, OperatorFactoryGenerator::class, + OperatorTestGenerator::class, ], ], @@ -27,6 +29,7 @@ 'generators' => [ OperatorClassGenerator::class, OperatorFactoryGenerator::class, + OperatorTestGenerator::class, ], ], @@ -38,6 +41,7 @@ 'generators' => [ OperatorClassGenerator::class, OperatorFactoryGenerator::class, + OperatorTestGenerator::class, ], ], @@ -49,6 +53,7 @@ 'generators' => [ OperatorClassGenerator::class, OperatorFactoryGenerator::class, + OperatorTestGenerator::class, ], ], @@ -60,6 +65,7 @@ 'generators' => [ OperatorClassGenerator::class, OperatorFactoryGenerator::class, + OperatorTestGenerator::class, ], ], ]; diff --git a/generator/config/query/regex.yaml b/generator/config/query/regex.yaml index 0b0b8f1af..7aee0c8ec 100644 --- a/generator/config/query/regex.yaml +++ b/generator/config/query/regex.yaml @@ -11,3 +11,27 @@ arguments: name: regex type: - regex + +tests: + - + name: 'Perform a LIKE Match' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/regex/#perform-a-like-match' + pipeline: + - + $match: + sku: + # Should be nested in $regex + $regularExpression: + pattern: '789$' + options: '' + - + name: 'Perform Case-Insensitive Regular Expression Match' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/regex/#perform-case-insensitive-regular-expression-match' + pipeline: + - + $match: + sku: + # Should be nested in $regex + $regularExpression: + pattern: '^ABC' + options: 'i' diff --git a/generator/config/schema.json b/generator/config/schema.json index e5ea1638b..02091080c 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -15,9 +15,7 @@ "$comment": "The link to the operator's documentation on MongoDB's website.", "type": "string", "format": "uri", - "qt-uri-protocols": [ - "https" - ] + "pattern": "^https://" }, "type": { "type": "array", @@ -76,6 +74,13 @@ "items": { "$ref": "#/definitions/Argument" } + }, + "tests": { + "$comment": "An optional list of examples for the operator.", + "type": "array", + "items": { + "$ref": "#/definitions/Test" + } } }, "required": [ @@ -168,6 +173,26 @@ "type" ], "title": "Argument" + }, + "Test": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "link": { + "type": "string", + "format": "uri", + "pattern": "^https://" + }, + "pipeline": { + "type": "array", + "items": { + "type": "object" + } + } + } } } } diff --git a/generator/config/stage/addFields.yaml b/generator/config/stage/addFields.yaml index f4084f054..343362a74 100644 --- a/generator/config/stage/addFields.yaml +++ b/generator/config/stage/addFields.yaml @@ -14,3 +14,28 @@ arguments: variadic: object description: | Specify the name of each field to add and set its value to an aggregation expression or an empty object. + +tests: + - + name: 'Using Two $addFields Stages' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#using-two--addfields-stages' + pipeline: + - + $addFields: + totalHomework: + $sum: '$homework' + totalQuiz: + $sum: '$quiz' + - + $addFields: + totalScore: + $add: + - '$totalHomework' + - '$totalQuiz' + - '$extraCredit' + - + name: 'Adding Fields to an Embedded Document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#adding-fields-to-an-embedded-document' + pipeline: + - $addFields: + specs.fuel_type: 'unleaded' diff --git a/generator/src/AbstractGenerator.php b/generator/src/AbstractGenerator.php index 17fb61eb3..b0fef69aa 100644 --- a/generator/src/AbstractGenerator.php +++ b/generator/src/AbstractGenerator.php @@ -16,9 +16,11 @@ use function current; use function dirname; use function explode; +use function file_get_contents; use function file_put_contents; use function implode; use function is_dir; +use function is_file; use function ltrim; use function mkdir; use function sprintf; @@ -48,7 +50,7 @@ final protected function splitNamespaceAndClassName(string $fqcn): array return [implode('\\', $parts), $className]; } - final protected function writeFile(PhpNamespace $namespace): void + final protected function writeFile(PhpNamespace $namespace, bool $autoGeneratedWarning = true): void { $classes = $namespace->getClasses(); assert(count($classes) === 1, sprintf('Expected exactly one class in namespace "%s", got %d.', $namespace->getName(), count($classes))); @@ -62,12 +64,26 @@ final protected function writeFile(PhpNamespace $namespace): void $file = new PhpFile(); $file->setStrictTypes(); - $file->setComment('THIS FILE IS AUTO-GENERATED. ANY CHANGES WILL BE LOST!'); + if ($autoGeneratedWarning) { + $file->setComment('THIS FILE IS AUTO-GENERATED. ANY CHANGES WILL BE LOST!'); + } + $file->addNamespace($namespace); file_put_contents($filename, $this->printer->printFile($file)); } + final protected function readFile(string ...$fqcn): PhpFile|null + { + $filename = $this->rootDir . $this->getFileName(...$fqcn); + + if (! is_file($filename)) { + return null; + } + + return PhpFile::fromCode(file_get_contents($filename)); + } + /** * Thanks to PSR-4, the file name can be determined from the fully qualified class name. * diff --git a/generator/src/Definition/OperatorDefinition.php b/generator/src/Definition/OperatorDefinition.php index acbcc97e4..f97c52212 100644 --- a/generator/src/Definition/OperatorDefinition.php +++ b/generator/src/Definition/OperatorDefinition.php @@ -7,17 +7,22 @@ use MongoDB\Builder\Type\Encode; use UnexpectedValueException; +use function array_map; use function array_merge; +use function array_values; use function assert; use function count; use function sprintf; final class OperatorDefinition { - public Encode $encode; + public readonly Encode $encode; /** @var list */ - public array $arguments; + public readonly array $arguments; + + /** @var list */ + public readonly array $tests; public function __construct( public string $name, @@ -27,6 +32,7 @@ public function __construct( public array $type, public string|null $description = null, array $arguments = [], + array $tests = [], ) { $this->encode = match ($encode) { 'single' => Encode::Single, @@ -55,5 +61,7 @@ public function __construct( } $this->arguments = array_merge($requiredArgs, $optionalArgs); + + $this->tests = array_map(static fn (array $test): TestDefinition => new TestDefinition(...$test), array_values($tests)); } } diff --git a/generator/src/Definition/TestDefinition.php b/generator/src/Definition/TestDefinition.php new file mode 100644 index 000000000..11a14f408 --- /dev/null +++ b/generator/src/Definition/TestDefinition.php @@ -0,0 +1,21 @@ + */ + public array $pipeline, + public string|null $link = null, + ) { + assert(array_is_list($pipeline), sprintf('Argument "%s" pipeline must be a list', $name)); + } +} diff --git a/generator/src/OperatorGenerator.php b/generator/src/OperatorGenerator.php index fbab6023f..b194c9792 100644 --- a/generator/src/OperatorGenerator.php +++ b/generator/src/OperatorGenerator.php @@ -45,7 +45,7 @@ final public function __construct( abstract public function generate(GeneratorDefinition $definition): void; - /** @return list> */ + /** @return list */ final protected function getOperators(GeneratorDefinition $definition): array { // Remove unsupported operators diff --git a/generator/src/OperatorTestGenerator.php b/generator/src/OperatorTestGenerator.php new file mode 100644 index 000000000..18273bf99 --- /dev/null +++ b/generator/src/OperatorTestGenerator.php @@ -0,0 +1,114 @@ +createExpectedClass($definition); + + foreach ($this->getOperators($definition) as $operator) { + // Skip operators without tests + if (! $operator->tests) { + continue; + } + + try { + $this->writeFile($this->createClass($definition, $operator, $dataNamespace->getClasses()[self::DATA_ENUM]), false); + } catch (Throwable $e) { + throw new RuntimeException(sprintf('Failed to generate class for operator "%s"', $operator->name), 0, $e); + } + } + + $this->writeFile($dataNamespace); + } + + public function createExpectedClass(GeneratorDefinition $definition): PhpNamespace + { + $dataNamespace = str_replace('MongoDB', 'MongoDB\\Tests', $definition->namespace); + + $namespace = new PhpNamespace($dataNamespace); + $enum = $namespace->addEnum(self::DATA_ENUM); + $enum->setType('string'); + + return $namespace; + } + + public function createClass(GeneratorDefinition $definition, OperatorDefinition $operator, EnumType $dataEnum): PhpNamespace + { + $testNamespace = str_replace('MongoDB', 'MongoDB\\Tests', $definition->namespace); + $testClass = $this->getOperatorClassName($definition, $operator) . 'Test'; + + $namespace = $this->readFile($testNamespace, $testClass)?->getNamespaces()[$testNamespace] ?? null; + $namespace ??= new PhpNamespace($testNamespace); + + $class = $namespace->getClasses()[$testClass] ?? null; + $class ??= $namespace->addClass($testClass); + $namespace->addUse(PipelineTestCase::class); + $class->setExtends(PipelineTestCase::class); + $namespace->addUse(Pipeline::class); + $class->setComment('Test ' . $operator->name . ' ' . basename($definition->configFiles)); + + foreach ($operator->tests as $test) { + $testName = 'test' . str_replace([' ', '-'], '', ucwords(str_replace('$', '', $test->name))); + $caseName = str_replace([' ', '-'], '', ucwords(str_replace('$', '', $operator->name . ' ' . $test->name))); + + $case = $dataEnum->addCase($caseName, new Literal('<<<\'JSON\'' . "\n" . json_encode($test->pipeline, JSON_PRETTY_PRINT) . "\n" . 'JSON')); + $case->setComment($test->name); + if ($test->link) { + $case->addComment(''); + $case->addComment('@see ' . $test->link); + } + + $caseName = self::DATA_ENUM . '::' . $caseName; + + if ($class->hasMethod($testName)) { + $testMethod = $class->getMethod($testName); + } else { + $testMethod = $class->addMethod($testName); + $testMethod->setBody(<<assertSamePipeline({$caseName}, \$pipeline); + PHP); + } + + $testMethod->setPublic(); + $testMethod->setReturnType(Type::Void); + } + + $methods = $class->getMethods(); + ksort($methods); + $class->setMethods($methods); + + return $namespace; + } +} diff --git a/src/Builder/Accumulator/AccumulatorAccumulator.php b/src/Builder/Accumulator/AccumulatorAccumulator.php index 49cae3e0c..0f2088683 100644 --- a/src/Builder/Accumulator/AccumulatorAccumulator.php +++ b/src/Builder/Accumulator/AccumulatorAccumulator.php @@ -8,6 +8,7 @@ namespace MongoDB\Builder\Accumulator; +use MongoDB\BSON\Javascript; use MongoDB\BSON\PackedArray; use MongoDB\Builder\Expression\ResolvesToArray; use MongoDB\Builder\Type\AccumulatorInterface; @@ -30,17 +31,17 @@ class AccumulatorAccumulator implements AccumulatorInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var non-empty-string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. */ - public readonly string $init; + /** @var Javascript|non-empty-string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. */ + public readonly Javascript|string $init; - /** @var non-empty-string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. */ - public readonly string $accumulate; + /** @var Javascript|non-empty-string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. */ + public readonly Javascript|string $accumulate; /** @var BSONArray|PackedArray|ResolvesToArray|array $accumulateArgs Arguments passed to the accumulate function. You can use accumulateArgs to specify what field value(s) to pass to the accumulate function. */ public readonly PackedArray|ResolvesToArray|BSONArray|array $accumulateArgs; - /** @var non-empty-string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. */ - public readonly string $merge; + /** @var Javascript|non-empty-string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. */ + public readonly Javascript|string $merge; /** @var non-empty-string $lang The language used in the $accumulator code. */ public readonly string $lang; @@ -48,26 +49,26 @@ class AccumulatorAccumulator implements AccumulatorInterface, OperatorInterface /** @var Optional|BSONArray|PackedArray|ResolvesToArray|array $initArgs Arguments passed to the init function. */ public readonly Optional|PackedArray|ResolvesToArray|BSONArray|array $initArgs; - /** @var Optional|non-empty-string $finalize Function used to update the result of the accumulation. */ - public readonly Optional|string $finalize; + /** @var Optional|Javascript|non-empty-string $finalize Function used to update the result of the accumulation. */ + public readonly Optional|Javascript|string $finalize; /** - * @param non-empty-string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. - * @param non-empty-string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. + * @param Javascript|non-empty-string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. + * @param Javascript|non-empty-string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. * @param BSONArray|PackedArray|ResolvesToArray|array $accumulateArgs Arguments passed to the accumulate function. You can use accumulateArgs to specify what field value(s) to pass to the accumulate function. - * @param non-empty-string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. + * @param Javascript|non-empty-string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. * @param non-empty-string $lang The language used in the $accumulator code. * @param Optional|BSONArray|PackedArray|ResolvesToArray|array $initArgs Arguments passed to the init function. - * @param Optional|non-empty-string $finalize Function used to update the result of the accumulation. + * @param Optional|Javascript|non-empty-string $finalize Function used to update the result of the accumulation. */ public function __construct( - string $init, - string $accumulate, + Javascript|string $init, + Javascript|string $accumulate, PackedArray|ResolvesToArray|BSONArray|array $accumulateArgs, - string $merge, + Javascript|string $merge, string $lang, Optional|PackedArray|ResolvesToArray|BSONArray|array $initArgs = Optional::Undefined, - Optional|string $finalize = Optional::Undefined, + Optional|Javascript|string $finalize = Optional::Undefined, ) { $this->init = $init; $this->accumulate = $accumulate; diff --git a/src/Builder/Accumulator/FactoryTrait.php b/src/Builder/Accumulator/FactoryTrait.php index 09f70c918..c53f323a3 100644 --- a/src/Builder/Accumulator/FactoryTrait.php +++ b/src/Builder/Accumulator/FactoryTrait.php @@ -11,6 +11,7 @@ use MongoDB\BSON\Decimal128; use MongoDB\BSON\Document; use MongoDB\BSON\Int64; +use MongoDB\BSON\Javascript; use MongoDB\BSON\PackedArray; use MongoDB\BSON\Serializable; use MongoDB\BSON\Type; @@ -35,22 +36,22 @@ trait FactoryTrait * New in MongoDB 4.4. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/accumulator/ - * @param non-empty-string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. - * @param non-empty-string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. + * @param Javascript|non-empty-string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. + * @param Javascript|non-empty-string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. * @param BSONArray|PackedArray|ResolvesToArray|array $accumulateArgs Arguments passed to the accumulate function. You can use accumulateArgs to specify what field value(s) to pass to the accumulate function. - * @param non-empty-string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. + * @param Javascript|non-empty-string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. * @param non-empty-string $lang The language used in the $accumulator code. * @param Optional|BSONArray|PackedArray|ResolvesToArray|array $initArgs Arguments passed to the init function. - * @param Optional|non-empty-string $finalize Function used to update the result of the accumulation. + * @param Optional|Javascript|non-empty-string $finalize Function used to update the result of the accumulation. */ public static function accumulator( - string $init, - string $accumulate, + Javascript|string $init, + Javascript|string $accumulate, PackedArray|ResolvesToArray|BSONArray|array $accumulateArgs, - string $merge, + Javascript|string $merge, string $lang, Optional|PackedArray|ResolvesToArray|BSONArray|array $initArgs = Optional::Undefined, - Optional|string $finalize = Optional::Undefined, + Optional|Javascript|string $finalize = Optional::Undefined, ): AccumulatorAccumulator { return new AccumulatorAccumulator($init, $accumulate, $accumulateArgs, $merge, $lang, $initArgs, $finalize); diff --git a/src/Builder/Query.php b/src/Builder/Query.php index a7f4ccccb..bb74d4cbf 100644 --- a/src/Builder/Query.php +++ b/src/Builder/Query.php @@ -4,8 +4,9 @@ namespace MongoDB\Builder; +use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; use MongoDB\BSON\Regex; -use MongoDB\BSON\Serializable; use MongoDB\Builder\Query\RegexOperator; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\QueryInterface; @@ -40,7 +41,7 @@ public static function regex(Regex|string $regex, string|null $flags = null): Re return self::generatedRegex($regex); } - public static function query(FieldQueryInterface|QueryInterface|Serializable|array|bool|float|int|stdClass|string|null ...$query): QueryInterface + public static function query(QueryInterface|FieldQueryInterface|Decimal128|Int64|Regex|stdClass|array|bool|float|int|string|null ...$query): QueryInterface { return QueryObject::create($query); } diff --git a/src/Builder/Type/QueryObject.php b/src/Builder/Type/QueryObject.php index 2fbf34c0c..af6e4376a 100644 --- a/src/Builder/Type/QueryObject.php +++ b/src/Builder/Type/QueryObject.php @@ -27,7 +27,7 @@ final class QueryObject implements QueryInterface { public readonly array $queries; - /** @param array $queries */ + /** @param array $queries */ public static function create(array $queries): QueryInterface { // We don't wrap a single query in a QueryObject @@ -38,7 +38,7 @@ public static function create(array $queries): QueryInterface return new self($queries); } - /** @param array $queriesOrArrayOfQueries */ + /** @param array $queriesOrArrayOfQueries */ private function __construct(array $queriesOrArrayOfQueries) { // If the first element is an array and not an operator, we assume variadic arguments were not used diff --git a/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php b/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php new file mode 100644 index 000000000..8a392f00a --- /dev/null +++ b/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php @@ -0,0 +1,62 @@ +assertSamePipeline(Pipelines::AccumulatorUseAccumulatorToImplementTheAvgOperator, $pipeline); + } + + public function testUseInitArgsToVaryTheInitialStateByGroup(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: object(city: Expression::fieldPath('city')), + restaurants: Accumulator::accumulator( + init: new Javascript('function (city, userProfileCity) { return { max: city === userProfileCity ? 3 : 1, restaurants: [] } }'), + initArgs: [ + Expression::fieldPath('city'), + 'Bettles', + ], + accumulate: new Javascript('function (state, restaurantName) { if (state.restaurants.length < state.max) { state.restaurants.push(restaurantName); } return state; }'), + accumulateArgs: [Expression::fieldPath('name')], + merge: new Javascript('function (state1, state2) { return { max: state1.max, restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) } }'), + finalize: new Javascript('function (state) { return state.restaurants }'), + lang: 'js', + ), + ), + ); + + $this->assertSamePipeline(Pipelines::AccumulatorUseInitArgsToVaryTheInitialStateByGroup, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/AddToSetAccumulatorTest.php b/tests/Builder/Accumulator/AddToSetAccumulatorTest.php new file mode 100644 index 000000000..a63b3270f --- /dev/null +++ b/tests/Builder/Accumulator/AddToSetAccumulatorTest.php @@ -0,0 +1,57 @@ +assertSamePipeline(Pipelines::AddToSetUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + cakeTypesForState: Accumulator::outputWindow( + Accumulator::addToSet(Expression::fieldPath('type')), + documents: [ + 'unbounded', + 'current', + ], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::AddToSetUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/Pipelines.php b/tests/Builder/Accumulator/Pipelines.php new file mode 100644 index 000000000..3e1013999 --- /dev/null +++ b/tests/Builder/Accumulator/Pipelines.php @@ -0,0 +1,146 @@ +value; + } + + // BSON Documents doesn't support top-level arrays. + $expected = toPHP(fromJSON('{"root":' . $expectedJson . '}'))->root; + + $codec = new BuilderEncoder(); + $actual = $codec->encode($pipeline); + + self::assertEquals($expected, $actual, var_export($actual, true)); + } +} diff --git a/tests/Builder/Projection/Pipelines.php b/tests/Builder/Projection/Pipelines.php new file mode 100644 index 000000000..251d9e69a --- /dev/null +++ b/tests/Builder/Projection/Pipelines.php @@ -0,0 +1,13 @@ +assertSamePipeline(Pipelines::RegexPerformALIKEMatch, $pipeline); + } + + public function testPerformCaseInsensitiveRegularExpressionMatch(): void + { + $pipeline = new Pipeline( + Stage::match( + // sku: \MongoDB\Builder\Query::regex('^ABC', 'i'), + sku: new Regex('^ABC', 'i'), + ), + ); + + $this->assertSamePipeline(Pipelines::RegexPerformCaseInsensitiveRegularExpressionMatch, $pipeline); + } +} diff --git a/tests/Builder/Stage/AddFieldsStageTest.php b/tests/Builder/Stage/AddFieldsStageTest.php new file mode 100644 index 000000000..9e8863139 --- /dev/null +++ b/tests/Builder/Stage/AddFieldsStageTest.php @@ -0,0 +1,48 @@ + 'unleaded'], + ), + ); + + $this->assertSamePipeline(Pipelines::AddFieldsAddingFieldsToAnEmbeddedDocument, $pipeline); + } + + public function testUsingTwoAddFieldsStages(): void + { + $this->markTestSkipped('$sum must accept arrayFieldPath and render it as a single value: https://jira.mongodb.org/browse/PHPLIB-1287'); + + $pipeline = new Pipeline( + Stage::addFields( + totalHomework: Expression::sum(Expression::fieldPath('homework')), + totalQuiz: Expression::sum(Expression::fieldPath('quiz')), + ), + Stage::addFields( + totalScore: Expression::add( + Expression::fieldPath('totalHomework'), + Expression::fieldPath('totalQuiz'), + Expression::fieldPath('extraCredit'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::AddFieldsUsingTwoAddFieldsStages, $pipeline); + } +} diff --git a/tests/Builder/Stage/Pipelines.php b/tests/Builder/Stage/Pipelines.php new file mode 100644 index 000000000..c671f1c19 --- /dev/null +++ b/tests/Builder/Stage/Pipelines.php @@ -0,0 +1,58 @@ + Date: Tue, 31 Oct 2023 18:46:22 +0100 Subject: [PATCH 12/95] Validate duplicate operators are not used in CombinedFieldQuery (#3) --- src/Builder/Type/CombinedFieldQuery.php | 70 ++++++++++++--- tests/Builder/Type/CombinedFieldQueryTest.php | 85 ++++++++++++++++--- 2 files changed, 135 insertions(+), 20 deletions(-) diff --git a/src/Builder/Type/CombinedFieldQuery.php b/src/Builder/Type/CombinedFieldQuery.php index 67494363a..4a86fa31a 100644 --- a/src/Builder/Type/CombinedFieldQuery.php +++ b/src/Builder/Type/CombinedFieldQuery.php @@ -4,28 +4,78 @@ namespace MongoDB\Builder\Type; -use MongoDB\BSON\Document; -use MongoDB\BSON\Serializable; +use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; +use MongoDB\BSON\Regex; use MongoDB\Exception\InvalidArgumentException; use stdClass; +use function array_is_list; +use function array_key_exists; +use function array_key_first; +use function array_merge; +use function array_reduce; +use function count; use function get_debug_type; +use function get_object_vars; use function is_array; +use function is_string; use function sprintf; +use function str_starts_with; /** - * List of filters that apply to the same field path. + * List of field queries that apply to the same field path. */ class CombinedFieldQuery implements FieldQueryInterface { - public function __construct( - /** @var list $fieldQueries */ - public readonly array $fieldQueries, - ) { - foreach ($fieldQueries as $fieldQuery) { - if (! $fieldQuery instanceof FieldQueryInterface && ! $fieldQuery instanceof Serializable && ! is_array($fieldQuery) && ! $fieldQuery instanceof stdClass) { - throw new InvalidArgumentException(sprintf('Expected filters to be a list of %s, %s, array or stdClass, %s given.', FieldQueryInterface::class, Document::class, get_debug_type($fieldQuery))); + /** @var list $fieldQueries */ + public readonly array $fieldQueries; + + /** @param list $fieldQueries */ + public function __construct(array $fieldQueries) + { + if (! array_is_list($fieldQueries)) { + throw new InvalidArgumentException('Expected filters to be a list, invalid array given.'); + } + + // Flatten nested CombinedFieldQuery + $this->fieldQueries = array_reduce($fieldQueries, static function (array $fieldQueries, QueryInterface|FieldQueryInterface|Decimal128|Int64|Regex|stdClass|array|bool|float|int|string|null $fieldQuery): array { + if ($fieldQuery instanceof CombinedFieldQuery) { + return array_merge($fieldQueries, $fieldQuery->fieldQueries); + } + + $fieldQueries[] = $fieldQuery; + + return $fieldQueries; + }, []); + + // Validate FieldQuery types and non-duplicate operators + $seenOperators = []; + foreach ($this->fieldQueries as $fieldQuery) { + if ($fieldQuery instanceof stdClass) { + $fieldQuery = get_object_vars($fieldQuery); } + + if ($fieldQuery instanceof FieldQueryInterface && $fieldQuery instanceof OperatorInterface) { + $operator = $fieldQuery->getOperator(); + } elseif (is_array($fieldQuery)) { + if (count($fieldQuery) !== 1) { + throw new InvalidArgumentException(sprintf('Operator must contain exactly one key, %d given', count($fieldQuery))); + } + + $operator = array_key_first($fieldQuery); + if (! is_string($operator) || ! str_starts_with($operator, '$')) { + throw new InvalidArgumentException(sprintf('Operator must contain exactly one key starting with $, "%s" given', $operator)); + } + } else { + throw new InvalidArgumentException(sprintf('Expected filters to be a list of field query operators, array or stdClass, %s given', get_debug_type($fieldQuery))); + } + + if (array_key_exists($operator, $seenOperators)) { + throw new InvalidArgumentException(sprintf('Duplicate operator "%s" detected', $operator)); + } + + $seenOperators[$operator] = true; } } } diff --git a/tests/Builder/Type/CombinedFieldQueryTest.php b/tests/Builder/Type/CombinedFieldQueryTest.php index d9b088264..1e302f8bd 100644 --- a/tests/Builder/Type/CombinedFieldQueryTest.php +++ b/tests/Builder/Type/CombinedFieldQueryTest.php @@ -5,44 +5,109 @@ namespace MongoDB\Tests\Builder\Type; use Generator; +use MongoDB\Builder\Query\EqOperator; +use MongoDB\Builder\Query\GtOperator; use MongoDB\Builder\Type\CombinedFieldQuery; use MongoDB\Exception\InvalidArgumentException; use PHPUnit\Framework\TestCase; class CombinedFieldQueryTest extends TestCase { - public function testEmptyFieldQueries(): void + public function testEmpty(): void { $fieldQueries = new CombinedFieldQuery([]); $this->assertSame([], $fieldQueries->fieldQueries); } - public function testFieldQueries(): void + public function testSupportedTypes(): void { $fieldQueries = new CombinedFieldQuery([ - $this->createMock(CombinedFieldQuery::class), + new EqOperator(1), ['$gt' => 1], - new CombinedFieldQuery([]), + (object) ['$lt' => 1], + ]); + + $this->assertCount(3, $fieldQueries->fieldQueries); + } + + public function testFlattenCombinedFieldQueries(): void + { + $fieldQueries = new CombinedFieldQuery([ + new CombinedFieldQuery([ + new CombinedFieldQuery([ + ['$lt' => 1], + new CombinedFieldQuery([]), + ]), + ['$gt' => 1], + ]), + ['$gte' => 1], ]); $this->assertCount(3, $fieldQueries->fieldQueries); } /** @dataProvider provideInvalidFieldQuery */ - public function testRejectInvalidFieldQueries($invalidQuery): void + public function testRejectInvalidFieldQueries(mixed $invalidQuery, string $message = '-'): void { $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($message); new CombinedFieldQuery([$invalidQuery]); } public static function provideInvalidFieldQuery(): Generator { - yield 'int' => [1]; - yield 'float' => [1.1]; - yield 'string' => ['foo']; - yield 'bool' => [true]; - yield 'null' => [null]; + yield 'int' => [1, 'Expected filters to be a list of field query operators, array or stdClass, int given']; + yield 'float' => [1.1, 'Expected filters to be a list of field query operators, array or stdClass, float given']; + yield 'string' => ['foo', 'Expected filters to be a list of field query operators, array or stdClass, string given']; + yield 'bool' => [true, 'Expected filters to be a list of field query operators, array or stdClass, bool given']; + yield 'null' => [null, 'Expected filters to be a list of field query operators, array or stdClass, null given']; + yield 'empty array' => [[], 'Operator must contain exactly one key, 0 given']; + yield 'array with two keys' => [['$eq' => 1, '$ne' => 2], 'Operator must contain exactly one key, 2 given']; + yield 'array key without $' => [['eq' => 1], 'Operator must contain exactly one key starting with $, "eq" given']; + yield 'empty object' => [(object) [], 'Operator must contain exactly one key, 0 given']; + yield 'object with two keys' => [(object) ['$eq' => 1, '$ne' => 2], 'Operator must contain exactly one key, 2 given']; + yield 'object key without $' => [(object) ['eq' => 1], 'Operator must contain exactly one key starting with $, "eq" given']; + } + + /** + * @param array $fieldQueries + * + * @dataProvider provideDuplicateOperator + */ + public function testRejectDuplicateOperator(array $fieldQueries): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Duplicate operator "$eq" detected'); + + new CombinedFieldQuery([ + ['$eq' => 1], + new EqOperator(2), + ]); + } + + public function provideDuplicateOperator(): Generator + { + yield 'array and FieldQuery' => [ + [ + ['$eq' => 1], + new EqOperator(2), + ], + ]; + + yield 'object and FieldQuery' => [ + [ + (object) ['$gt' => 1], + new GtOperator(2), + ], + ]; + + yield 'object and array' => [ + [ + (object) ['$ne' => 1], + ['$ne' => 2], + ], + ]; } } From 6f260f99a0680b4ae50d33508c1e38b6321f96de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 2 Nov 2023 20:52:56 +0100 Subject: [PATCH 13/95] Expand accepted types for field queries (#11) --- generator/config/expressions.php | 2 +- src/Builder/Query.php | 5 ++--- src/Builder/Query/FactoryTrait.php | 4 ++-- src/Builder/Query/NotOperator.php | 15 ++++++--------- src/Builder/Stage.php | 8 +++----- src/Builder/Type/CombinedFieldQuery.php | 10 ++++------ src/Builder/Type/QueryObject.php | 2 +- tests/Builder/Type/QueryObjectTest.php | 16 ++++++++++------ 8 files changed, 29 insertions(+), 33 deletions(-) diff --git a/generator/config/expressions.php b/generator/config/expressions.php index 869e1e784..41cc3ecf9 100644 --- a/generator/config/expressions.php +++ b/generator/config/expressions.php @@ -83,7 +83,7 @@ ], 'fieldQuery' => [ 'returnType' => Type\FieldQueryInterface::class, - 'acceptedTypes' => [Type\FieldQueryInterface::class, BSON\Regex::class, 'bool', 'string', 'array', 'null', stdClass::class, ...$bsonTypes['number']], + 'acceptedTypes' => [Type\FieldQueryInterface::class, ...$bsonTypes['any']], ], 'query' => [ 'returnType' => Type\QueryInterface::class, diff --git a/src/Builder/Query.php b/src/Builder/Query.php index bb74d4cbf..2d8f5c015 100644 --- a/src/Builder/Query.php +++ b/src/Builder/Query.php @@ -4,9 +4,8 @@ namespace MongoDB\Builder; -use MongoDB\BSON\Decimal128; -use MongoDB\BSON\Int64; use MongoDB\BSON\Regex; +use MongoDB\BSON\Type; use MongoDB\Builder\Query\RegexOperator; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\QueryInterface; @@ -41,7 +40,7 @@ public static function regex(Regex|string $regex, string|null $flags = null): Re return self::generatedRegex($regex); } - public static function query(QueryInterface|FieldQueryInterface|Decimal128|Int64|Regex|stdClass|array|bool|float|int|string|null ...$query): QueryInterface + public static function query(QueryInterface|FieldQueryInterface|Type|stdClass|array|bool|float|int|string|null ...$query): QueryInterface { return QueryObject::create($query); } diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index 473e46797..5ed63b815 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -413,10 +413,10 @@ public static function nor(QueryInterface|array ...$queries): NorOperator * Inverts the effect of a query expression and returns documents that do not match the query expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/not/ - * @param Decimal128|FieldQueryInterface|Int64|Regex|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param FieldQueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ public static function not( - Decimal128|Int64|Regex|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression, + Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression, ): NotOperator { return new NotOperator($expression); diff --git a/src/Builder/Query/NotOperator.php b/src/Builder/Query/NotOperator.php index 5fa774043..6c48f9308 100644 --- a/src/Builder/Query/NotOperator.php +++ b/src/Builder/Query/NotOperator.php @@ -8,9 +8,7 @@ namespace MongoDB\Builder\Query; -use MongoDB\BSON\Decimal128; -use MongoDB\BSON\Int64; -use MongoDB\BSON\Regex; +use MongoDB\BSON\Type; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\OperatorInterface; @@ -25,15 +23,14 @@ class NotOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Decimal128|FieldQueryInterface|Int64|Regex|array|bool|float|int|non-empty-string|null|stdClass $expression */ - public readonly Decimal128|Int64|Regex|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression; + /** @var FieldQueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + public readonly Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param Decimal128|FieldQueryInterface|Int64|Regex|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param FieldQueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ - public function __construct( - Decimal128|Int64|Regex|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression, - ) { + public function __construct(Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression) + { $this->expression = $expression; } diff --git a/src/Builder/Stage.php b/src/Builder/Stage.php index 63f091d01..2232a0975 100644 --- a/src/Builder/Stage.php +++ b/src/Builder/Stage.php @@ -4,9 +4,7 @@ namespace MongoDB\Builder; -use MongoDB\BSON\Decimal128; -use MongoDB\BSON\Int64; -use MongoDB\BSON\Regex; +use MongoDB\BSON\Type; use MongoDB\Builder\Stage\MatchStage; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\QueryInterface; @@ -23,9 +21,9 @@ final class Stage * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/ * - * @param QueryInterface|FieldQueryInterface|Decimal128|Int64|Regex|stdClass|array|bool|float|int|string|null ...$queries The query predicates to match + * @param QueryInterface|FieldQueryInterface|Type|stdClass|array|bool|float|int|string|null ...$queries The query predicates to match */ - public static function match(QueryInterface|FieldQueryInterface|Decimal128|Int64|Regex|stdClass|array|bool|float|int|string|null ...$queries): MatchStage + public static function match(QueryInterface|FieldQueryInterface|Type|stdClass|array|bool|float|int|string|null ...$queries): MatchStage { // Override the generated method to allow variadic arguments return self::generatedMatch($queries); diff --git a/src/Builder/Type/CombinedFieldQuery.php b/src/Builder/Type/CombinedFieldQuery.php index 4a86fa31a..b85096e5b 100644 --- a/src/Builder/Type/CombinedFieldQuery.php +++ b/src/Builder/Type/CombinedFieldQuery.php @@ -4,9 +4,7 @@ namespace MongoDB\Builder\Type; -use MongoDB\BSON\Decimal128; -use MongoDB\BSON\Int64; -use MongoDB\BSON\Regex; +use MongoDB\BSON\Type; use MongoDB\Exception\InvalidArgumentException; use stdClass; @@ -28,10 +26,10 @@ */ class CombinedFieldQuery implements FieldQueryInterface { - /** @var list $fieldQueries */ + /** @var list $fieldQueries */ public readonly array $fieldQueries; - /** @param list $fieldQueries */ + /** @param list $fieldQueries */ public function __construct(array $fieldQueries) { if (! array_is_list($fieldQueries)) { @@ -39,7 +37,7 @@ public function __construct(array $fieldQueries) } // Flatten nested CombinedFieldQuery - $this->fieldQueries = array_reduce($fieldQueries, static function (array $fieldQueries, QueryInterface|FieldQueryInterface|Decimal128|Int64|Regex|stdClass|array|bool|float|int|string|null $fieldQuery): array { + $this->fieldQueries = array_reduce($fieldQueries, static function (array $fieldQueries, QueryInterface|FieldQueryInterface|Type|stdClass|array|bool|float|int|string|null $fieldQuery): array { if ($fieldQuery instanceof CombinedFieldQuery) { return array_merge($fieldQueries, $fieldQuery->fieldQueries); } diff --git a/src/Builder/Type/QueryObject.php b/src/Builder/Type/QueryObject.php index af6e4376a..293350bd9 100644 --- a/src/Builder/Type/QueryObject.php +++ b/src/Builder/Type/QueryObject.php @@ -84,7 +84,7 @@ private function __construct(array $queriesOrArrayOfQueries) $this->queries = $queries; } - /** @psalm-assert-if-true list $values */ + /** @psalm-assert-if-true list $values */ private static function isListOfFilters(mixed $values): bool { if (! is_array($values) || ! array_is_list($values)) { diff --git a/tests/Builder/Type/QueryObjectTest.php b/tests/Builder/Type/QueryObjectTest.php index ccebab89f..fe7a86959 100644 --- a/tests/Builder/Type/QueryObjectTest.php +++ b/tests/Builder/Type/QueryObjectTest.php @@ -5,9 +5,7 @@ namespace MongoDB\Tests\Builder\Type; use Generator; -use MongoDB\BSON\Decimal128; -use MongoDB\BSON\Int64; -use MongoDB\BSON\Regex; +use MongoDB\BSON; use MongoDB\Builder\Query\CommentOperator; use MongoDB\Builder\Query\EqOperator; use MongoDB\Builder\Query\GtOperator; @@ -66,9 +64,15 @@ public function provideQueryObjectValue(): Generator yield 'string' => [['foo' => 'bar']]; yield 'bool' => [['foo' => true]]; yield 'null' => [['foo' => null]]; - yield 'decimal128' => [['foo' => new Decimal128('1.1')]]; - yield 'int64' => [[1 => new Int64(1)]]; - yield 'regex' => [['foo' => new Regex('foo')]]; + yield 'decimal128' => [['foo' => new BSON\Decimal128('1.1')]]; + yield 'int64' => [[1 => new BSON\Int64(1)]]; + yield 'objectId' => [['foo' => new BSON\ObjectId()]]; + yield 'binary' => [['foo' => new BSON\Binary('foo')]]; + yield 'regex' => [['foo' => new BSON\Regex('foo')]]; + yield 'datetime' => [['foo' => new BSON\UTCDateTime()]]; + yield 'timestamp' => [['foo' => new BSON\Timestamp(1234, 5678)]]; + yield 'bson document' => [['foo' => BSON\Document::fromPHP(['bar' => 'baz'])]]; + yield 'bson array' => [['foo' => BSON\PackedArray::fromPHP(['bar', 'baz'])]]; yield 'object' => [['foo' => (object) ['bar' => 'baz']]]; yield 'list' => [['foo' => ['bar', 'baz']]]; yield 'operator as array' => [['foo' => ['$eq' => 1]]]; From d8898cecfa36d3bd19bc87f44bc8a30b6163bc4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 3 Nov 2023 17:20:02 +0100 Subject: [PATCH 14/95] Use BSON round-trip to normalize pipelines for comparison (#13) * Use BSON round-trip to normalize pipelines for comparison * Use Canonical Extended JSON * Fix $regex comparison --- generator/config/query/regex.yaml | 16 ++++++------- generator/src/OperatorTestGenerator.php | 6 ++++- src/Builder/Type/OutputWindow.php | 2 -- tests/Builder/Accumulator/Pipelines.php | 4 +++- tests/Builder/BuilderEncoderTest.php | 29 +++++------------------ tests/Builder/PipelineTestCase.php | 13 +++++----- tests/Builder/Query/Pipelines.php | 16 ++++++++----- tests/Builder/Query/RegexOperatorTest.php | 8 +++---- 8 files changed, 41 insertions(+), 53 deletions(-) diff --git a/generator/config/query/regex.yaml b/generator/config/query/regex.yaml index 7aee0c8ec..d1e69ad23 100644 --- a/generator/config/query/regex.yaml +++ b/generator/config/query/regex.yaml @@ -20,10 +20,10 @@ tests: - $match: sku: - # Should be nested in $regex - $regularExpression: - pattern: '789$' - options: '' + $regex: + $regularExpression: + pattern: '789$' + options: '' - name: 'Perform Case-Insensitive Regular Expression Match' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/regex/#perform-case-insensitive-regular-expression-match' @@ -31,7 +31,7 @@ tests: - $match: sku: - # Should be nested in $regex - $regularExpression: - pattern: '^ABC' - options: 'i' + $regex: + $regularExpression: + pattern: '^ABC' + options: 'i' diff --git a/generator/src/OperatorTestGenerator.php b/generator/src/OperatorTestGenerator.php index 18273bf99..04a27e907 100644 --- a/generator/src/OperatorTestGenerator.php +++ b/generator/src/OperatorTestGenerator.php @@ -4,6 +4,7 @@ namespace MongoDB\CodeGenerator; +use MongoDB\BSON\Document; use MongoDB\Builder\Pipeline; use MongoDB\CodeGenerator\Definition\GeneratorDefinition; use MongoDB\CodeGenerator\Definition\OperatorDefinition; @@ -16,6 +17,7 @@ use Throwable; use function basename; +use function json_decode; use function json_encode; use function ksort; use function sprintf; @@ -81,7 +83,9 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition $testName = 'test' . str_replace([' ', '-'], '', ucwords(str_replace('$', '', $test->name))); $caseName = str_replace([' ', '-'], '', ucwords(str_replace('$', '', $operator->name . ' ' . $test->name))); - $case = $dataEnum->addCase($caseName, new Literal('<<<\'JSON\'' . "\n" . json_encode($test->pipeline, JSON_PRETTY_PRINT) . "\n" . 'JSON')); + $json = Document::fromPHP(['pipeline' => $test->pipeline])->toCanonicalExtendedJSON(); + $json = json_encode(json_decode($json)->pipeline, JSON_PRETTY_PRINT); + $case = $dataEnum->addCase($caseName, new Literal('<<<\'JSON\'' . "\n" . $json . "\n" . 'JSON')); $case->setComment($test->name); if ($test->link) { $case->addComment(''); diff --git a/src/Builder/Type/OutputWindow.php b/src/Builder/Type/OutputWindow.php index aa30f9487..33096de46 100644 --- a/src/Builder/Type/OutputWindow.php +++ b/src/Builder/Type/OutputWindow.php @@ -25,8 +25,6 @@ */ class OutputWindow implements WindowInterface { - public const ENCODE = Encode::Object; - /** * Function used to initialize the state. The init function receives its arguments from the initArgs array expression. * You can specify the function definition as either BSON type Code or String. diff --git a/tests/Builder/Accumulator/Pipelines.php b/tests/Builder/Accumulator/Pipelines.php index 3e1013999..e0ce83e9b 100644 --- a/tests/Builder/Accumulator/Pipelines.php +++ b/tests/Builder/Accumulator/Pipelines.php @@ -126,7 +126,9 @@ enum Pipelines: string "$setWindowFields": { "partitionBy": "$state", "sortBy": { - "orderDate": 1 + "orderDate": { + "$numberInt": "1" + } }, "output": { "cakeTypesForState": { diff --git a/tests/Builder/BuilderEncoderTest.php b/tests/Builder/BuilderEncoderTest.php index e51287aac..b28078599 100644 --- a/tests/Builder/BuilderEncoderTest.php +++ b/tests/Builder/BuilderEncoderTest.php @@ -5,6 +5,7 @@ namespace MongoDB\Tests\Builder; use Generator; +use MongoDB\BSON\Document; use MongoDB\Builder\Accumulator; use MongoDB\Builder\BuilderEncoder; use MongoDB\Builder\Expression; @@ -15,10 +16,7 @@ use MongoDB\Builder\Variable; use PHPUnit\Framework\TestCase; -use function array_is_list; use function array_merge; -use function array_walk; -use function is_array; use function MongoDB\object; use function var_export; @@ -342,26 +340,11 @@ private static function assertSamePipeline(array $expected, Pipeline $pipeline): $codec = new BuilderEncoder(); $actual = $codec->encode($pipeline); - self::objectify($expected); + // Normalize with BSON round-trip + // BSON Documents doesn't support top-level arrays. + $actual = Document::fromPHP(['root' => $actual])->toCanonicalExtendedJSON(); + $expected = Document::fromPHP(['root' => $expected])->toCanonicalExtendedJSON(); - self::assertEquals($expected, $actual, var_export($actual, true)); - } - - /** - * Recursively convert associative arrays to objects. - * - * @param array $array - */ - private static function objectify(array &$array): void - { - array_walk($array, function (&$value): void { - if (is_array($value)) { - self::objectify($value); - - if (! array_is_list($value)) { - $value = (object) $value; - } - } - }); + self::assertJsonStringEqualsJsonString($expected, $actual, var_export($actual, true)); } } diff --git a/tests/Builder/PipelineTestCase.php b/tests/Builder/PipelineTestCase.php index c220bc5d9..1975a4ab3 100644 --- a/tests/Builder/PipelineTestCase.php +++ b/tests/Builder/PipelineTestCase.php @@ -5,15 +5,12 @@ namespace MongoDB\Tests\Builder; use BackedEnum; +use MongoDB\BSON\Document; use MongoDB\Builder\BuilderEncoder; use MongoDB\Builder\Pipeline; use PHPUnit\Framework\TestCase; -use function MongoDB\BSON\fromJSON; -use function MongoDB\BSON\toPHP; -use function var_export; - -class PipelineTestCase extends TestCase +abstract class PipelineTestCase extends TestCase { final public static function assertSamePipeline(string|BackedEnum $expectedJson, Pipeline $pipeline): void { @@ -22,11 +19,13 @@ final public static function assertSamePipeline(string|BackedEnum $expectedJson, } // BSON Documents doesn't support top-level arrays. - $expected = toPHP(fromJSON('{"root":' . $expectedJson . '}'))->root; + $expected = '{"pipeline":' . $expectedJson . '}'; $codec = new BuilderEncoder(); $actual = $codec->encode($pipeline); + // Normalize with BSON round-trip + $actual = Document::fromPHP(['pipeline' => $actual])->toCanonicalExtendedJSON(); - self::assertEquals($expected, $actual, var_export($actual, true)); + self::assertJsonStringEqualsJsonString($expected, $actual); } } diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 644100bbc..3ea8c1e52 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -20,9 +20,11 @@ enum Pipelines: string { "$match": { "sku": { - "$regularExpression": { - "pattern": "789$", - "options": "" + "$regex": { + "$regularExpression": { + "pattern": "789$", + "options": "" + } } } } @@ -40,9 +42,11 @@ enum Pipelines: string { "$match": { "sku": { - "$regularExpression": { - "pattern": "^ABC", - "options": "i" + "$regex": { + "$regularExpression": { + "pattern": "^ABC", + "options": "i" + } } } } diff --git a/tests/Builder/Query/RegexOperatorTest.php b/tests/Builder/Query/RegexOperatorTest.php index 7837dfe63..6f9109387 100644 --- a/tests/Builder/Query/RegexOperatorTest.php +++ b/tests/Builder/Query/RegexOperatorTest.php @@ -4,8 +4,8 @@ namespace MongoDB\Tests\Builder\Query; -use MongoDB\BSON\Regex; use MongoDB\Builder\Pipeline; +use MongoDB\Builder\Query; use MongoDB\Builder\Stage; use MongoDB\Tests\Builder\PipelineTestCase; @@ -18,8 +18,7 @@ public function testPerformALIKEMatch(): void { $pipeline = new Pipeline( Stage::match( - // sku: \MongoDB\Builder\Query::regex('789$', ''), - sku: new Regex('789$', ''), + sku: Query::regex('789$', ''), ), ); @@ -30,8 +29,7 @@ public function testPerformCaseInsensitiveRegularExpressionMatch(): void { $pipeline = new Pipeline( Stage::match( - // sku: \MongoDB\Builder\Query::regex('^ABC', 'i'), - sku: new Regex('^ABC', 'i'), + sku: Query::regex('^ABC', 'i'), ), ); From 0bedc20a9eea97a7e25d9ac3d681a5b5c3c66a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 9 Nov 2023 09:34:03 +0100 Subject: [PATCH 15/95] Add script to help convert JS to Yaml (#15) --- generator/README.md | 3 ++ generator/js2yaml.html | 105 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 generator/js2yaml.html diff --git a/generator/README.md b/generator/README.md index 5f05180d9..fbb54183f 100644 --- a/generator/README.md +++ b/generator/README.md @@ -16,3 +16,6 @@ To run the generator, you need to have PHP 8.1+ installed and Composer. ## Configuration The `generator/config/*.yaml` files contains the list of operators and stages that are supported by the library. + +To add new test cases to operators, you can get inspiration from the official MongoDB documentation and use +the `generator/js2yaml.html` web page to manually convert a pipeline array from JS to Yaml. diff --git a/generator/js2yaml.html b/generator/js2yaml.html new file mode 100644 index 000000000..f333ae5ae --- /dev/null +++ b/generator/js2yaml.html @@ -0,0 +1,105 @@ + + +

Convert JS examples into Yaml

+ +
+ + +
+ +
+ + +
+ + + From a939aa0d6024e2704b902bb179f0ebde9a1a2682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 13 Nov 2023 20:51:09 +0100 Subject: [PATCH 16/95] Add `first`, `firstN`, `last`, `lastN` operators and tests (#14) * Add first,firstN,last,lastN operators and tests * Add tests on $first and $last --- generator/config/accumulator/first.yaml | 30 ++ generator/config/accumulator/firstN.yaml | 41 ++- generator/config/accumulator/last.yaml | 30 ++ generator/config/accumulator/lastN.yaml | 70 +++- generator/config/expression/first.yaml | 21 ++ generator/config/expression/firstN.yaml | 33 ++ generator/config/expression/last.yaml | 21 ++ generator/config/expression/lastN.yaml | 51 +++ generator/config/stage/project.yaml | 5 +- generator/config/stage/setWindowFields.yaml | 7 - src/Builder/Accumulator/FactoryTrait.php | 14 +- src/Builder/Accumulator/FirstNAccumulator.php | 34 +- src/Builder/Accumulator/LastNAccumulator.php | 7 +- src/Builder/Expression/FactoryTrait.php | 52 +++ src/Builder/Expression/FirstNOperator.php | 53 +++ src/Builder/Expression/FirstOperator.php | 48 +++ src/Builder/Expression/LastNOperator.php | 53 +++ src/Builder/Expression/LastOperator.php | 48 +++ src/Builder/Stage/FactoryTrait.php | 10 +- src/Builder/Stage/ProjectStage.php | 9 +- src/Builder/Stage/SetWindowFieldsStage.php | 8 - .../AccumulatorAccumulatorTest.php | 8 +- .../Accumulator/FirstAccumulatorTest.php | 55 ++++ .../Accumulator/FirstNAccumulatorTest.php | 41 +++ .../Accumulator/LastAccumulatorTest.php | 55 ++++ .../Accumulator/LastNAccumulatorTest.php | 101 ++++++ tests/Builder/Accumulator/Pipelines.php | 303 ++++++++++++++++++ .../Builder/Expression/FirstNOperatorTest.php | 27 ++ .../Builder/Expression/FirstOperatorTest.php | 27 ++ .../Builder/Expression/LastNOperatorTest.php | 43 +++ tests/Builder/Expression/LastOperatorTest.php | 27 ++ tests/Builder/Expression/Pipelines.php | 111 +++++++ 32 files changed, 1379 insertions(+), 64 deletions(-) create mode 100644 generator/config/expression/first.yaml create mode 100644 generator/config/expression/firstN.yaml create mode 100644 generator/config/expression/last.yaml create mode 100644 generator/config/expression/lastN.yaml create mode 100644 src/Builder/Expression/FirstNOperator.php create mode 100644 src/Builder/Expression/FirstOperator.php create mode 100644 src/Builder/Expression/LastNOperator.php create mode 100644 src/Builder/Expression/LastOperator.php create mode 100644 tests/Builder/Accumulator/FirstAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/FirstNAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/LastAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/LastNAccumulatorTest.php create mode 100644 tests/Builder/Expression/FirstNOperatorTest.php create mode 100644 tests/Builder/Expression/FirstOperatorTest.php create mode 100644 tests/Builder/Expression/LastNOperatorTest.php create mode 100644 tests/Builder/Expression/LastOperatorTest.php diff --git a/generator/config/accumulator/first.yaml b/generator/config/accumulator/first.yaml index 2de21e535..d82f831a0 100644 --- a/generator/config/accumulator/first.yaml +++ b/generator/config/accumulator/first.yaml @@ -13,3 +13,33 @@ arguments: name: expression type: - expression +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/#use-in--group-stage' + pipeline: + - + $sort: + item: 1 + date: 1 + - + $group: + _id: '$item' + firstSale: + $first: '$date' + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + firstOrderTypeForState: + $first: '$type' + window: + documents: + - 'unbounded' + - 'current' diff --git a/generator/config/accumulator/firstN.yaml b/generator/config/accumulator/firstN.yaml index 06ea49bcc..85676ea1c 100644 --- a/generator/config/accumulator/firstN.yaml +++ b/generator/config/accumulator/firstN.yaml @@ -6,12 +6,14 @@ type: - window encode: object description: | - Returns a specified number of elements from the beginning of an array. Distinct from the $firstN accumulator. + Returns an aggregation of the first n elements within a group. + The elements returned are meaningful only if in a specified sort order. + If the group contains fewer than n elements, $firstN returns all elements in the group. arguments: - name: input type: - - resolvesToArray + - expression description: | An expression that resolves to the array from which to return n elements. - @@ -19,4 +21,37 @@ arguments: type: - resolvesToInt description: | - An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. + A positive integral expression that is either a constant or depends on the _id value for $group. +tests: + - + name: 'Null and Missing Values' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#null-and-missing-values' + pipeline: + - + $documents: + - + playerId: 'PlayerA' + gameId: 'G1' + score: 1 + - + playerId: 'PlayerB' + gameId: 'G1' + score: 2 + - + playerId: 'PlayerC' + gameId: 'G1' + score: 3 + - + playerId: 'PlayerD' + gameId: 'G1' + - + playerId: 'PlayerE' + gameId: 'G1' + score: ~ + - + $group: + _id: '$gameId' + firstFiveScores: + $firstN: + input: '$score' + n: 5 diff --git a/generator/config/accumulator/last.yaml b/generator/config/accumulator/last.yaml index 175846ef4..969c05524 100644 --- a/generator/config/accumulator/last.yaml +++ b/generator/config/accumulator/last.yaml @@ -13,3 +13,33 @@ arguments: name: expression type: - expression +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/' + pipeline: + - + $sort: + item: 1 + date: 1 + - + $group: + _id: '$item' + lastSalesDate: + $last: '$date' + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + lastOrderTypeForState: + $last: '$type' + window: + documents: + - 'current' + - 'unbounded' diff --git a/generator/config/accumulator/lastN.yaml b/generator/config/accumulator/lastN.yaml index 224c05c94..13c9b72bd 100644 --- a/generator/config/accumulator/lastN.yaml +++ b/generator/config/accumulator/lastN.yaml @@ -2,10 +2,13 @@ name: $lastN link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/' type: + - accumulator - window encode: object description: | - Returns a specified number of elements from the end of an array. Distinct from the $lastN accumulator. + Returns an aggregation of the last n elements within a group. + The elements returned are meaningful only if in a specified sort order. + If the group contains fewer than n elements, $lastN returns all elements in the group. arguments: - name: input @@ -19,3 +22,68 @@ arguments: - resolvesToInt description: | An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. +tests: + - + name: 'Find the Last Three Player Scores for a Single Game' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#find-the-last-three-player-scores-for-a-single-game' + pipeline: + - + $match: + gameId: 'G1' + - + $group: + _id: '$gameId' + lastThreeScores: + $lastN: + input: + - '$playerId' + - '$score' + n: 3 + - + name: 'Finding the Last Three Player Scores Across Multiple Games' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#finding-the-last-three-player-scores-across-multiple-games' + pipeline: + - + $group: + _id: '$gameId' + playerId: + $lastN: + input: + - '$playerId' + - '$score' + n: 3 + - + name: 'Using $sort With $lastN' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#using--sort-with--lastn' + pipeline: + - + $sort: + score: -1 + - + $group: + _id: '$gameId' + playerId: + $lastN: + input: + - '$playerId' + - '$score' + n: 3 + - + name: 'Computing n Based on the Group Key for $group' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#computing-n-based-on-the-group-key-for--group' + pipeline: + - + $group: + _id: + gameId: '$gameId' + gamescores: + $lastN: + input: '$score' + n: + $cond: + if: + $eq: + - '$gameId' + - 'G2' + then: 1 + else: 3 diff --git a/generator/config/expression/first.yaml b/generator/config/expression/first.yaml new file mode 100644 index 000000000..262d340c3 --- /dev/null +++ b/generator/config/expression/first.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $first +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/' +type: + - resolvesToAny +encode: single +description: | + Returns the result of an expression for the first document in an array. +arguments: + - + name: expression + type: + - resolvesToArray +tests: + - + name: 'Use in $addFields Stage' + pipeline: + - + $addFields: + firstItem: + $first: '$items' diff --git a/generator/config/expression/firstN.yaml b/generator/config/expression/firstN.yaml new file mode 100644 index 000000000..23a67ab74 --- /dev/null +++ b/generator/config/expression/firstN.yaml @@ -0,0 +1,33 @@ +# $schema: ../schema.json +name: $firstN +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN-array-element/' +type: + - resolvesToArray +encode: object +description: | + Returns a specified number of elements from the beginning of an array. +arguments: + - + name: 'n' + type: + - resolvesToInt + description: | + An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. + - + name: input + type: + - resolvesToArray + description: | + An expression that resolves to the array from which to return n elements. + +tests: + - + name: Example + link: https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN-array-element/#example + pipeline: + - + $addFields: + firstScores: + $firstN: + n: 3 + input: '$score' diff --git a/generator/config/expression/last.yaml b/generator/config/expression/last.yaml new file mode 100644 index 000000000..2bf18dc7b --- /dev/null +++ b/generator/config/expression/last.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $last +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/' +type: + - resolvesToAny +encode: single +description: | + Returns the result of an expression for the last document in an array. +arguments: + - + name: expression + type: + - resolvesToArray +tests: + - + name: 'Use in $addFields Stage' + pipeline: + - + $addFields: + lastItem: + $last: '$items' diff --git a/generator/config/expression/lastN.yaml b/generator/config/expression/lastN.yaml new file mode 100644 index 000000000..0c29ad76e --- /dev/null +++ b/generator/config/expression/lastN.yaml @@ -0,0 +1,51 @@ +# $schema: ../schema.json +name: $lastN +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN-array-element/' +type: + - resolvesToArray +encode: object +description: | + Returns a specified number of elements from the end of an array. +arguments: + - + name: 'n' + type: + - resolvesToInt + description: | + An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. + - + name: input + type: + - resolvesToArray + description: | + An expression that resolves to the array from which to return n elements. +tests: + - + name: Example + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN-array-element/#example' + pipeline: + - + $addFields: + lastScores: + $lastN: + n: 3 + input: '$score' + + - + name: 'Using $lastN as an Aggregation Expression' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#using--lastn-as-an-aggregation-expression' + pipeline: + - + $documents: + - + array: + - 10 + - 20 + - 30 + - 40 + - + $project: + lastThreeElements: + $lastN: + input: '$array' + n: 3 diff --git a/generator/config/stage/project.yaml b/generator/config/stage/project.yaml index ee82ea070..ee45a0fa9 100644 --- a/generator/config/stage/project.yaml +++ b/generator/config/stage/project.yaml @@ -10,9 +10,6 @@ arguments: - name: specification type: - - bool - - int + - expression - projection - - resolvesToBool - - object variadic: object diff --git a/generator/config/stage/setWindowFields.yaml b/generator/config/stage/setWindowFields.yaml index 730b2edc7..7afc5c990 100644 --- a/generator/config/stage/setWindowFields.yaml +++ b/generator/config/stage/setWindowFields.yaml @@ -27,10 +27,3 @@ arguments: description: | Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. - - - name: window - type: - - window - optional: true - description: | - Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. diff --git a/src/Builder/Accumulator/FactoryTrait.php b/src/Builder/Accumulator/FactoryTrait.php index c53f323a3..edae0228d 100644 --- a/src/Builder/Accumulator/FactoryTrait.php +++ b/src/Builder/Accumulator/FactoryTrait.php @@ -237,14 +237,16 @@ public static function first( } /** - * Returns a specified number of elements from the beginning of an array. Distinct from the $firstN accumulator. + * Returns an aggregation of the first n elements within a group. + * The elements returned are meaningful only if in a specified sort order. + * If the group contains fewer than n elements, $firstN returns all elements in the group. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/ - * @param BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to the array from which to return n elements. - * @param ResolvesToInt|int $n An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input An expression that resolves to the array from which to return n elements. + * @param ResolvesToInt|int $n A positive integral expression that is either a constant or depends on the _id value for $group. */ public static function firstN( - PackedArray|ResolvesToArray|BSONArray|array $input, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $input, ResolvesToInt|int $n, ): FirstNAccumulator { @@ -266,7 +268,9 @@ public static function last( } /** - * Returns a specified number of elements from the end of an array. Distinct from the $lastN accumulator. + * Returns an aggregation of the last n elements within a group. + * The elements returned are meaningful only if in a specified sort order. + * If the group contains fewer than n elements, $lastN returns all elements in the group. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/ * @param BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to the array from which to return n elements. diff --git a/src/Builder/Accumulator/FirstNAccumulator.php b/src/Builder/Accumulator/FirstNAccumulator.php index fd76b0aff..ef5b4de77 100644 --- a/src/Builder/Accumulator/FirstNAccumulator.php +++ b/src/Builder/Accumulator/FirstNAccumulator.php @@ -8,21 +8,19 @@ namespace MongoDB\Builder\Accumulator; -use MongoDB\BSON\PackedArray; -use MongoDB\Builder\Expression\ResolvesToArray; +use MongoDB\BSON\Type; use MongoDB\Builder\Expression\ResolvesToInt; use MongoDB\Builder\Type\AccumulatorInterface; use MongoDB\Builder\Type\Encode; +use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\WindowInterface; -use MongoDB\Exception\InvalidArgumentException; -use MongoDB\Model\BSONArray; - -use function array_is_list; -use function is_array; +use stdClass; /** - * Returns a specified number of elements from the beginning of an array. Distinct from the $firstN accumulator. + * Returns an aggregation of the first n elements within a group. + * The elements returned are meaningful only if in a specified sort order. + * If the group contains fewer than n elements, $firstN returns all elements in the group. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/ */ @@ -30,22 +28,20 @@ class FirstNAccumulator implements AccumulatorInterface, WindowInterface, Operat { public const ENCODE = Encode::Object; - /** @var BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to the array from which to return n elements. */ - public readonly PackedArray|ResolvesToArray|BSONArray|array $input; + /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input An expression that resolves to the array from which to return n elements. */ + public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $input; - /** @var ResolvesToInt|int $n An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. */ + /** @var ResolvesToInt|int $n A positive integral expression that is either a constant or depends on the _id value for $group. */ public readonly ResolvesToInt|int $n; /** - * @param BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to the array from which to return n elements. - * @param ResolvesToInt|int $n An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input An expression that resolves to the array from which to return n elements. + * @param ResolvesToInt|int $n A positive integral expression that is either a constant or depends on the _id value for $group. */ - public function __construct(PackedArray|ResolvesToArray|BSONArray|array $input, ResolvesToInt|int $n) - { - if (is_array($input) && ! array_is_list($input)) { - throw new InvalidArgumentException('Expected $input argument to be a list, got an associative array.'); - } - + public function __construct( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $input, + ResolvesToInt|int $n, + ) { $this->input = $input; $this->n = $n; } diff --git a/src/Builder/Accumulator/LastNAccumulator.php b/src/Builder/Accumulator/LastNAccumulator.php index def4370b4..d0752f58d 100644 --- a/src/Builder/Accumulator/LastNAccumulator.php +++ b/src/Builder/Accumulator/LastNAccumulator.php @@ -11,6 +11,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\Builder\Expression\ResolvesToArray; use MongoDB\Builder\Expression\ResolvesToInt; +use MongoDB\Builder\Type\AccumulatorInterface; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\WindowInterface; @@ -21,11 +22,13 @@ use function is_array; /** - * Returns a specified number of elements from the end of an array. Distinct from the $lastN accumulator. + * Returns an aggregation of the last n elements within a group. + * The elements returned are meaningful only if in a specified sort order. + * If the group contains fewer than n elements, $lastN returns all elements in the group. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/ */ -class LastNAccumulator implements WindowInterface, OperatorInterface +class LastNAccumulator implements AccumulatorInterface, WindowInterface, OperatorInterface { public const ENCODE = Encode::Object; diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 01fbf2eef..066df2544 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -718,6 +718,32 @@ public static function filter( return new FilterOperator($input, $cond, $as, $limit); } + /** + * Returns the result of an expression for the first document in an array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/ + * @param BSONArray|PackedArray|ResolvesToArray|array $expression + */ + public static function first(PackedArray|ResolvesToArray|BSONArray|array $expression): FirstOperator + { + return new FirstOperator($expression); + } + + /** + * Returns a specified number of elements from the beginning of an array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN-array-element/ + * @param ResolvesToInt|int $n An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. + * @param BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to the array from which to return n elements. + */ + public static function firstN( + ResolvesToInt|int $n, + PackedArray|ResolvesToArray|BSONArray|array $input, + ): FirstNOperator + { + return new FirstNOperator($n, $input); + } + /** * Returns the largest integer less than or equal to the specified number. * @@ -1001,6 +1027,32 @@ public static function isoWeekYear( return new IsoWeekYearOperator($date, $timezone); } + /** + * Returns the result of an expression for the last document in an array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/ + * @param BSONArray|PackedArray|ResolvesToArray|array $expression + */ + public static function last(PackedArray|ResolvesToArray|BSONArray|array $expression): LastOperator + { + return new LastOperator($expression); + } + + /** + * Returns a specified number of elements from the end of an array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN-array-element/ + * @param ResolvesToInt|int $n An expression that resolves to a positive integer. The integer specifies the number of array elements that $firstN returns. + * @param BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to the array from which to return n elements. + */ + public static function lastN( + ResolvesToInt|int $n, + PackedArray|ResolvesToArray|BSONArray|array $input, + ): LastNOperator + { + return new LastNOperator($n, $input); + } + /** * Defines variables for use within the scope of a subexpression and returns the result of the subexpression. Accepts named parameters. * Accepts any number of argument expressions. diff --git a/src/Builder/Expression/FirstNOperator.php b/src/Builder/Expression/FirstNOperator.php new file mode 100644 index 000000000..7aeb04eba --- /dev/null +++ b/src/Builder/Expression/FirstNOperator.php @@ -0,0 +1,53 @@ +n = $n; + if (is_array($input) && ! array_is_list($input)) { + throw new InvalidArgumentException('Expected $input argument to be a list, got an associative array.'); + } + + $this->input = $input; + } + + public function getOperator(): string + { + return '$firstN'; + } +} diff --git a/src/Builder/Expression/FirstOperator.php b/src/Builder/Expression/FirstOperator.php new file mode 100644 index 000000000..5453af68f --- /dev/null +++ b/src/Builder/Expression/FirstOperator.php @@ -0,0 +1,48 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$first'; + } +} diff --git a/src/Builder/Expression/LastNOperator.php b/src/Builder/Expression/LastNOperator.php new file mode 100644 index 000000000..f1bbaab11 --- /dev/null +++ b/src/Builder/Expression/LastNOperator.php @@ -0,0 +1,53 @@ +n = $n; + if (is_array($input) && ! array_is_list($input)) { + throw new InvalidArgumentException('Expected $input argument to be a list, got an associative array.'); + } + + $this->input = $input; + } + + public function getOperator(): string + { + return '$lastN'; + } +} diff --git a/src/Builder/Expression/LastOperator.php b/src/Builder/Expression/LastOperator.php new file mode 100644 index 000000000..ea622018c --- /dev/null +++ b/src/Builder/Expression/LastOperator.php @@ -0,0 +1,48 @@ +expression = $expression; + } + + public function getOperator(): string + { + return '$last'; + } +} diff --git a/src/Builder/Stage/FactoryTrait.php b/src/Builder/Stage/FactoryTrait.php index e143bdeb1..c96b8c564 100644 --- a/src/Builder/Stage/FactoryTrait.php +++ b/src/Builder/Stage/FactoryTrait.php @@ -18,7 +18,6 @@ use MongoDB\Builder\Expression\ArrayFieldPath; use MongoDB\Builder\Expression\FieldPath; use MongoDB\Builder\Expression\ResolvesToArray; -use MongoDB\Builder\Expression\ResolvesToBool; use MongoDB\Builder\Expression\ResolvesToObject; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Type\AccumulatorInterface; @@ -26,7 +25,6 @@ use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\ProjectionInterface; use MongoDB\Builder\Type\QueryInterface; -use MongoDB\Builder\Type\WindowInterface; use MongoDB\Model\BSONArray; use stdClass; @@ -482,10 +480,10 @@ public static function planCacheStats(): PlanCacheStatsStage * Reshapes each document in the stream, such as by adding new fields or removing existing fields. For each input document, outputs one document. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/ - * @param Document|ProjectionInterface|ResolvesToBool|Serializable|array|bool|int|stdClass ...$specification + * @param Document|ExpressionInterface|ProjectionInterface|Serializable|Type|array|bool|float|int|non-empty-string|null|stdClass ...$specification */ public static function project( - Document|Serializable|ResolvesToBool|ProjectionInterface|stdClass|array|bool|int ...$specification, + Document|Serializable|Type|ExpressionInterface|ProjectionInterface|stdClass|array|bool|float|int|null|string ...$specification, ): ProjectStage { return new ProjectStage(...$specification); @@ -587,16 +585,14 @@ public static function set(Type|ExpressionInterface|stdClass|array|bool|float|in * @param Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. * @param Document|Serializable|array|stdClass $output Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. * A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. - * @param Optional|Document|Serializable|WindowInterface|array|stdClass $window Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. */ public static function setWindowFields( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy, Document|Serializable|stdClass|array $sortBy, Document|Serializable|stdClass|array $output, - Optional|Document|Serializable|WindowInterface|stdClass|array $window = Optional::Undefined, ): SetWindowFieldsStage { - return new SetWindowFieldsStage($partitionBy, $sortBy, $output, $window); + return new SetWindowFieldsStage($partitionBy, $sortBy, $output); } /** diff --git a/src/Builder/Stage/ProjectStage.php b/src/Builder/Stage/ProjectStage.php index daf35d705..3c9e57909 100644 --- a/src/Builder/Stage/ProjectStage.php +++ b/src/Builder/Stage/ProjectStage.php @@ -10,8 +10,9 @@ use MongoDB\BSON\Document; use MongoDB\BSON\Serializable; -use MongoDB\Builder\Expression\ResolvesToBool; +use MongoDB\BSON\Type; use MongoDB\Builder\Type\Encode; +use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\ProjectionInterface; use MongoDB\Builder\Type\StageInterface; @@ -29,14 +30,14 @@ class ProjectStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var stdClass $specification */ + /** @var stdClass $specification */ public readonly stdClass $specification; /** - * @param Document|ProjectionInterface|ResolvesToBool|Serializable|array|bool|int|stdClass ...$specification + * @param Document|ExpressionInterface|ProjectionInterface|Serializable|Type|array|bool|float|int|non-empty-string|null|stdClass ...$specification */ public function __construct( - Document|Serializable|ResolvesToBool|ProjectionInterface|stdClass|array|bool|int ...$specification, + Document|Serializable|Type|ExpressionInterface|ProjectionInterface|stdClass|array|bool|float|int|null|string ...$specification, ) { if (\count($specification) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $specification, got %d.', 1, \count($specification))); diff --git a/src/Builder/Stage/SetWindowFieldsStage.php b/src/Builder/Stage/SetWindowFieldsStage.php index c93020d1d..400c1c795 100644 --- a/src/Builder/Stage/SetWindowFieldsStage.php +++ b/src/Builder/Stage/SetWindowFieldsStage.php @@ -14,9 +14,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\OperatorInterface; -use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\StageInterface; -use MongoDB\Builder\Type\WindowInterface; use stdClass; /** @@ -41,26 +39,20 @@ class SetWindowFieldsStage implements StageInterface, OperatorInterface */ public readonly Document|Serializable|stdClass|array $output; - /** @var Optional|Document|Serializable|WindowInterface|array|stdClass $window Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. */ - public readonly Optional|Document|Serializable|WindowInterface|stdClass|array $window; - /** * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. * @param Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. * @param Document|Serializable|array|stdClass $output Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. * A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. - * @param Optional|Document|Serializable|WindowInterface|array|stdClass $window Specifies the window boundaries and parameters. Window boundaries are inclusive. Default is an unbounded window, which includes all documents in the partition. */ public function __construct( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy, Document|Serializable|stdClass|array $sortBy, Document|Serializable|stdClass|array $output, - Optional|Document|Serializable|WindowInterface|stdClass|array $window = Optional::Undefined, ) { $this->partitionBy = $partitionBy; $this->sortBy = $sortBy; $this->output = $output; - $this->window = $window; } public function getOperator(): string diff --git a/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php b/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php index 8a392f00a..5d8a6b361 100644 --- a/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php +++ b/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php @@ -44,15 +44,15 @@ public function testUseInitArgsToVaryTheInitialStateByGroup(): void _id: object(city: Expression::fieldPath('city')), restaurants: Accumulator::accumulator( init: new Javascript('function (city, userProfileCity) { return { max: city === userProfileCity ? 3 : 1, restaurants: [] } }'), + accumulate: new Javascript('function (state, restaurantName) { if (state.restaurants.length < state.max) { state.restaurants.push(restaurantName); } return state; }'), + accumulateArgs: [Expression::fieldPath('name')], + merge: new Javascript('function (state1, state2) { return { max: state1.max, restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) } }'), + lang: 'js', initArgs: [ Expression::fieldPath('city'), 'Bettles', ], - accumulate: new Javascript('function (state, restaurantName) { if (state.restaurants.length < state.max) { state.restaurants.push(restaurantName); } return state; }'), - accumulateArgs: [Expression::fieldPath('name')], - merge: new Javascript('function (state1, state2) { return { max: state1.max, restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) } }'), finalize: new Javascript('function (state) { return state.restaurants }'), - lang: 'js', ), ), ); diff --git a/tests/Builder/Accumulator/FirstAccumulatorTest.php b/tests/Builder/Accumulator/FirstAccumulatorTest.php new file mode 100644 index 000000000..dc453af26 --- /dev/null +++ b/tests/Builder/Accumulator/FirstAccumulatorTest.php @@ -0,0 +1,55 @@ +assertSamePipeline(Pipelines::FirstUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + firstOrderTypeForState: Accumulator::outputWindow( + Accumulator::first(Expression::stringFieldPath('type')), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FirstUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/FirstNAccumulatorTest.php b/tests/Builder/Accumulator/FirstNAccumulatorTest.php new file mode 100644 index 000000000..58821d2dd --- /dev/null +++ b/tests/Builder/Accumulator/FirstNAccumulatorTest.php @@ -0,0 +1,41 @@ +assertSamePipeline(Pipelines::FirstNNullAndMissingValues, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/LastAccumulatorTest.php b/tests/Builder/Accumulator/LastAccumulatorTest.php new file mode 100644 index 000000000..427e180c4 --- /dev/null +++ b/tests/Builder/Accumulator/LastAccumulatorTest.php @@ -0,0 +1,55 @@ +assertSamePipeline(Pipelines::LastUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + lastOrderTypeForState: Accumulator::outputWindow( + Accumulator::last(Expression::stringFieldPath('type')), + documents: ['current', 'unbounded'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::LastUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/LastNAccumulatorTest.php b/tests/Builder/Accumulator/LastNAccumulatorTest.php new file mode 100644 index 000000000..9040b919a --- /dev/null +++ b/tests/Builder/Accumulator/LastNAccumulatorTest.php @@ -0,0 +1,101 @@ + Expression::arrayFieldPath('gameId')], + gamescores: Accumulator::lastN( + input: Expression::arrayFieldPath('score'), + n: Expression::cond( + if: Expression::eq( + Expression::arrayFieldPath('gameId'), + 'G2', + ), + then: 1, + else: 3, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::LastNComputingNBasedOnTheGroupKeyForGroup, $pipeline); + } + + public function testFindTheLastThreePlayerScoresForASingleGame(): void + { + $pipeline = new Pipeline( + Stage::match( + gameId: 'G1', + ), + Stage::group( + _id: Expression::fieldPath('gameId'), + lastThreeScores: Accumulator::lastN( + input: [ + Expression::arrayFieldPath('playerId'), + Expression::arrayFieldPath('score'), + ], + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::LastNFindTheLastThreePlayerScoresForASingleGame, $pipeline); + } + + public function testFindingTheLastThreePlayerScoresAcrossMultipleGames(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('gameId'), + playerId: Accumulator::lastN( + input: [ + Expression::arrayFieldPath('playerId'), + Expression::arrayFieldPath('score'), + ], + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::LastNFindingTheLastThreePlayerScoresAcrossMultipleGames, $pipeline); + } + + public function testUsingSortWithLastN(): void + { + $pipeline = new Pipeline( + Stage::sort(object( + score: -1, + )), + Stage::group( + _id: Expression::fieldPath('gameId'), + playerId: Accumulator::lastN( + input: [ + Expression::arrayFieldPath('playerId'), + Expression::arrayFieldPath('score'), + ], + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::LastNUsingSortWithLastN, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/Pipelines.php b/tests/Builder/Accumulator/Pipelines.php index e0ce83e9b..0057da9fa 100644 --- a/tests/Builder/Accumulator/Pipelines.php +++ b/tests/Builder/Accumulator/Pipelines.php @@ -145,4 +145,307 @@ enum Pipelines: string } ] JSON; + + /** + * Use in $group Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/#use-in--group-stage + */ + case FirstUseInGroupStage = <<<'JSON' + [ + { + "$sort": { + "item": { + "$numberInt": "1" + }, + "date": { + "$numberInt": "1" + } + } + }, + { + "$group": { + "_id": "$item", + "firstSale": { + "$first": "$date" + } + } + } + ] + JSON; + + /** + * Use in $setWindowFields Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/#use-in--setwindowfields-stage + */ + case FirstUseInSetWindowFieldsStage = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "firstOrderTypeForState": { + "$first": "$type", + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } + } + } + } + ] + JSON; + + /** + * Null and Missing Values + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#null-and-missing-values + */ + case FirstNNullAndMissingValues = <<<'JSON' + [ + { + "$documents": [ + { + "playerId": "PlayerA", + "gameId": "G1", + "score": { + "$numberInt": "1" + } + }, + { + "playerId": "PlayerB", + "gameId": "G1", + "score": { + "$numberInt": "2" + } + }, + { + "playerId": "PlayerC", + "gameId": "G1", + "score": { + "$numberInt": "3" + } + }, + { + "playerId": "PlayerD", + "gameId": "G1" + }, + { + "playerId": "PlayerE", + "gameId": "G1", + "score": null + } + ] + }, + { + "$group": { + "_id": "$gameId", + "firstFiveScores": { + "$firstN": { + "input": "$score", + "n": { + "$numberInt": "5" + } + } + } + } + } + ] + JSON; + + /** + * Use in $group Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/ + */ + case LastUseInGroupStage = <<<'JSON' + [ + { + "$sort": { + "item": { + "$numberInt": "1" + }, + "date": { + "$numberInt": "1" + } + } + }, + { + "$group": { + "_id": "$item", + "lastSalesDate": { + "$last": "$date" + } + } + } + ] + JSON; + + /** + * Use in $setWindowFields Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/#use-in--setwindowfields-stage + */ + case LastUseInSetWindowFieldsStage = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "lastOrderTypeForState": { + "$last": "$type", + "window": { + "documents": [ + "current", + "unbounded" + ] + } + } + } + } + } + ] + JSON; + + /** + * Find the Last Three Player Scores for a Single Game + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#find-the-last-three-player-scores-for-a-single-game + */ + case LastNFindTheLastThreePlayerScoresForASingleGame = <<<'JSON' + [ + { + "$match": { + "gameId": "G1" + } + }, + { + "$group": { + "_id": "$gameId", + "lastThreeScores": { + "$lastN": { + "input": [ + "$playerId", + "$score" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Finding the Last Three Player Scores Across Multiple Games + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#finding-the-last-three-player-scores-across-multiple-games + */ + case LastNFindingTheLastThreePlayerScoresAcrossMultipleGames = <<<'JSON' + [ + { + "$group": { + "_id": "$gameId", + "playerId": { + "$lastN": { + "input": [ + "$playerId", + "$score" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Using $sort With $lastN + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#using--sort-with--lastn + */ + case LastNUsingSortWithLastN = <<<'JSON' + [ + { + "$sort": { + "score": { + "$numberInt": "-1" + } + } + }, + { + "$group": { + "_id": "$gameId", + "playerId": { + "$lastN": { + "input": [ + "$playerId", + "$score" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Computing n Based on the Group Key for $group + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#computing-n-based-on-the-group-key-for--group + */ + case LastNComputingNBasedOnTheGroupKeyForGroup = <<<'JSON' + [ + { + "$group": { + "_id": { + "gameId": "$gameId" + }, + "gamescores": { + "$lastN": { + "input": "$score", + "n": { + "$cond": { + "if": { + "$eq": [ + "$gameId", + "G2" + ] + }, + "then": { + "$numberInt": "1" + }, + "else": { + "$numberInt": "3" + } + } + } + } + } + } + } + ] + JSON; } diff --git a/tests/Builder/Expression/FirstNOperatorTest.php b/tests/Builder/Expression/FirstNOperatorTest.php new file mode 100644 index 000000000..65ef27364 --- /dev/null +++ b/tests/Builder/Expression/FirstNOperatorTest.php @@ -0,0 +1,27 @@ +assertSamePipeline(Pipelines::FirstNExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/FirstOperatorTest.php b/tests/Builder/Expression/FirstOperatorTest.php new file mode 100644 index 000000000..10fab6cdc --- /dev/null +++ b/tests/Builder/Expression/FirstOperatorTest.php @@ -0,0 +1,27 @@ +assertSamePipeline(Pipelines::FirstUseInAddFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Expression/LastNOperatorTest.php b/tests/Builder/Expression/LastNOperatorTest.php new file mode 100644 index 000000000..485721135 --- /dev/null +++ b/tests/Builder/Expression/LastNOperatorTest.php @@ -0,0 +1,43 @@ +assertSamePipeline(Pipelines::LastNExample, $pipeline); + } + + public function testUsingLastNAsAnAggregationExpression(): void + { + $pipeline = new Pipeline( + Stage::documents([ + [ + 'array' => [10, 20, 30, 40], + ], + ]), + Stage::project( + lastThreeElements: Expression::lastN(input: Expression::arrayFieldPath('array'), n: 3), + ), + ); + + $this->assertSamePipeline(Pipelines::LastNUsingLastNAsAnAggregationExpression, $pipeline); + } +} diff --git a/tests/Builder/Expression/LastOperatorTest.php b/tests/Builder/Expression/LastOperatorTest.php new file mode 100644 index 000000000..7383819d6 --- /dev/null +++ b/tests/Builder/Expression/LastOperatorTest.php @@ -0,0 +1,27 @@ +assertSamePipeline(Pipelines::LastUseInAddFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 9c5401721..23a079ce5 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -10,4 +10,115 @@ enum Pipelines: string { + /** Use in $addFields Stage */ + case FirstUseInAddFieldsStage = <<<'JSON' + [ + { + "$addFields": { + "firstItem": { + "$first": "$items" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN-array-element/#example + */ + case FirstNExample = <<<'JSON' + [ + { + "$addFields": { + "firstScores": { + "$firstN": { + "n": { + "$numberInt": "3" + }, + "input": "$score" + } + } + } + } + ] + JSON; + + /** Use in $addFields Stage */ + case LastUseInAddFieldsStage = <<<'JSON' + [ + { + "$addFields": { + "lastItem": { + "$last": "$items" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN-array-element/#example + */ + case LastNExample = <<<'JSON' + [ + { + "$addFields": { + "lastScores": { + "$lastN": { + "n": { + "$numberInt": "3" + }, + "input": "$score" + } + } + } + } + ] + JSON; + + /** + * Using $lastN as an Aggregation Expression + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#using--lastn-as-an-aggregation-expression + */ + case LastNUsingLastNAsAnAggregationExpression = <<<'JSON' + [ + { + "$documents": [ + { + "array": [ + { + "$numberInt": "10" + }, + { + "$numberInt": "20" + }, + { + "$numberInt": "30" + }, + { + "$numberInt": "40" + } + ] + } + ] + }, + { + "$project": { + "lastThreeElements": { + "$lastN": { + "input": "$array", + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; } From 29807eda8f955302681172acab1894d5c3c4c6e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 15 Nov 2023 11:38:00 +0100 Subject: [PATCH 17/95] Reduce CI matrix as we don't use MongoDB server (#16) --- .github/workflows/tests.yml | 26 -------------------------- generator/composer.json | 5 ++--- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c1d42b34b..8ad4a39cb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,32 +34,6 @@ jobs: - "mongodb/mongo-php-driver@master" topology: - "server" - include: - - os: "ubuntu-20.04" - php-version: "8.1" - mongodb-version: "6.0" - driver-version: "mongodb/mongo-php-driver@master" - topology: "replica_set" - - os: "ubuntu-20.04" - php-version: "8.1" - mongodb-version: "6.0" - driver-version: "mongodb/mongo-php-driver@master" - topology: "sharded_cluster" - - os: "ubuntu-20.04" - php-version: "8.1" - mongodb-version: "5.0" - driver-version: "mongodb/mongo-php-driver@master" - topology: "server" - - os: "ubuntu-20.04" - php-version: "8.1" - mongodb-version: "4.4" - driver-version: "mongodb/mongo-php-driver@master" - topology: "replica_set" - - os: "ubuntu-20.04" - php-version: "8.1" - mongodb-version: "4.4" - driver-version: "mongodb/mongo-php-driver@master" - topology: "sharded_cluster" steps: - name: "Checkout" diff --git a/generator/composer.json b/generator/composer.json index d03eb2eac..1416655c4 100644 --- a/generator/composer.json +++ b/generator/composer.json @@ -14,9 +14,8 @@ }, "require": { "php": ">=8.1", - "ext-mongodb": "^1.16.0", - "mongodb/builder": "@dev", - "mongodb/mongodb": "^1.17.0@dev", + "ext-mongodb": "^1.17.0", + "mongodb/mongodb": "^1.17.0", "nette/php-generator": "^4", "nikic/php-parser": "^4.17", "symfony/console": "^6.3|^7.0", From 48353b44f72cd175df78ff0035614ca5235ff129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 20 Nov 2023 20:48:06 +0100 Subject: [PATCH 18/95] Add `$all` and `$elemMatch` field query operator tests (#17) * Add $all and $elemMatch query operator tests * Accept a single Query operator in $elemMatch * Remove variadic argument for $elemMatch operator to prevent ambiquity --- generator/config/projection/elemMatch.yaml | 2 +- generator/config/query/all.yaml | 31 +++- generator/config/query/elemMatch.yaml | 57 +++++- src/Builder/Projection/ElemMatchOperator.php | 2 +- src/Builder/Query.php | 9 + src/Builder/Query/AllOperator.php | 6 +- src/Builder/Query/ElemMatchOperator.php | 15 +- src/Builder/Query/FactoryTrait.php | 12 +- tests/Builder/Query/AllOperatorTest.php | 51 ++++++ tests/Builder/Query/ElemMatchOperatorTest.php | 92 ++++++++++ tests/Builder/Query/Pipelines.php | 173 ++++++++++++++++++ 11 files changed, 433 insertions(+), 17 deletions(-) create mode 100644 tests/Builder/Query/AllOperatorTest.php create mode 100644 tests/Builder/Query/ElemMatchOperatorTest.php diff --git a/generator/config/projection/elemMatch.yaml b/generator/config/projection/elemMatch.yaml index b309c5a9e..32f6ccd71 100644 --- a/generator/config/projection/elemMatch.yaml +++ b/generator/config/projection/elemMatch.yaml @@ -3,7 +3,7 @@ name: $elemMatch link: 'https://www.mongodb.com/docs/manual/reference/operator/projection/elemMatch/' type: - projection -encode: object +encode: single description: | Projects the first element in an array that matches the specified $elemMatch condition. arguments: diff --git a/generator/config/query/all.yaml b/generator/config/query/all.yaml index 0a50e2091..868e205e2 100644 --- a/generator/config/query/all.yaml +++ b/generator/config/query/all.yaml @@ -10,5 +10,34 @@ arguments: - name: value type: - - any + - fieldQuery variadic: array +tests: + - + name: 'Use $all to Match Values' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/all/#use--all-to-match-values' + pipeline: + - + $match: + tags: + $all: + - 'appliance' + - 'school' + - 'book' + - + name: 'Use $all with $elemMatch' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/all/#use--all-with--elemmatch' + pipeline: + - + $match: + qty: + $all: + - + $elemMatch: + size: 'M' + num: + $gt: 50 + - + $elemMatch: + num: 100 + color: 'green' diff --git a/generator/config/query/elemMatch.yaml b/generator/config/query/elemMatch.yaml index 2b9866bce..95db9572e 100644 --- a/generator/config/query/elemMatch.yaml +++ b/generator/config/query/elemMatch.yaml @@ -3,7 +3,7 @@ name: $elemMatch link: 'https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/' type: - fieldQuery -encode: object +encode: single description: | The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria. arguments: @@ -11,3 +11,58 @@ arguments: name: query type: - query + - fieldQuery +tests: + - + name: 'Element Match' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/#element-match' + pipeline: + - + $match: + results: + $elemMatch: + $gte: 80 + $lt: 85 + - + name: 'Array of Embedded Documents' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/#array-of-embedded-documents' + pipeline: + - + $match: + results: + $elemMatch: + product: 'xyz' + score: + $gte: 8 + - + name: 'Single Query Condition' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/#single-query-condition' + pipeline: + - + $match: + results: + $elemMatch: + product: + $ne: 'xyz' + - + name: 'Using $or with $elemMatch' + pipeline: + - + $match: + game: + $elemMatch: + $or: + - + score: + $gt: 10 + - + score: + $lt: 5 + - + name: 'Single field operator' + pipeline: + - + $match: + results: + $elemMatch: + $gt: 10 diff --git a/src/Builder/Projection/ElemMatchOperator.php b/src/Builder/Projection/ElemMatchOperator.php index 03daadcc8..3d33ae52d 100644 --- a/src/Builder/Projection/ElemMatchOperator.php +++ b/src/Builder/Projection/ElemMatchOperator.php @@ -23,7 +23,7 @@ */ class ElemMatchOperator implements ProjectionInterface, OperatorInterface { - public const ENCODE = Encode::Object; + public const ENCODE = Encode::Single; /** @var QueryInterface|array $query */ public readonly QueryInterface|array $query; diff --git a/src/Builder/Query.php b/src/Builder/Query.php index 2d8f5c015..06a78e705 100644 --- a/src/Builder/Query.php +++ b/src/Builder/Query.php @@ -7,6 +7,7 @@ use MongoDB\BSON\Regex; use MongoDB\BSON\Type; use MongoDB\Builder\Query\RegexOperator; +use MongoDB\Builder\Type\CombinedFieldQuery; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Builder\Type\QueryObject; @@ -40,6 +41,14 @@ public static function regex(Regex|string $regex, string|null $flags = null): Re return self::generatedRegex($regex); } + /** + * Combine multiple field query operators that apply to a same field. + */ + public static function fieldQuery(FieldQueryInterface|Type|stdClass|array|bool|float|int|string|null ...$query): FieldQueryInterface + { + return new CombinedFieldQuery($query); + } + public static function query(QueryInterface|FieldQueryInterface|Type|stdClass|array|bool|float|int|string|null ...$query): QueryInterface { return QueryObject::create($query); diff --git a/src/Builder/Query/AllOperator.php b/src/Builder/Query/AllOperator.php index b9a87565a..537f74ffa 100644 --- a/src/Builder/Query/AllOperator.php +++ b/src/Builder/Query/AllOperator.php @@ -26,14 +26,14 @@ class AllOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $value */ + /** @var list $value */ public readonly array $value; /** - * @param Type|array|bool|float|int|non-empty-string|null|stdClass ...$value + * @param FieldQueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$value * @no-named-arguments */ - public function __construct(Type|stdClass|array|bool|float|int|null|string ...$value) + public function __construct(Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string ...$value) { if (\count($value) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $value, got %d.', 1, \count($value))); diff --git a/src/Builder/Query/ElemMatchOperator.php b/src/Builder/Query/ElemMatchOperator.php index 9b0054142..97a02c32d 100644 --- a/src/Builder/Query/ElemMatchOperator.php +++ b/src/Builder/Query/ElemMatchOperator.php @@ -8,11 +8,13 @@ namespace MongoDB\Builder\Query; +use MongoDB\BSON\Type; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Builder\Type\QueryObject; +use stdClass; use function is_array; @@ -23,16 +25,17 @@ */ class ElemMatchOperator implements FieldQueryInterface, OperatorInterface { - public const ENCODE = Encode::Object; + public const ENCODE = Encode::Single; - /** @var QueryInterface|array $query */ - public readonly QueryInterface|array $query; + /** @var FieldQueryInterface|QueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $query */ + public readonly Type|FieldQueryInterface|QueryInterface|stdClass|array|bool|float|int|null|string $query; /** - * @param QueryInterface|array $query + * @param FieldQueryInterface|QueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $query */ - public function __construct(QueryInterface|array $query) - { + public function __construct( + Type|FieldQueryInterface|QueryInterface|stdClass|array|bool|float|int|null|string $query, + ) { if (is_array($query)) { $query = QueryObject::create($query); } diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index 5ed63b815..bef68e66b 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -35,9 +35,11 @@ trait FactoryTrait * * @see https://www.mongodb.com/docs/manual/reference/operator/query/all/ * @no-named-arguments - * @param Type|array|bool|float|int|non-empty-string|null|stdClass ...$value + * @param FieldQueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$value */ - public static function all(Type|stdClass|array|bool|float|int|null|string ...$value): AllOperator + public static function all( + Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string ...$value, + ): AllOperator { return new AllOperator(...$value); } @@ -146,9 +148,11 @@ public static function comment(string $comment): CommentOperator * The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/ - * @param QueryInterface|array $query + * @param FieldQueryInterface|QueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $query */ - public static function elemMatch(QueryInterface|array $query): ElemMatchOperator + public static function elemMatch( + Type|FieldQueryInterface|QueryInterface|stdClass|array|bool|float|int|null|string $query, + ): ElemMatchOperator { return new ElemMatchOperator($query); } diff --git a/tests/Builder/Query/AllOperatorTest.php b/tests/Builder/Query/AllOperatorTest.php new file mode 100644 index 000000000..ce0dbe0c8 --- /dev/null +++ b/tests/Builder/Query/AllOperatorTest.php @@ -0,0 +1,51 @@ +assertSamePipeline(Pipelines::AllUseAllToMatchValues, $pipeline); + } + + public function testUseAllWithElemMatch(): void + { + $pipeline = new Pipeline( + Stage::match( + qty: Query::all( + Query::elemMatch( + Query::query( + size: 'M', + num: Query::gt(50), + ), + ), + Query::elemMatch( + Query::query( + num: 100, + color: 'green', + ), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::AllUseAllWithElemMatch, $pipeline); + } +} diff --git a/tests/Builder/Query/ElemMatchOperatorTest.php b/tests/Builder/Query/ElemMatchOperatorTest.php new file mode 100644 index 000000000..b179df446 --- /dev/null +++ b/tests/Builder/Query/ElemMatchOperatorTest.php @@ -0,0 +1,92 @@ +assertSamePipeline(Pipelines::ElemMatchArrayOfEmbeddedDocuments, $pipeline); + } + + public function testElementMatch(): void + { + $pipeline = new Pipeline( + Stage::match( + results: Query::elemMatch( + Query::fieldQuery( + Query::gte(80), + Query::lt(85), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ElemMatchElementMatch, $pipeline); + } + + public function testSingleFieldOperator(): void + { + $pipeline = new Pipeline( + Stage::match( + results: Query::elemMatch( + Query::gt(10), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ElemMatchSingleFieldOperator, $pipeline); + } + + public function testSingleQueryCondition(): void + { + $pipeline = new Pipeline( + Stage::match( + results: Query::elemMatch( + Query::query( + product: Query::ne('xyz'), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ElemMatchSingleQueryCondition, $pipeline); + } + + public function testUsingOrWithElemMatch(): void + { + $pipeline = new Pipeline( + Stage::match( + game: Query::elemMatch( + Query::or( + Query::query(score: Query::gt(10)), + Query::query(score: Query::lt(5)), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ElemMatchUsingOrWithElemMatch, $pipeline); + } +} diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 3ea8c1e52..ee49d656c 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -10,6 +10,179 @@ enum Pipelines: string { + /** + * Use $all to Match Values + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/all/#use--all-to-match-values + */ + case AllUseAllToMatchValues = <<<'JSON' + [ + { + "$match": { + "tags": { + "$all": [ + "appliance", + "school", + "book" + ] + } + } + } + ] + JSON; + + /** + * Use $all with $elemMatch + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/all/#use--all-with--elemmatch + */ + case AllUseAllWithElemMatch = <<<'JSON' + [ + { + "$match": { + "qty": { + "$all": [ + { + "$elemMatch": { + "size": "M", + "num": { + "$gt": { + "$numberInt": "50" + } + } + } + }, + { + "$elemMatch": { + "num": { + "$numberInt": "100" + }, + "color": "green" + } + } + ] + } + } + } + ] + JSON; + + /** + * Element Match + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/#element-match + */ + case ElemMatchElementMatch = <<<'JSON' + [ + { + "$match": { + "results": { + "$elemMatch": { + "$gte": { + "$numberInt": "80" + }, + "$lt": { + "$numberInt": "85" + } + } + } + } + } + ] + JSON; + + /** + * Array of Embedded Documents + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/#array-of-embedded-documents + */ + case ElemMatchArrayOfEmbeddedDocuments = <<<'JSON' + [ + { + "$match": { + "results": { + "$elemMatch": { + "product": "xyz", + "score": { + "$gte": { + "$numberInt": "8" + } + } + } + } + } + } + ] + JSON; + + /** + * Single Query Condition + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/#single-query-condition + */ + case ElemMatchSingleQueryCondition = <<<'JSON' + [ + { + "$match": { + "results": { + "$elemMatch": { + "product": { + "$ne": "xyz" + } + } + } + } + } + ] + JSON; + + /** Using $or with $elemMatch */ + case ElemMatchUsingOrWithElemMatch = <<<'JSON' + [ + { + "$match": { + "game": { + "$elemMatch": { + "$or": [ + { + "score": { + "$gt": { + "$numberInt": "10" + } + } + }, + { + "score": { + "$lt": { + "$numberInt": "5" + } + } + } + ] + } + } + } + } + ] + JSON; + + /** Single field operator */ + case ElemMatchSingleFieldOperator = <<<'JSON' + [ + { + "$match": { + "results": { + "$elemMatch": { + "$gt": { + "$numberInt": "10" + } + } + } + } + } + ] + JSON; + /** * Perform a LIKE Match * From b0886f8385291b1c560ed69d6b305f71d2ffd12f Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 22 Dec 2023 13:01:15 +0100 Subject: [PATCH 19/95] Various improvements (#19) --- generator/config/expression/add.yaml | 30 +- generator/config/expression/mergeObjects.yaml | 39 +++ generator/config/expression/subtract.yaml | 42 ++- generator/config/expression/zip.yaml | 55 ++++ generator/config/query/expr.yaml | 36 ++- src/Builder/Expression/AddOperator.php | 4 +- src/Builder/Expression/FactoryTrait.php | 22 +- .../Expression/MergeObjectsOperator.php | 53 ++++ src/Builder/Expression/SubtractOperator.php | 2 +- src/Builder/Expression/ZipOperator.php | 17 +- src/Builder/Query/ExprOperator.php | 4 +- tests/Builder/Expression/AddOperatorTest.php | 46 +++ .../Expression/MergeObjectsOperatorTest.php | 39 +++ tests/Builder/Expression/Pipelines.php | 270 ++++++++++++++++++ .../Expression/SubtractOperatorTest.php | 64 +++++ tests/Builder/Expression/ZipOperatorTest.php | 58 ++++ tests/Builder/Query/ExprOperatorTest.php | 58 ++++ tests/Builder/Query/Pipelines.php | 69 +++++ 18 files changed, 887 insertions(+), 21 deletions(-) create mode 100644 generator/config/expression/mergeObjects.yaml create mode 100644 src/Builder/Expression/MergeObjectsOperator.php create mode 100644 tests/Builder/Expression/AddOperatorTest.php create mode 100644 tests/Builder/Expression/MergeObjectsOperatorTest.php create mode 100644 tests/Builder/Expression/SubtractOperatorTest.php create mode 100644 tests/Builder/Expression/ZipOperatorTest.php create mode 100644 tests/Builder/Query/ExprOperatorTest.php diff --git a/generator/config/expression/add.yaml b/generator/config/expression/add.yaml index cd3c70ae2..fa23253d0 100644 --- a/generator/config/expression/add.yaml +++ b/generator/config/expression/add.yaml @@ -2,9 +2,12 @@ name: $add link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/add/' type: - - resolvesToNumber + - resolvesToInt + - resolvesToLong + - resolvesToDouble + - resolvesToDecimal - resolvesToDate -encode: array +encode: single description: | Adds numbers to return the sum, or adds numbers and a date to return a new date. If adding numbers and a date, treats the numbers as milliseconds. Accepts any number of argument expressions, but at most, one expression can resolve to a date. arguments: @@ -16,3 +19,26 @@ arguments: variadic: array description: | The arguments can be any valid expression as long as they resolve to either all numbers or to numbers and a date. +tests: + - + name: 'Add Numbers' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/add/#add-numbers' + pipeline: + - + $project: + item: 1 + total: + $add: + - '$price' + - '$fee' + - + name: 'Perform Addition on a Date' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/add/#perform-addition-on-a-date' + pipeline: + - + $project: + item: 1 + billing_date: + $add: + - '$date' + - 259200000 diff --git a/generator/config/expression/mergeObjects.yaml b/generator/config/expression/mergeObjects.yaml new file mode 100644 index 000000000..928166852 --- /dev/null +++ b/generator/config/expression/mergeObjects.yaml @@ -0,0 +1,39 @@ +# $schema: ../schema.json +name: $mergeObjects +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/' +type: + - resolvesToObject +encode: single +description: | + Combines multiple documents into a single document. +arguments: + - + name: document + type: + - resolvesToObject + variadic: array + description: | + Any valid expression that resolves to a document. +tests: + - + name: '$mergeObjects' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/#-mergeobjects' + pipeline: + - + $lookup: + from: 'items' + localField: 'item' + foreignField: 'item' + as: 'fromItems' + - + $replaceRoot: + newRoot: + $mergeObjects: + - + $arrayElemAt: + - '$fromItems' + - 0 + - '$$ROOT' + - + $project: + fromItems: 0 diff --git a/generator/config/expression/subtract.yaml b/generator/config/expression/subtract.yaml index 66b00e501..b6db65ac9 100644 --- a/generator/config/expression/subtract.yaml +++ b/generator/config/expression/subtract.yaml @@ -2,7 +2,10 @@ name: $subtract link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/' type: - - resolvesToNumber + - resolvesToInt + - resolvesToLong + - resolvesToDouble + - resolvesToDecimal - resolvesToDate encode: array description: | @@ -18,3 +21,40 @@ arguments: type: - resolvesToNumber - resolvesToDate +tests: + - + name: 'Subtract Numbers' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/#subtract-numbers' + pipeline: + - + $project: + item: 1 + total: + $subtract: + - + $add: + - '$price' + - '$fee' + - '$discount' + - + name: 'Subtract Two Dates' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/#subtract-two-dates' + pipeline: + - + $project: + item: 1 + dateDifference: + $subtract: + - '$$NOW' + - '$date' + - + name: 'Subtract Milliseconds from a Date' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/#subtract-milliseconds-from-a-date' + pipeline: + - + $project: + item: 1 + dateDifference: + $subtract: + - '$date' + - 300000 diff --git a/generator/config/expression/zip.yaml b/generator/config/expression/zip.yaml index 3b8697037..76402dac5 100644 --- a/generator/config/expression/zip.yaml +++ b/generator/config/expression/zip.yaml @@ -19,6 +19,7 @@ arguments: name: useLongestLength type: - bool + optional: true description: | A boolean which specifies whether the length of the longest array determines the number of arrays in the output array. The default value is false: the shortest array length determines the number of arrays in the output array. @@ -26,7 +27,61 @@ arguments: name: defaults type: - array + optional: true description: | An array of default element values to use if the input arrays have different lengths. You must specify useLongestLength: true along with this field, or else $zip will return an error. If useLongestLength: true but defaults is empty or not specified, $zip uses null as the default value. If specifying a non-empty defaults, you must specify a default for each input array or else $zip will return an error. +tests: + - + name: 'Matrix Transposition' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/zip/#matrix-transposition' + pipeline: + - + $project: + _id: false + transposed: + $zip: + inputs: + - + $arrayElemAt: + - '$matrix' + - 0 + - + $arrayElemAt: + - '$matrix' + - 1 + - + $arrayElemAt: + - '$matrix' + - 2 + - + name: 'Filtering and Preserving Indexes' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/zip/#filtering-and-preserving-indexes' + pipeline: + - + $project: + _id: false + pages: + $filter: + input: + $zip: + inputs: + - '$pages' + - + $range: + - 0 + - + $size: '$pages' + as: 'pageWithIndex' + cond: + $let: + vars: + page: + $arrayElemAt: + - '$$pageWithIndex' + - 0 + in: + $gte: + - '$$page.reviews' + - 1 diff --git a/generator/config/query/expr.yaml b/generator/config/query/expr.yaml index 04f2cf449..320c84507 100644 --- a/generator/config/query/expr.yaml +++ b/generator/config/query/expr.yaml @@ -2,7 +2,7 @@ name: $expr link: 'https://www.mongodb.com/docs/manual/reference/operator/query/expr/' type: - - fieldQuery + - query encode: single description: | Allows use of aggregation expressions within the query language. @@ -11,3 +11,37 @@ arguments: name: expression type: - expression +tests: + - + name: 'Compare Two Fields from A Single Document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/expr/#compare-two-fields-from-a-single-document' + pipeline: + - + $match: + $expr: + $gt: + - '$spent' + - '$budget' + - + name: 'Using $expr With Conditional Statements' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/expr/#using--expr-with-conditional-statements' + pipeline: + - + $match: + $expr: + $lt: + - + $cond: + if: + $gte: + - '$qty' + - 100 + then: + $multiply: + - '$price' + - 0.5 + else: + $multiply: + - '$price' + - 0.75 + - 5 diff --git a/src/Builder/Expression/AddOperator.php b/src/Builder/Expression/AddOperator.php index 82e0e59cc..eb36b6ea9 100644 --- a/src/Builder/Expression/AddOperator.php +++ b/src/Builder/Expression/AddOperator.php @@ -22,9 +22,9 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/add/ */ -class AddOperator implements ResolvesToNumber, ResolvesToDate, OperatorInterface +class AddOperator implements ResolvesToInt, ResolvesToLong, ResolvesToDouble, ResolvesToDecimal, ResolvesToDate, OperatorInterface { - public const ENCODE = Encode::Array; + public const ENCODE = Encode::Single; /** @var list $expression The arguments can be any valid expression as long as they resolve to either all numbers or to numbers and a date. */ public readonly array $expression; diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 066df2544..d95083b71 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -1259,6 +1259,20 @@ public static function median(Decimal128|Int64|ResolvesToNumber|float|int $input return new MedianOperator($input, $method); } + /** + * Combines multiple documents into a single document. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/ + * @no-named-arguments + * @param Document|ResolvesToObject|Serializable|array|stdClass ...$document Any valid expression that resolves to a document. + */ + public static function mergeObjects( + Document|Serializable|ResolvesToObject|stdClass|array ...$document, + ): MergeObjectsOperator + { + return new MergeObjectsOperator(...$document); + } + /** * Access available per-document metadata related to the aggregation operation. * @@ -2331,16 +2345,16 @@ public static function year( * @param BSONArray|PackedArray|ResolvesToArray|array $inputs An array of expressions that resolve to arrays. The elements of these input arrays combine to form the arrays of the output array. * If any of the inputs arrays resolves to a value of null or refers to a missing field, $zip returns null. * If any of the inputs arrays does not resolve to an array or null nor refers to a missing field, $zip returns an error. - * @param bool $useLongestLength A boolean which specifies whether the length of the longest array determines the number of arrays in the output array. + * @param Optional|bool $useLongestLength A boolean which specifies whether the length of the longest array determines the number of arrays in the output array. * The default value is false: the shortest array length determines the number of arrays in the output array. - * @param BSONArray|PackedArray|array $defaults An array of default element values to use if the input arrays have different lengths. You must specify useLongestLength: true along with this field, or else $zip will return an error. + * @param Optional|BSONArray|PackedArray|array $defaults An array of default element values to use if the input arrays have different lengths. You must specify useLongestLength: true along with this field, or else $zip will return an error. * If useLongestLength: true but defaults is empty or not specified, $zip uses null as the default value. * If specifying a non-empty defaults, you must specify a default for each input array or else $zip will return an error. */ public static function zip( PackedArray|ResolvesToArray|BSONArray|array $inputs, - bool $useLongestLength, - PackedArray|BSONArray|array $defaults, + Optional|bool $useLongestLength = Optional::Undefined, + Optional|PackedArray|BSONArray|array $defaults = Optional::Undefined, ): ZipOperator { return new ZipOperator($inputs, $useLongestLength, $defaults); diff --git a/src/Builder/Expression/MergeObjectsOperator.php b/src/Builder/Expression/MergeObjectsOperator.php new file mode 100644 index 000000000..e1e170f3c --- /dev/null +++ b/src/Builder/Expression/MergeObjectsOperator.php @@ -0,0 +1,53 @@ + $document Any valid expression that resolves to a document. */ + public readonly array $document; + + /** + * @param Document|ResolvesToObject|Serializable|array|stdClass ...$document Any valid expression that resolves to a document. + * @no-named-arguments + */ + public function __construct(Document|Serializable|ResolvesToObject|stdClass|array ...$document) + { + if (\count($document) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $document, got %d.', 1, \count($document))); + } + + if (! array_is_list($document)) { + throw new InvalidArgumentException('Expected $document arguments to be a list (array), named arguments are not supported'); + } + + $this->document = $document; + } + + public function getOperator(): string + { + return '$mergeObjects'; + } +} diff --git a/src/Builder/Expression/SubtractOperator.php b/src/Builder/Expression/SubtractOperator.php index d707c8153..b8cf78387 100644 --- a/src/Builder/Expression/SubtractOperator.php +++ b/src/Builder/Expression/SubtractOperator.php @@ -19,7 +19,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/ */ -class SubtractOperator implements ResolvesToNumber, ResolvesToDate, OperatorInterface +class SubtractOperator implements ResolvesToInt, ResolvesToLong, ResolvesToDouble, ResolvesToDecimal, ResolvesToDate, OperatorInterface { public const ENCODE = Encode::Array; diff --git a/src/Builder/Expression/ZipOperator.php b/src/Builder/Expression/ZipOperator.php index 4333e0c75..0c23bbd4f 100644 --- a/src/Builder/Expression/ZipOperator.php +++ b/src/Builder/Expression/ZipOperator.php @@ -11,6 +11,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\Optional; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -34,32 +35,32 @@ class ZipOperator implements ResolvesToArray, OperatorInterface public readonly PackedArray|ResolvesToArray|BSONArray|array $inputs; /** - * @var bool $useLongestLength A boolean which specifies whether the length of the longest array determines the number of arrays in the output array. + * @var Optional|bool $useLongestLength A boolean which specifies whether the length of the longest array determines the number of arrays in the output array. * The default value is false: the shortest array length determines the number of arrays in the output array. */ - public readonly bool $useLongestLength; + public readonly Optional|bool $useLongestLength; /** - * @var BSONArray|PackedArray|array $defaults An array of default element values to use if the input arrays have different lengths. You must specify useLongestLength: true along with this field, or else $zip will return an error. + * @var Optional|BSONArray|PackedArray|array $defaults An array of default element values to use if the input arrays have different lengths. You must specify useLongestLength: true along with this field, or else $zip will return an error. * If useLongestLength: true but defaults is empty or not specified, $zip uses null as the default value. * If specifying a non-empty defaults, you must specify a default for each input array or else $zip will return an error. */ - public readonly PackedArray|BSONArray|array $defaults; + public readonly Optional|PackedArray|BSONArray|array $defaults; /** * @param BSONArray|PackedArray|ResolvesToArray|array $inputs An array of expressions that resolve to arrays. The elements of these input arrays combine to form the arrays of the output array. * If any of the inputs arrays resolves to a value of null or refers to a missing field, $zip returns null. * If any of the inputs arrays does not resolve to an array or null nor refers to a missing field, $zip returns an error. - * @param bool $useLongestLength A boolean which specifies whether the length of the longest array determines the number of arrays in the output array. + * @param Optional|bool $useLongestLength A boolean which specifies whether the length of the longest array determines the number of arrays in the output array. * The default value is false: the shortest array length determines the number of arrays in the output array. - * @param BSONArray|PackedArray|array $defaults An array of default element values to use if the input arrays have different lengths. You must specify useLongestLength: true along with this field, or else $zip will return an error. + * @param Optional|BSONArray|PackedArray|array $defaults An array of default element values to use if the input arrays have different lengths. You must specify useLongestLength: true along with this field, or else $zip will return an error. * If useLongestLength: true but defaults is empty or not specified, $zip uses null as the default value. * If specifying a non-empty defaults, you must specify a default for each input array or else $zip will return an error. */ public function __construct( PackedArray|ResolvesToArray|BSONArray|array $inputs, - bool $useLongestLength, - PackedArray|BSONArray|array $defaults, + Optional|bool $useLongestLength = Optional::Undefined, + Optional|PackedArray|BSONArray|array $defaults = Optional::Undefined, ) { if (is_array($inputs) && ! array_is_list($inputs)) { throw new InvalidArgumentException('Expected $inputs argument to be a list, got an associative array.'); diff --git a/src/Builder/Query/ExprOperator.php b/src/Builder/Query/ExprOperator.php index 0710b1404..98f35a751 100644 --- a/src/Builder/Query/ExprOperator.php +++ b/src/Builder/Query/ExprOperator.php @@ -11,8 +11,8 @@ use MongoDB\BSON\Type; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\ExpressionInterface; -use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\QueryInterface; use stdClass; /** @@ -20,7 +20,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/query/expr/ */ -class ExprOperator implements FieldQueryInterface, OperatorInterface +class ExprOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; diff --git a/tests/Builder/Expression/AddOperatorTest.php b/tests/Builder/Expression/AddOperatorTest.php new file mode 100644 index 000000000..b6fd43935 --- /dev/null +++ b/tests/Builder/Expression/AddOperatorTest.php @@ -0,0 +1,46 @@ +assertSamePipeline(Pipelines::AddAddNumbers, $pipeline); + } + + public function testPerformAdditionOnADate(): void + { + $pipeline = new Pipeline( + Stage::project( + item: 1, + billing_date: Expression::add( + Expression::fieldPath('date'), + 3 * 24 * 60 * 60000, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::AddPerformAdditionOnADate, $pipeline); + } +} diff --git a/tests/Builder/Expression/MergeObjectsOperatorTest.php b/tests/Builder/Expression/MergeObjectsOperatorTest.php new file mode 100644 index 000000000..0ca4f313f --- /dev/null +++ b/tests/Builder/Expression/MergeObjectsOperatorTest.php @@ -0,0 +1,39 @@ +assertSamePipeline(Pipelines::MergeObjectsMergeObjects, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 23a079ce5..cff869627 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -10,6 +10,54 @@ enum Pipelines: string { + /** + * Add Numbers + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/add/#add-numbers + */ + case AddAddNumbers = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "total": { + "$add": [ + "$price", + "$fee" + ] + } + } + } + ] + JSON; + + /** + * Perform Addition on a Date + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/add/#perform-addition-on-a-date + */ + case AddPerformAdditionOnADate = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "billing_date": { + "$add": [ + "$date", + { + "$numberInt": "259200000" + } + ] + } + } + } + ] + JSON; + /** Use in $addFields Stage */ case FirstUseInAddFieldsStage = <<<'JSON' [ @@ -121,4 +169,226 @@ enum Pipelines: string } ] JSON; + + /** + * $mergeObjects + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/#-mergeobjects + */ + case MergeObjectsMergeObjects = <<<'JSON' + [ + { + "$lookup": { + "from": "items", + "localField": "item", + "foreignField": "item", + "as": "fromItems" + } + }, + { + "$replaceRoot": { + "newRoot": { + "$mergeObjects": [ + { + "$arrayElemAt": [ + "$fromItems", + { + "$numberInt": "0" + } + ] + }, + "$$ROOT" + ] + } + } + }, + { + "$project": { + "fromItems": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Subtract Numbers + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/#subtract-numbers + */ + case SubtractSubtractNumbers = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "total": { + "$subtract": [ + { + "$add": [ + "$price", + "$fee" + ] + }, + "$discount" + ] + } + } + } + ] + JSON; + + /** + * Subtract Two Dates + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/#subtract-two-dates + */ + case SubtractSubtractTwoDates = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "dateDifference": { + "$subtract": [ + "$$NOW", + "$date" + ] + } + } + } + ] + JSON; + + /** + * Subtract Milliseconds from a Date + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/subtract/#subtract-milliseconds-from-a-date + */ + case SubtractSubtractMillisecondsFromADate = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "dateDifference": { + "$subtract": [ + "$date", + { + "$numberInt": "300000" + } + ] + } + } + } + ] + JSON; + + /** + * Matrix Transposition + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/zip/#matrix-transposition + */ + case ZipMatrixTransposition = <<<'JSON' + [ + { + "$project": { + "_id": false, + "transposed": { + "$zip": { + "inputs": [ + { + "$arrayElemAt": [ + "$matrix", + { + "$numberInt": "0" + } + ] + }, + { + "$arrayElemAt": [ + "$matrix", + { + "$numberInt": "1" + } + ] + }, + { + "$arrayElemAt": [ + "$matrix", + { + "$numberInt": "2" + } + ] + } + ] + } + } + } + } + ] + JSON; + + /** + * Filtering and Preserving Indexes + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/zip/#filtering-and-preserving-indexes + */ + case ZipFilteringAndPreservingIndexes = <<<'JSON' + [ + { + "$project": { + "_id": false, + "pages": { + "$filter": { + "input": { + "$zip": { + "inputs": [ + "$pages", + { + "$range": [ + { + "$numberInt": "0" + }, + { + "$size": "$pages" + } + ] + } + ] + } + }, + "as": "pageWithIndex", + "cond": { + "$let": { + "vars": { + "page": { + "$arrayElemAt": [ + "$$pageWithIndex", + { + "$numberInt": "0" + } + ] + } + }, + "in": { + "$gte": [ + "$$page.reviews", + { + "$numberInt": "1" + } + ] + } + } + } + } + } + } + } + ] + JSON; } diff --git a/tests/Builder/Expression/SubtractOperatorTest.php b/tests/Builder/Expression/SubtractOperatorTest.php new file mode 100644 index 000000000..95b691a95 --- /dev/null +++ b/tests/Builder/Expression/SubtractOperatorTest.php @@ -0,0 +1,64 @@ +assertSamePipeline(Pipelines::SubtractSubtractMillisecondsFromADate, $pipeline); + } + + public function testSubtractNumbers(): void + { + $pipeline = new Pipeline( + Stage::project( + item: 1, + total: Expression::subtract( + Expression::add( + Expression::numberFieldPath('price'), + Expression::numberFieldPath('fee'), + ), + Expression::numberFieldPath('discount'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SubtractSubtractNumbers, $pipeline); + } + + public function testSubtractTwoDates(): void + { + $pipeline = new Pipeline( + Stage::project( + item: 1, + dateDifference: Expression::subtract( + Expression::variable('NOW'), + Expression::dateFieldPath('date'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SubtractSubtractTwoDates, $pipeline); + } +} diff --git a/tests/Builder/Expression/ZipOperatorTest.php b/tests/Builder/Expression/ZipOperatorTest.php new file mode 100644 index 000000000..d0039c5d6 --- /dev/null +++ b/tests/Builder/Expression/ZipOperatorTest.php @@ -0,0 +1,58 @@ +assertSamePipeline(Pipelines::ZipFilteringAndPreservingIndexes, $pipeline); + } + + public function testMatrixTransposition(): void + { + $pipeline = new Pipeline( + Stage::project( + _id: false, + transposed: Expression::zip([ + Expression::arrayElemAt(Expression::arrayFieldPath('matrix'), 0), + Expression::arrayElemAt(Expression::arrayFieldPath('matrix'), 1), + Expression::arrayElemAt(Expression::arrayFieldPath('matrix'), 2), + ]), + ), + ); + + $this->assertSamePipeline(Pipelines::ZipMatrixTransposition, $pipeline); + } +} diff --git a/tests/Builder/Query/ExprOperatorTest.php b/tests/Builder/Query/ExprOperatorTest.php new file mode 100644 index 000000000..3b87fc35b --- /dev/null +++ b/tests/Builder/Query/ExprOperatorTest.php @@ -0,0 +1,58 @@ +assertSamePipeline(Pipelines::ExprCompareTwoFieldsFromASingleDocument, $pipeline); + } + + public function testUsingExprWithConditionalStatements(): void + { + $discountedPrice = Expression::cond( + if: Expression::gte(Expression::fieldPath('qty'), 100), + then: Expression::multiply( + Expression::numberfieldPath('price'), + 0.5, + ), + else: Expression::multiply( + Expression::numberfieldPath('price'), + 0.75, + ), + ); + + $pipeline = new Pipeline( + Stage::match( + Query::expr( + Expression::lt($discountedPrice, 5), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ExprUsingExprWithConditionalStatements, $pipeline); + } +} diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index ee49d656c..0ed2beb7b 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -183,6 +183,75 @@ enum Pipelines: string ] JSON; + /** + * Compare Two Fields from A Single Document + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/expr/#compare-two-fields-from-a-single-document + */ + case ExprCompareTwoFieldsFromASingleDocument = <<<'JSON' + [ + { + "$match": { + "$expr": { + "$gt": [ + "$spent", + "$budget" + ] + } + } + } + ] + JSON; + + /** + * Using $expr With Conditional Statements + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/expr/#using--expr-with-conditional-statements + */ + case ExprUsingExprWithConditionalStatements = <<<'JSON' + [ + { + "$match": { + "$expr": { + "$lt": [ + { + "$cond": { + "if": { + "$gte": [ + "$qty", + { + "$numberInt": "100" + } + ] + }, + "then": { + "$multiply": [ + "$price", + { + "$numberDouble": "0.5" + } + ] + }, + "else": { + "$multiply": [ + "$price", + { + "$numberDouble": "0.75" + } + ] + } + } + }, + { + "$numberInt": "5" + } + ] + } + } + } + ] + JSON; + /** * Perform a LIKE Match * From 919c4e647f3f7f4bc495665502069d178195b421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 11 Jan 2024 13:25:53 +0100 Subject: [PATCH 20/95] PHPLIB-1333 Add tests on Comparison Query Operators (#20) $eq $gt $gte $in $lt $lte $ne $nin --- generator/config/query/eq.yaml | 52 +++++ generator/config/query/gt.yaml | 9 + generator/config/query/gte.yaml | 9 + generator/config/query/in.yaml | 25 ++ generator/config/query/lt.yaml | 9 + generator/config/query/lte.yaml | 9 + generator/config/query/ne.yaml | 9 + generator/config/query/nin.yaml | 17 ++ tests/Builder/Query/EqOperatorTest.php | 70 ++++++ tests/Builder/Query/GtOperatorTest.php | 27 +++ tests/Builder/Query/GteOperatorTest.php | 27 +++ tests/Builder/Query/InOperatorTest.php | 39 ++++ tests/Builder/Query/LtOperatorTest.php | 27 +++ tests/Builder/Query/LteOperatorTest.php | 27 +++ tests/Builder/Query/NeOperatorTest.php | 27 +++ tests/Builder/Query/NinOperatorTest.php | 38 ++++ tests/Builder/Query/Pipelines.php | 288 ++++++++++++++++++++++++ 17 files changed, 709 insertions(+) create mode 100644 tests/Builder/Query/EqOperatorTest.php create mode 100644 tests/Builder/Query/GtOperatorTest.php create mode 100644 tests/Builder/Query/GteOperatorTest.php create mode 100644 tests/Builder/Query/InOperatorTest.php create mode 100644 tests/Builder/Query/LtOperatorTest.php create mode 100644 tests/Builder/Query/LteOperatorTest.php create mode 100644 tests/Builder/Query/NeOperatorTest.php create mode 100644 tests/Builder/Query/NinOperatorTest.php diff --git a/generator/config/query/eq.yaml b/generator/config/query/eq.yaml index 363964f46..9e1ca42a5 100644 --- a/generator/config/query/eq.yaml +++ b/generator/config/query/eq.yaml @@ -11,3 +11,55 @@ arguments: name: value type: - any +tests: + - + name: 'Equals a Specified Value' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/eq/#equals-a-specified-value' + pipeline: + - + $match: + qty: + $eq: 20 + + - + name: 'Field in Embedded Document Equals a Value' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/eq/#field-in-embedded-document-equals-a-value' + pipeline: + - + $match: + 'item.name': + $eq: 'ab' + + - + name: 'Equals an Array Value' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/eq/#equals-an-array-value' + pipeline: + - + $match: + tags: + $eq: ['A', 'B'] + + - + name: 'Regex Match Behaviour' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/eq/#regex-match-behaviour' + pipeline: + - + $match: + company: 'MongoDB' + - + $match: + company: + $eq: 'MongoDB' + - + $match: + company: + $regularExpression: + options: '' + pattern: '^MongoDB' + - + $match: + company: + $eq: + $regularExpression: + options: '' + pattern: '^MongoDB' diff --git a/generator/config/query/gt.yaml b/generator/config/query/gt.yaml index 3d06d02e8..9914a5f34 100644 --- a/generator/config/query/gt.yaml +++ b/generator/config/query/gt.yaml @@ -11,3 +11,12 @@ arguments: name: value type: - any +tests: + - + name: 'Match Document Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/gt/#match-document-fields' + pipeline: + - + $match: + qty: + $gt: 20 diff --git a/generator/config/query/gte.yaml b/generator/config/query/gte.yaml index ba81633a0..d8617a7c6 100644 --- a/generator/config/query/gte.yaml +++ b/generator/config/query/gte.yaml @@ -11,3 +11,12 @@ arguments: name: value type: - any +tests: + - + name: 'Match Document Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/gte/#match-document-fields' + pipeline: + - + $match: + qty: + $gte: 20 diff --git a/generator/config/query/in.yaml b/generator/config/query/in.yaml index dce69929a..3767c39ec 100644 --- a/generator/config/query/in.yaml +++ b/generator/config/query/in.yaml @@ -11,3 +11,28 @@ arguments: name: value type: - array # of expression +tests: + - + name: 'Use the $in Operator to Match Values in an Array' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/in/#use-the--in-operator-to-match-values' + pipeline: + - + $match: + tags: + $in: ['home', 'school'] + - + name: 'Use the $in Operator with a Regular Expression' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/in/#use-the--in-operator-with-a-regular-expression' + pipeline: + - + $match: + tags: + $in: + - + $regularExpression: + options: '' + pattern: '^be' + - + $regularExpression: + options: '' + pattern: '^st' diff --git a/generator/config/query/lt.yaml b/generator/config/query/lt.yaml index d07d26d4c..f1c996ded 100644 --- a/generator/config/query/lt.yaml +++ b/generator/config/query/lt.yaml @@ -11,3 +11,12 @@ arguments: name: value type: - any +tests: + - + name: 'Match Document Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/lt/#match-document-fields' + pipeline: + - + $match: + qty: + $lt: 20 diff --git a/generator/config/query/lte.yaml b/generator/config/query/lte.yaml index 68f1c458d..e61d01b03 100644 --- a/generator/config/query/lte.yaml +++ b/generator/config/query/lte.yaml @@ -11,3 +11,12 @@ arguments: name: value type: - any +tests: + - + name: 'Match Document Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/lte/#match-document-fields' + pipeline: + - + $match: + qty: + $lte: 20 diff --git a/generator/config/query/ne.yaml b/generator/config/query/ne.yaml index aabe88c40..a1f5a046b 100644 --- a/generator/config/query/ne.yaml +++ b/generator/config/query/ne.yaml @@ -11,3 +11,12 @@ arguments: name: value type: - any +tests: + - + name: 'Match Document Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/ne/#match-document-fields' + pipeline: + - + $match: + quantity: + $ne: 20 diff --git a/generator/config/query/nin.yaml b/generator/config/query/nin.yaml index 8fccbe3b8..4285e4fe7 100644 --- a/generator/config/query/nin.yaml +++ b/generator/config/query/nin.yaml @@ -11,3 +11,20 @@ arguments: name: value type: - array # of expression +tests: + - + name: 'Select on Unmatching Documents' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nin/#select-on-unmatching-documents' + pipeline: + - + $match: + quantity: + $nin: [5, 15] + - + name: 'Select on Elements Not in an Array' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nin/#select-on-elements-not-in-an-array' + pipeline: + - + $match: + tags: + $nin: ['school'] diff --git a/tests/Builder/Query/EqOperatorTest.php b/tests/Builder/Query/EqOperatorTest.php new file mode 100644 index 000000000..813c780ac --- /dev/null +++ b/tests/Builder/Query/EqOperatorTest.php @@ -0,0 +1,70 @@ +assertSamePipeline(Pipelines::EqEqualsASpecifiedValue, $pipeline); + } + + public function testEqualsAnArrayValue(): void + { + $pipeline = new Pipeline( + Stage::match( + tags: Query::eq(['A', 'B']), + ), + ); + + $this->assertSamePipeline(Pipelines::EqEqualsAnArrayValue, $pipeline); + } + + public function testFieldInEmbeddedDocumentEqualsAValue(): void + { + $pipeline = new Pipeline( + Stage::match( + ...['item.name' => Query::eq('ab')], + ), + ); + + $this->assertSamePipeline(Pipelines::EqFieldInEmbeddedDocumentEqualsAValue, $pipeline); + } + + public function testRegexMatchBehaviour(): void + { + $pipeline = new Pipeline( + Stage::match( + company: 'MongoDB', + ), + Stage::match( + company: Query::eq('MongoDB'), + ), + Stage::match( + company: new Regex('^MongoDB'), + ), + Stage::match( + company: Query::eq(new Regex('^MongoDB')), + ), + ); + + $this->assertSamePipeline(Pipelines::EqRegexMatchBehaviour, $pipeline); + } +} diff --git a/tests/Builder/Query/GtOperatorTest.php b/tests/Builder/Query/GtOperatorTest.php new file mode 100644 index 000000000..c2838d00c --- /dev/null +++ b/tests/Builder/Query/GtOperatorTest.php @@ -0,0 +1,27 @@ +assertSamePipeline(Pipelines::GtMatchDocumentFields, $pipeline); + } +} diff --git a/tests/Builder/Query/GteOperatorTest.php b/tests/Builder/Query/GteOperatorTest.php new file mode 100644 index 000000000..48198adf8 --- /dev/null +++ b/tests/Builder/Query/GteOperatorTest.php @@ -0,0 +1,27 @@ +assertSamePipeline(Pipelines::GteMatchDocumentFields, $pipeline); + } +} diff --git a/tests/Builder/Query/InOperatorTest.php b/tests/Builder/Query/InOperatorTest.php new file mode 100644 index 000000000..25a9001fd --- /dev/null +++ b/tests/Builder/Query/InOperatorTest.php @@ -0,0 +1,39 @@ +assertSamePipeline(Pipelines::InUseTheInOperatorToMatchValuesInAnArray, $pipeline); + } + + public function testUseTheInOperatorWithARegularExpression(): void + { + $pipeline = new Pipeline( + Stage::match( + tags: Query::in([new Regex('^be'), new Regex('^st')]), + ), + ); + + $this->assertSamePipeline(Pipelines::InUseTheInOperatorWithARegularExpression, $pipeline); + } +} diff --git a/tests/Builder/Query/LtOperatorTest.php b/tests/Builder/Query/LtOperatorTest.php new file mode 100644 index 000000000..119f08a4b --- /dev/null +++ b/tests/Builder/Query/LtOperatorTest.php @@ -0,0 +1,27 @@ +assertSamePipeline(Pipelines::LtMatchDocumentFields, $pipeline); + } +} diff --git a/tests/Builder/Query/LteOperatorTest.php b/tests/Builder/Query/LteOperatorTest.php new file mode 100644 index 000000000..5a0d5da7f --- /dev/null +++ b/tests/Builder/Query/LteOperatorTest.php @@ -0,0 +1,27 @@ +assertSamePipeline(Pipelines::LteMatchDocumentFields, $pipeline); + } +} diff --git a/tests/Builder/Query/NeOperatorTest.php b/tests/Builder/Query/NeOperatorTest.php new file mode 100644 index 000000000..dd6196293 --- /dev/null +++ b/tests/Builder/Query/NeOperatorTest.php @@ -0,0 +1,27 @@ +assertSamePipeline(Pipelines::NeMatchDocumentFields, $pipeline); + } +} diff --git a/tests/Builder/Query/NinOperatorTest.php b/tests/Builder/Query/NinOperatorTest.php new file mode 100644 index 000000000..04799b791 --- /dev/null +++ b/tests/Builder/Query/NinOperatorTest.php @@ -0,0 +1,38 @@ +assertSamePipeline(Pipelines::NinSelectOnElementsNotInAnArray, $pipeline); + } + + public function testSelectOnUnmatchingDocuments(): void + { + $pipeline = new Pipeline( + Stage::match( + quantity: Query::nin([5, 15]), + ), + ); + + $this->assertSamePipeline(Pipelines::NinSelectOnUnmatchingDocuments, $pipeline); + } +} diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 0ed2beb7b..78aefcc69 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -183,6 +183,106 @@ enum Pipelines: string ] JSON; + /** + * Equals a Specified Value + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/eq/#equals-a-specified-value + */ + case EqEqualsASpecifiedValue = <<<'JSON' + [ + { + "$match": { + "qty": { + "$eq": { + "$numberInt": "20" + } + } + } + } + ] + JSON; + + /** + * Field in Embedded Document Equals a Value + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/eq/#field-in-embedded-document-equals-a-value + */ + case EqFieldInEmbeddedDocumentEqualsAValue = <<<'JSON' + [ + { + "$match": { + "item.name": { + "$eq": "ab" + } + } + } + ] + JSON; + + /** + * Equals an Array Value + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/eq/#equals-an-array-value + */ + case EqEqualsAnArrayValue = <<<'JSON' + [ + { + "$match": { + "tags": { + "$eq": [ + "A", + "B" + ] + } + } + } + ] + JSON; + + /** + * Regex Match Behaviour + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/eq/#regex-match-behaviour + */ + case EqRegexMatchBehaviour = <<<'JSON' + [ + { + "$match": { + "company": "MongoDB" + } + }, + { + "$match": { + "company": { + "$eq": "MongoDB" + } + } + }, + { + "$match": { + "company": { + "$regularExpression": { + "options": "", + "pattern": "^MongoDB" + } + } + } + }, + { + "$match": { + "company": { + "$eq": { + "$regularExpression": { + "options": "", + "pattern": "^MongoDB" + } + } + } + } + } + ] + JSON; + /** * Compare Two Fields from A Single Document * @@ -252,6 +352,194 @@ enum Pipelines: string ] JSON; + /** + * Match Document Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/gt/#match-document-fields + */ + case GtMatchDocumentFields = <<<'JSON' + [ + { + "$match": { + "qty": { + "$gt": { + "$numberInt": "20" + } + } + } + } + ] + JSON; + + /** + * Match Document Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/gte/#match-document-fields + */ + case GteMatchDocumentFields = <<<'JSON' + [ + { + "$match": { + "qty": { + "$gte": { + "$numberInt": "20" + } + } + } + } + ] + JSON; + + /** + * Use the $in Operator to Match Values in an Array + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/in/#use-the--in-operator-to-match-values + */ + case InUseTheInOperatorToMatchValuesInAnArray = <<<'JSON' + [ + { + "$match": { + "tags": { + "$in": [ + "home", + "school" + ] + } + } + } + ] + JSON; + + /** + * Use the $in Operator with a Regular Expression + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/in/#use-the--in-operator-with-a-regular-expression + */ + case InUseTheInOperatorWithARegularExpression = <<<'JSON' + [ + { + "$match": { + "tags": { + "$in": [ + { + "$regularExpression": { + "options": "", + "pattern": "^be" + } + }, + { + "$regularExpression": { + "options": "", + "pattern": "^st" + } + } + ] + } + } + } + ] + JSON; + + /** + * Match Document Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/lt/#match-document-fields + */ + case LtMatchDocumentFields = <<<'JSON' + [ + { + "$match": { + "qty": { + "$lt": { + "$numberInt": "20" + } + } + } + } + ] + JSON; + + /** + * Match Document Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/lte/#match-document-fields + */ + case LteMatchDocumentFields = <<<'JSON' + [ + { + "$match": { + "qty": { + "$lte": { + "$numberInt": "20" + } + } + } + } + ] + JSON; + + /** + * Match Document Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/ne/#match-document-fields + */ + case NeMatchDocumentFields = <<<'JSON' + [ + { + "$match": { + "quantity": { + "$ne": { + "$numberInt": "20" + } + } + } + } + ] + JSON; + + /** + * Select on Unmatching Documents + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/nin/#select-on-unmatching-documents + */ + case NinSelectOnUnmatchingDocuments = <<<'JSON' + [ + { + "$match": { + "quantity": { + "$nin": [ + { + "$numberInt": "5" + }, + { + "$numberInt": "15" + } + ] + } + } + } + ] + JSON; + + /** + * Select on Elements Not in an Array + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/nin/#select-on-elements-not-in-an-array + */ + case NinSelectOnElementsNotInAnArray = <<<'JSON' + [ + { + "$match": { + "tags": { + "$nin": [ + "school" + ] + } + } + } + ] + JSON; + /** * Perform a LIKE Match * From 4176b5a7686a587bd116f6a3169a8310c0096bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 12 Jan 2024 12:40:23 +0100 Subject: [PATCH 21/95] PHPLIB-1334 Add tests on Logical Query Operators (#21) https://www.mongodb.com/docs/manual/reference/operator/query/#logical $and $not $nor $or --- generator/config/query/and.yaml | 36 ++++ generator/config/query/nor.yaml | 43 ++++ generator/config/query/not.yaml | 21 ++ generator/config/query/or.yaml | 32 +++ tests/Builder/Query/AndOperatorTest.php | 62 ++++++ tests/Builder/Query/NorOperatorTest.php | 79 +++++++ tests/Builder/Query/NotOperatorTest.php | 43 ++++ tests/Builder/Query/OrOperatorTest.php | 59 +++++ tests/Builder/Query/Pipelines.php | 276 ++++++++++++++++++++++++ 9 files changed, 651 insertions(+) create mode 100644 tests/Builder/Query/AndOperatorTest.php create mode 100644 tests/Builder/Query/NorOperatorTest.php create mode 100644 tests/Builder/Query/NotOperatorTest.php create mode 100644 tests/Builder/Query/OrOperatorTest.php diff --git a/generator/config/query/and.yaml b/generator/config/query/and.yaml index 86d8faf12..74ebf506e 100644 --- a/generator/config/query/and.yaml +++ b/generator/config/query/and.yaml @@ -13,3 +13,39 @@ arguments: - query variadic: array variadicMin: 1 +tests: + - + name: 'AND Queries With Multiple Expressions Specifying the Same Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/and/#and-queries-with-multiple-expressions-specifying-the-same-field' + pipeline: + - + $match: + $and: + - + price: + $ne: 1.99 + - + price: + $exists: true + - + name: 'AND Queries With Multiple Expressions Specifying the Same Operator' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/and/#and-queries-with-multiple-expressions-specifying-the-same-operator' + pipeline: + - + $match: + $and: + - + $or: + - + qty: + $lt: 10 + - + qty: + $gt: 50 + - + $or: + - + sale: true + - + price: + $lt: 5 diff --git a/generator/config/query/nor.yaml b/generator/config/query/nor.yaml index d6349b1e8..d1ad7159a 100644 --- a/generator/config/query/nor.yaml +++ b/generator/config/query/nor.yaml @@ -13,3 +13,46 @@ arguments: - query variadic: array variadicMin: 1 +tests: + - + name: 'Query with Two Expressions' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nor/#-nor-query-with-two-expressions' + pipeline: + - + $match: + $nor: + - + price: 1.99 + - + sale: true + - + name: 'Additional Comparisons' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nor/#-nor-and-additional-comparisons' + pipeline: + - + $match: + $nor: + - + price: 1.99 + - + qty: + $lt: 20 + - + sale: true + - + name: '$nor and $exists' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nor/#-nor-and--exists' + pipeline: + - + $match: + $nor: + - + price: 1.99 + - + price: + $exists: false + - + sale: true + - + sale: + $exists: false diff --git a/generator/config/query/not.yaml b/generator/config/query/not.yaml index a2d05312a..2b3702e7c 100644 --- a/generator/config/query/not.yaml +++ b/generator/config/query/not.yaml @@ -11,3 +11,24 @@ arguments: name: expression type: - fieldQuery +tests: + - + name: 'Syntax' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/not/#syntax' + pipeline: + - + $match: + price: + $not: + $gt: 1.99 + - + name: 'Regular Expressions' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/not/#-not-and-regular-expressions' + pipeline: + - + $match: + price: + $not: + $regularExpression: + pattern: '^p.*' + options: '' diff --git a/generator/config/query/or.yaml b/generator/config/query/or.yaml index ec9cc02a6..ce2b7603c 100644 --- a/generator/config/query/or.yaml +++ b/generator/config/query/or.yaml @@ -13,3 +13,35 @@ arguments: - query variadic: array variadicMin: 1 +tests: + - + name: '$or Clauses' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/or/#-or-clauses-and-indexes' + pipeline: + - + $match: + $or: + - + quantity: + $lt: 20 + - + price: 10 + + - + name: 'Error Handling' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/or/#error-handling' + pipeline: + - + $match: + $or: + - + x: + $eq: 0 + - + $expr: + $eq: + - + $divide: + - 1 + - '$x' + - 3 diff --git a/tests/Builder/Query/AndOperatorTest.php b/tests/Builder/Query/AndOperatorTest.php new file mode 100644 index 000000000..32446faae --- /dev/null +++ b/tests/Builder/Query/AndOperatorTest.php @@ -0,0 +1,62 @@ +assertSamePipeline(Pipelines::AndANDQueriesWithMultipleExpressionsSpecifyingTheSameField, $pipeline); + } + + public function testANDQueriesWithMultipleExpressionsSpecifyingTheSameOperator(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::and( + Query::or( + Query::query( + qty: Query::lt(10), + ), + Query::query( + qty: Query::gt(50), + ), + ), + Query::or( + Query::query( + sale: true, + ), + Query::query( + price: Query::lt(5), + ), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::AndANDQueriesWithMultipleExpressionsSpecifyingTheSameOperator, $pipeline); + } +} diff --git a/tests/Builder/Query/NorOperatorTest.php b/tests/Builder/Query/NorOperatorTest.php new file mode 100644 index 000000000..db03502dc --- /dev/null +++ b/tests/Builder/Query/NorOperatorTest.php @@ -0,0 +1,79 @@ +assertSamePipeline(Pipelines::NorAdditionalComparisons, $pipeline); + } + + public function testNorAndExists(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::nor( + Query::query( + price: 1.99, + ), + Query::query( + price: Query::exists(false), + ), + Query::query( + sale: true, + ), + Query::query( + sale: Query::exists(false), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::NorNorAndExists, $pipeline); + } + + public function testQueryWithTwoExpressions(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::nor( + Query::query( + price: 1.99, + ), + Query::query( + sale: true, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::NorQueryWithTwoExpressions, $pipeline); + } +} diff --git a/tests/Builder/Query/NotOperatorTest.php b/tests/Builder/Query/NotOperatorTest.php new file mode 100644 index 000000000..e69062f9c --- /dev/null +++ b/tests/Builder/Query/NotOperatorTest.php @@ -0,0 +1,43 @@ +assertSamePipeline(Pipelines::NotRegularExpressions, $pipeline); + } + + public function testSyntax(): void + { + $pipeline = new Pipeline( + Stage::match( + price: Query::not( + Query::gt(1.99), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::NotSyntax, $pipeline); + } +} diff --git a/tests/Builder/Query/OrOperatorTest.php b/tests/Builder/Query/OrOperatorTest.php new file mode 100644 index 000000000..3ad68b20b --- /dev/null +++ b/tests/Builder/Query/OrOperatorTest.php @@ -0,0 +1,59 @@ +assertSamePipeline(Pipelines::OrErrorHandling, $pipeline); + } + + public function testOrClauses(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::or( + Query::query( + quantity: Query::lt(20), + ), + Query::query( + price: 10, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::OrOrClauses, $pipeline); + } +} diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 78aefcc69..644f6dc35 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -67,6 +67,82 @@ enum Pipelines: string ] JSON; + /** + * AND Queries With Multiple Expressions Specifying the Same Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/and/#and-queries-with-multiple-expressions-specifying-the-same-field + */ + case AndANDQueriesWithMultipleExpressionsSpecifyingTheSameField = <<<'JSON' + [ + { + "$match": { + "$and": [ + { + "price": { + "$ne": { + "$numberDouble": "1.9899999999999999911" + } + } + }, + { + "price": { + "$exists": true + } + } + ] + } + } + ] + JSON; + + /** + * AND Queries With Multiple Expressions Specifying the Same Operator + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/and/#and-queries-with-multiple-expressions-specifying-the-same-operator + */ + case AndANDQueriesWithMultipleExpressionsSpecifyingTheSameOperator = <<<'JSON' + [ + { + "$match": { + "$and": [ + { + "$or": [ + { + "qty": { + "$lt": { + "$numberInt": "10" + } + } + }, + { + "qty": { + "$gt": { + "$numberInt": "50" + } + } + } + ] + }, + { + "$or": [ + { + "sale": true + }, + { + "price": { + "$lt": { + "$numberInt": "5" + } + } + } + ] + } + ] + } + } + ] + JSON; + /** * Element Match * @@ -540,6 +616,206 @@ enum Pipelines: string ] JSON; + /** + * Query with Two Expressions + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/nor/#-nor-query-with-two-expressions + */ + case NorQueryWithTwoExpressions = <<<'JSON' + [ + { + "$match": { + "$nor": [ + { + "price": { + "$numberDouble": "1.9899999999999999911" + } + }, + { + "sale": true + } + ] + } + } + ] + JSON; + + /** + * Additional Comparisons + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/nor/#-nor-and-additional-comparisons + */ + case NorAdditionalComparisons = <<<'JSON' + [ + { + "$match": { + "$nor": [ + { + "price": { + "$numberDouble": "1.9899999999999999911" + } + }, + { + "qty": { + "$lt": { + "$numberInt": "20" + } + } + }, + { + "sale": true + } + ] + } + } + ] + JSON; + + /** + * $nor and $exists + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/nor/#-nor-and--exists + */ + case NorNorAndExists = <<<'JSON' + [ + { + "$match": { + "$nor": [ + { + "price": { + "$numberDouble": "1.9899999999999999911" + } + }, + { + "price": { + "$exists": false + } + }, + { + "sale": true + }, + { + "sale": { + "$exists": false + } + } + ] + } + } + ] + JSON; + + /** + * Syntax + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/not/#syntax + */ + case NotSyntax = <<<'JSON' + [ + { + "$match": { + "price": { + "$not": { + "$gt": { + "$numberDouble": "1.9899999999999999911" + } + } + } + } + } + ] + JSON; + + /** + * Regular Expressions + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/not/#-not-and-regular-expressions + */ + case NotRegularExpressions = <<<'JSON' + [ + { + "$match": { + "price": { + "$not": { + "$regularExpression": { + "pattern": "^p.*", + "options": "" + } + } + } + } + } + ] + JSON; + + /** + * $or Clauses + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/or/#-or-clauses-and-indexes + */ + case OrOrClauses = <<<'JSON' + [ + { + "$match": { + "$or": [ + { + "quantity": { + "$lt": { + "$numberInt": "20" + } + } + }, + { + "price": { + "$numberInt": "10" + } + } + ] + } + } + ] + JSON; + + /** + * Error Handling + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/or/#error-handling + */ + case OrErrorHandling = <<<'JSON' + [ + { + "$match": { + "$or": [ + { + "x": { + "$eq": { + "$numberInt": "0" + } + } + }, + { + "$expr": { + "$eq": [ + { + "$divide": [ + { + "$numberInt": "1" + }, + "$x" + ] + }, + { + "$numberInt": "3" + } + ] + } + } + ] + } + } + ] + JSON; + /** * Perform a LIKE Match * From 5e8b98a9d84d9d21b8a64781a10c31783e3dcde0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 15 Jan 2024 12:14:21 +0100 Subject: [PATCH 22/95] PHPLIB-1343 Add tests on Array Expression Operators (#22) --- generator/config/expression/arrayElemAt.yaml | 16 + .../config/expression/arrayToObject.yaml | 37 + generator/config/expression/concatArrays.yaml | 11 + generator/config/expression/filter.yaml | 60 + generator/config/expression/in.yaml | 12 + generator/config/expression/indexOfArray.yaml | 11 + generator/config/expression/isArray.yaml | 25 +- generator/config/expression/map.yaml | 48 + generator/config/expression/maxN.yaml | 11 + generator/config/expression/minN.yaml | 11 + .../config/expression/objectToArray.yaml | 30 + generator/config/expression/range.yaml | 14 + generator/config/expression/reduce.yaml | 102 ++ generator/config/expression/reverseArray.yaml | 10 + generator/config/expression/size.yaml | 19 + generator/config/expression/slice.yaml | 12 + generator/config/expression/sortArray.yaml | 85 ++ src/Builder/BuilderEncoder.php | 2 + src/Builder/Expression/FactoryTrait.php | 11 +- src/Builder/Expression/IsArrayOperator.php | 20 +- src/Builder/Expression/SortArrayOperator.php | 8 +- .../Expression/ArrayElemAtOperatorTest.php | 35 + .../Expression/ArrayToObjectOperatorTest.php | 60 + .../Expression/ConcatArraysOperatorTest.php | 30 + .../Builder/Expression/FilterOperatorTest.php | 91 ++ tests/Builder/Expression/InOperatorTest.php | 33 + .../Expression/IndexOfArrayOperatorTest.php | 30 + .../Expression/IsArrayOperatorTest.php | 37 + tests/Builder/Expression/MapOperatorTest.php | 75 ++ tests/Builder/Expression/MaxNOperatorTest.php | 30 + tests/Builder/Expression/MinNOperatorTest.php | 30 + .../Expression/ObjectToArrayOperatorTest.php | 53 + tests/Builder/Expression/Pipelines.php | 1037 ++++++++++++++++- .../Builder/Expression/RangeOperatorTest.php | 36 + .../Builder/Expression/ReduceOperatorTest.php | 141 +++ .../Expression/ReverseArrayOperatorTest.php | 30 + tests/Builder/Expression/SizeOperatorTest.php | 36 + .../Builder/Expression/SliceOperatorTest.php | 31 + .../Expression/SortArrayOperatorTest.php | 115 ++ 39 files changed, 2434 insertions(+), 51 deletions(-) create mode 100644 tests/Builder/Expression/ArrayElemAtOperatorTest.php create mode 100644 tests/Builder/Expression/ArrayToObjectOperatorTest.php create mode 100644 tests/Builder/Expression/ConcatArraysOperatorTest.php create mode 100644 tests/Builder/Expression/FilterOperatorTest.php create mode 100644 tests/Builder/Expression/InOperatorTest.php create mode 100644 tests/Builder/Expression/IndexOfArrayOperatorTest.php create mode 100644 tests/Builder/Expression/IsArrayOperatorTest.php create mode 100644 tests/Builder/Expression/MapOperatorTest.php create mode 100644 tests/Builder/Expression/MaxNOperatorTest.php create mode 100644 tests/Builder/Expression/MinNOperatorTest.php create mode 100644 tests/Builder/Expression/ObjectToArrayOperatorTest.php create mode 100644 tests/Builder/Expression/RangeOperatorTest.php create mode 100644 tests/Builder/Expression/ReduceOperatorTest.php create mode 100644 tests/Builder/Expression/ReverseArrayOperatorTest.php create mode 100644 tests/Builder/Expression/SizeOperatorTest.php create mode 100644 tests/Builder/Expression/SliceOperatorTest.php create mode 100644 tests/Builder/Expression/SortArrayOperatorTest.php diff --git a/generator/config/expression/arrayElemAt.yaml b/generator/config/expression/arrayElemAt.yaml index 6baa62c9c..09fe9dae1 100644 --- a/generator/config/expression/arrayElemAt.yaml +++ b/generator/config/expression/arrayElemAt.yaml @@ -15,3 +15,19 @@ arguments: name: idx type: - resolvesToInt +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayElemAt/#example' + pipeline: + - + $project: + name: 1 + first: + $arrayElemAt: + - '$favorites' + - 0 + last: + $arrayElemAt: + - '$favorites' + - -1 diff --git a/generator/config/expression/arrayToObject.yaml b/generator/config/expression/arrayToObject.yaml index 78e5055c8..76baeb7b0 100644 --- a/generator/config/expression/arrayToObject.yaml +++ b/generator/config/expression/arrayToObject.yaml @@ -11,3 +11,40 @@ arguments: name: array type: - resolvesToArray +tests: + - + name: '$arrayToObject Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayToObject/#-arraytoobject--example' + pipeline: + - + $project: + item: 1 + dimensions: + # The example renders a single value, but the builder generates an array for consistency + # $arrayToObject: '$dimensions' + $arrayToObject: + - '$dimensions' + - + name: '$objectToArray and $arrayToObject Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayToObject/#-objecttoarray----arraytoobject-example' + pipeline: + - + $addFields: + instock: + $objectToArray: '$instock' + - + $addFields: + instock: + $concatArrays: + - '$instock' + - + - + k: 'total' + v: + $sum: + - '$instock.v' + - + $addFields: + instock: + $arrayToObject: + - '$instock' diff --git a/generator/config/expression/concatArrays.yaml b/generator/config/expression/concatArrays.yaml index a6e90df32..026541092 100644 --- a/generator/config/expression/concatArrays.yaml +++ b/generator/config/expression/concatArrays.yaml @@ -12,3 +12,14 @@ arguments: type: - resolvesToArray variadic: array +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/concatArrays/#example' + pipeline: + - + $project: + items: + $concatArrays: + - '$instock' + - '$ordered' diff --git a/generator/config/expression/filter.yaml b/generator/config/expression/filter.yaml index cacd31fd4..0b72f6d75 100644 --- a/generator/config/expression/filter.yaml +++ b/generator/config/expression/filter.yaml @@ -32,3 +32,63 @@ arguments: description: | A number expression that restricts the number of matching array elements that $filter returns. You cannot specify a limit less than 1. The matching array elements are returned in the order they appear in the input array. If the specified limit is greater than the number of matching array elements, $filter returns all matching array elements. If the limit is null, $filter returns all matching array elements. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/#examples' + pipeline: + - + $project: + items: + $filter: + input: '$items' + as: 'item' + cond: + $gte: + - '$$item.price' + - 100 + - + name: 'Using the limit field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/#using-the-limit-field' + pipeline: + - + $project: + items: + $filter: + input: '$items' + cond: + $gte: + - '$$item.price' + - 100 + as: 'item' + limit: 1 + - + name: 'limit as a Numeric Expression' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/#limit-as-a-numeric-expression' + pipeline: + - + $project: + items: + $filter: + input: '$items' + cond: + $lte: + - '$$item.price' + - 150 + as: 'item' + limit: 2 + - + name: 'limit Greater than Possible Matches' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/#limit-greater-than-possible-matches' + pipeline: + - + $project: + items: + $filter: + input: '$items' + cond: + $gte: + - '$$item.price' + - 100 + as: 'item' + limit: 5 diff --git a/generator/config/expression/in.yaml b/generator/config/expression/in.yaml index 2ec0c1f8f..7cef8c149 100644 --- a/generator/config/expression/in.yaml +++ b/generator/config/expression/in.yaml @@ -19,3 +19,15 @@ arguments: - resolvesToArray description: | Any valid expression that resolves to an array. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/in/#example' + pipeline: + - + $project: + store location: '$location' + has bananas: + $in: + - 'bananas' + - '$in_stock' diff --git a/generator/config/expression/indexOfArray.yaml b/generator/config/expression/indexOfArray.yaml index 061ca1ee7..5840ee0fa 100644 --- a/generator/config/expression/indexOfArray.yaml +++ b/generator/config/expression/indexOfArray.yaml @@ -35,3 +35,14 @@ arguments: description: | An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. If unspecified, the ending index position for the search is the end of the string. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfArray/#example' + pipeline: + - + $project: + index: + $indexOfArray: + - '$items' + - 2 diff --git a/generator/config/expression/isArray.yaml b/generator/config/expression/isArray.yaml index 1bdbb675f..9379d3ca8 100644 --- a/generator/config/expression/isArray.yaml +++ b/generator/config/expression/isArray.yaml @@ -11,4 +11,27 @@ arguments: name: expression type: - expression - variadic: array +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isArray/#example' + pipeline: + - + $project: + items: + $cond: + if: + $and: + # The example in the docs uses the short syntax for $isArray, + # but the aggregation builder always uses the more verbose syntax. + - + $isArray: + - '$instock' + - + $isArray: + - '$ordered' + then: + $concatArrays: + - '$instock' + - '$ordered' + else: 'One or more fields is not an array.' diff --git a/generator/config/expression/map.yaml b/generator/config/expression/map.yaml index 335716783..1019a2cec 100644 --- a/generator/config/expression/map.yaml +++ b/generator/config/expression/map.yaml @@ -26,3 +26,51 @@ arguments: - expression description: | An expression that is applied to each element of the input array. The expression references each element individually with the variable name specified in as. +tests: + - + name: 'Add to Each Element of an Array' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/#add-to-each-element-of-an-array' + pipeline: + - + $project: + adjustedGrades: + $map: + input: '$quizzes' + as: 'grade' + in: + $add: + - '$$grade' + - 2 + - + name: 'Truncate Each Array Element' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/#truncate-each-array-element' + pipeline: + - + $project: + city: '$city' + integerValues: + $map: + input: '$distances' + as: 'decimalValue' + in: + # The example renders a single value, but the builder generates an array for consistency + # $trunc: '$$decimalValue' + $trunc: + - '$$decimalValue' + - + name: 'Convert Celsius Temperatures to Fahrenheit' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/#convert-celsius-temperatures-to-fahrenheit' + pipeline: + - + $addFields: + tempsF: + $map: + input: '$tempsC' + as: 'tempInCelsius' + in: + $add: + - + $multiply: + - '$$tempInCelsius' + - 1.8 + - 32 diff --git a/generator/config/expression/maxN.yaml b/generator/config/expression/maxN.yaml index af3a9c5c5..e0d9d243e 100644 --- a/generator/config/expression/maxN.yaml +++ b/generator/config/expression/maxN.yaml @@ -19,3 +19,14 @@ arguments: - resolvesToInt description: | An expression that resolves to a positive integer. The integer specifies the number of array elements that $maxN returns. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN-array-element/#example' + pipeline: + - + $addFields: + maxScores: + $maxN: + n: 2 + input: '$score' diff --git a/generator/config/expression/minN.yaml b/generator/config/expression/minN.yaml index 07a3d957c..e7fa8cac1 100644 --- a/generator/config/expression/minN.yaml +++ b/generator/config/expression/minN.yaml @@ -19,3 +19,14 @@ arguments: - resolvesToInt description: | An expression that resolves to a positive integer. The integer specifies the number of array elements that $maxN returns. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN-array-element/#example' + pipeline: + - + $addFields: + minScores: + $minN: + n: 2 + input: '$score' diff --git a/generator/config/expression/objectToArray.yaml b/generator/config/expression/objectToArray.yaml index 40c1cba77..bcad8ca11 100644 --- a/generator/config/expression/objectToArray.yaml +++ b/generator/config/expression/objectToArray.yaml @@ -13,3 +13,33 @@ arguments: - resolvesToObject description: | Any valid expression as long as it resolves to a document object. $objectToArray applies to the top-level fields of its argument. If the argument is a document that itself contains embedded document fields, the $objectToArray does not recursively apply to the embedded document fields. +tests: + # "$objectToArray and $arrayToObject Example" omitted as it's already in arrayToObject.yaml + - + name: '$objectToArray Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/#-objecttoarray-example' + pipeline: + - + $project: + item: 1 + dimensions: + $objectToArray: '$dimensions' + - + name: '$objectToArray to Sum Nested Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/#-objecttoarray-to-sum-nested-fields' + pipeline: + - + $project: + warehouses: + $objectToArray: '$instock' + - + # The example in the docs uses the short syntax for $unwind, + # but the aggregation builder always uses the more verbose syntax. + # $unwind: '$warehouses' + $unwind: + path: '$warehouses' + - + $group: + _id: '$warehouses.k' + total: + $sum: '$warehouses.v' diff --git a/generator/config/expression/range.yaml b/generator/config/expression/range.yaml index ceea6be9f..5c78d8898 100644 --- a/generator/config/expression/range.yaml +++ b/generator/config/expression/range.yaml @@ -26,3 +26,17 @@ arguments: optional: true description: | An integer that specifies the increment value. Can be any valid expression that resolves to a non-zero integer. Defaults to 1. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/range/#example' + pipeline: + - + $project: + _id: 0 + city: 1 + Rest stops: + $range: + - 0 + - '$distance' + - 25 diff --git a/generator/config/expression/reduce.yaml b/generator/config/expression/reduce.yaml index a92eef3bd..bf51eabe3 100644 --- a/generator/config/expression/reduce.yaml +++ b/generator/config/expression/reduce.yaml @@ -30,3 +30,105 @@ arguments: During evaluation of the in expression, two variables will be available: - value is the variable that represents the cumulative value of the expression. - this is the variable that refers to the element being processed. +tests: + - + name: 'Multiplication' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/#multiplication' + pipeline: + - + $group: + _id: '$experimentId' + probabilityArr: + $push: '$probability' + - + $project: + description: 1 + results: + $reduce: + input: '$probabilityArr' + initialValue: 1 + in: + $multiply: + - '$$value' + - '$$this' + - + name: 'Discounted Merchandise' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/#discounted-merchandise' + pipeline: + - + $project: + discountedPrice: + $reduce: + input: '$discounts' + initialValue: '$price' + in: + $multiply: + - '$$value' + - + $subtract: + - 1 + - '$$this' + - + name: 'String Concatenation' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/#string-concatenation' + pipeline: + # Filter to return only non-empty arrays + - + $match: + hobbies: + $gt: [] + - + $project: + name: 1 + bio: + $reduce: + input: '$hobbies' + initialValue: 'My hobbies include:' + in: + $concat: + - '$$value' + - + $cond: + if: + $eq: + - '$$value' + - 'My hobbies include:' + then: ' ' + else: ', ' + - '$$this' + - + name: 'Array Concatenation' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/#array-concatenation' + pipeline: + - + $project: + collapsed: + $reduce: + input: '$arr' + initialValue: [] + in: + $concatArrays: + - '$$value' + - '$$this' + - + name: 'Computing a Multiple Reductions' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/#computing-a-multiple-reductions' + pipeline: + - + $project: + results: + $reduce: + input: '$arr' + initialValue: [] + in: + collapsed: + $concatArrays: + - '$$value.collapsed' + - '$$this' + firstValues: + $concatArrays: + - '$$value.firstValues' + - + $slice: + - '$$this' + - 1 diff --git a/generator/config/expression/reverseArray.yaml b/generator/config/expression/reverseArray.yaml index c351148fa..2cbe3f3cd 100644 --- a/generator/config/expression/reverseArray.yaml +++ b/generator/config/expression/reverseArray.yaml @@ -13,3 +13,13 @@ arguments: - resolvesToArray description: | The argument can be any valid expression as long as it resolves to an array. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/reverseArray/#example' + pipeline: + - + $project: + name: 1 + reverseFavorites: + $reverseArray: '$favorites' diff --git a/generator/config/expression/size.yaml b/generator/config/expression/size.yaml index b773e67f2..40944af00 100644 --- a/generator/config/expression/size.yaml +++ b/generator/config/expression/size.yaml @@ -13,3 +13,22 @@ arguments: - resolvesToArray description: | The argument for $size can be any expression as long as it resolves to an array. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/size/#example' + pipeline: + - + $project: + item: 1 + numberOfColors: + $cond: + if: + # The example in the docs uses the short syntax for $isArray, + # but the aggregation builder always uses the more verbose syntax. + # $isArray: '$colors' + $isArray: + - '$colors' + then: + $size: '$colors' + else: 'NA' diff --git a/generator/config/expression/slice.yaml b/generator/config/expression/slice.yaml index a034a00db..d0abbbbed 100644 --- a/generator/config/expression/slice.yaml +++ b/generator/config/expression/slice.yaml @@ -31,3 +31,15 @@ arguments: Any valid expression as long as it resolves to an integer. If position is specified, n must resolve to a positive integer. If positive, $slice returns up to the first n elements in the array. If the position is specified, $slice returns the first n elements starting from the position. If negative, $slice returns up to the last n elements in the array. n cannot resolve to a negative number if is specified. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/slice/#example' + pipeline: + - + $project: + name: 1 + threeFavorites: + $slice: + - '$favorites' + - 3 diff --git a/generator/config/expression/sortArray.yaml b/generator/config/expression/sortArray.yaml index 5b32bae4b..ee320c77e 100644 --- a/generator/config/expression/sortArray.yaml +++ b/generator/config/expression/sortArray.yaml @@ -19,5 +19,90 @@ arguments: name: sortBy type: - object # SortSpec + - int description: | The document specifies a sort ordering. +tests: + - + name: 'Sort on a Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-a-field' + pipeline: + - + $project: + _id: 0 + result: + $sortArray: + input: '$team' + sortBy: + name: 1 + - + name: 'Sort on a Subfield' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-a-subfield' + pipeline: + - + $project: + _id: 0 + result: + $sortArray: + input: '$team' + sortBy: + address.city: -1 + - + name: 'Sort on Multiple Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-multiple-fields' + pipeline: + - + $project: + _id: 0 + result: + $sortArray: + input: '$team' + sortBy: + age: -1 + name: 1 + - + name: 'Sort an Array of Integers' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-an-array-of-integers' + pipeline: + - + $project: + _id: 0 + result: + $sortArray: + input: + - 1 + - 4 + - 1 + - 6 + - 12 + - 5 + sortBy: 1 + - + name: 'Sort on Mixed Type Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-mixed-type-fields' + pipeline: + - + $project: + _id: 0 + result: + $sortArray: + input: + - 20 + - 4 + - + a: 'Free' + - 6 + - 21 + - 5 + - 'Gratis' + - + a: ~ + - + a: + sale: true + price: 19 + # Decimal128( "10.23" ) + - 10.23 + - + a: 'On sale' + sortBy: 1 diff --git a/src/Builder/BuilderEncoder.php b/src/Builder/BuilderEncoder.php index 0b38dbf9b..da4bd0596 100644 --- a/src/Builder/BuilderEncoder.php +++ b/src/Builder/BuilderEncoder.php @@ -278,6 +278,8 @@ private function recursiveEncode(mixed $value): mixed foreach (get_object_vars($value) as $key => $val) { $value->{$key} = $this->recursiveEncode($val); } + + return $value; } return $this->encodeIfSupported($value); diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index d95083b71..cca50e5b1 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -956,14 +956,13 @@ public static function integral( * Determines if the operand is an array. Returns a boolean. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isArray/ - * @no-named-arguments - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ public static function isArray( - Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, ): IsArrayOperator { - return new IsArrayOperator(...$expression); + return new IsArrayOperator($expression); } /** @@ -1868,11 +1867,11 @@ public static function slice( * @param BSONArray|PackedArray|ResolvesToArray|array $input The array to be sorted. * The result is null if the expression: is missing, evaluates to null, or evaluates to undefined * If the expression evaluates to any other non-array value, the document returns an error. - * @param Document|Serializable|array|stdClass $sortBy The document specifies a sort ordering. + * @param Document|Serializable|array|int|stdClass $sortBy The document specifies a sort ordering. */ public static function sortArray( PackedArray|ResolvesToArray|BSONArray|array $input, - Document|Serializable|stdClass|array $sortBy, + Document|Serializable|stdClass|array|int $sortBy, ): SortArrayOperator { return new SortArrayOperator($input, $sortBy); diff --git a/src/Builder/Expression/IsArrayOperator.php b/src/Builder/Expression/IsArrayOperator.php index 157bcef5f..cf764aa94 100644 --- a/src/Builder/Expression/IsArrayOperator.php +++ b/src/Builder/Expression/IsArrayOperator.php @@ -12,11 +12,8 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\OperatorInterface; -use MongoDB\Exception\InvalidArgumentException; use stdClass; -use function array_is_list; - /** * Determines if the operand is an array. Returns a boolean. * @@ -26,23 +23,14 @@ class IsArrayOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var list $expression */ - public readonly array $expression; + /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression - * @no-named-arguments + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ - public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) + public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { - if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); - } - - if (! array_is_list($expression)) { - throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); - } - $this->expression = $expression; } diff --git a/src/Builder/Expression/SortArrayOperator.php b/src/Builder/Expression/SortArrayOperator.php index eadf4d06b..225a602a5 100644 --- a/src/Builder/Expression/SortArrayOperator.php +++ b/src/Builder/Expression/SortArrayOperator.php @@ -36,18 +36,18 @@ class SortArrayOperator implements ResolvesToArray, OperatorInterface */ public readonly PackedArray|ResolvesToArray|BSONArray|array $input; - /** @var Document|Serializable|array|stdClass $sortBy The document specifies a sort ordering. */ - public readonly Document|Serializable|stdClass|array $sortBy; + /** @var Document|Serializable|array|int|stdClass $sortBy The document specifies a sort ordering. */ + public readonly Document|Serializable|stdClass|array|int $sortBy; /** * @param BSONArray|PackedArray|ResolvesToArray|array $input The array to be sorted. * The result is null if the expression: is missing, evaluates to null, or evaluates to undefined * If the expression evaluates to any other non-array value, the document returns an error. - * @param Document|Serializable|array|stdClass $sortBy The document specifies a sort ordering. + * @param Document|Serializable|array|int|stdClass $sortBy The document specifies a sort ordering. */ public function __construct( PackedArray|ResolvesToArray|BSONArray|array $input, - Document|Serializable|stdClass|array $sortBy, + Document|Serializable|stdClass|array|int $sortBy, ) { if (is_array($input) && ! array_is_list($input)) { throw new InvalidArgumentException('Expected $input argument to be a list, got an associative array.'); diff --git a/tests/Builder/Expression/ArrayElemAtOperatorTest.php b/tests/Builder/Expression/ArrayElemAtOperatorTest.php new file mode 100644 index 000000000..0dbe768ec --- /dev/null +++ b/tests/Builder/Expression/ArrayElemAtOperatorTest.php @@ -0,0 +1,35 @@ +assertSamePipeline(Pipelines::ArrayElemAtExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ArrayToObjectOperatorTest.php b/tests/Builder/Expression/ArrayToObjectOperatorTest.php new file mode 100644 index 000000000..c18d7bdcb --- /dev/null +++ b/tests/Builder/Expression/ArrayToObjectOperatorTest.php @@ -0,0 +1,60 @@ +assertSamePipeline(Pipelines::ArrayToObjectArrayToObjectExample, $pipeline); + } + + public function testObjectToArrayAndArrayToObjectExample(): void + { + $pipeline = new Pipeline( + Stage::addFields( + instock: Expression::objectToArray( + Expression::objectFieldPath('instock'), + ), + ), + Stage::addFields( + instock: Expression::concatArrays( + Expression::arrayFieldPath('instock'), + [ + object(k: 'total', v: Expression::sum( + Expression::fieldPath('instock.v'), + )), + ], + ), + ), + Stage::addFields( + instock: Expression::arrayToObject( + Expression::arrayFieldPath('instock'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ArrayToObjectObjectToArrayAndArrayToObjectExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ConcatArraysOperatorTest.php b/tests/Builder/Expression/ConcatArraysOperatorTest.php new file mode 100644 index 000000000..ba7dffb4c --- /dev/null +++ b/tests/Builder/Expression/ConcatArraysOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::ConcatArraysExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/FilterOperatorTest.php b/tests/Builder/Expression/FilterOperatorTest.php new file mode 100644 index 000000000..1cb3af584 --- /dev/null +++ b/tests/Builder/Expression/FilterOperatorTest.php @@ -0,0 +1,91 @@ +assertSamePipeline(Pipelines::FilterExample, $pipeline); + } + + public function testLimitAsANumericExpression(): void + { + $pipeline = new Pipeline( + Stage::project( + items: Expression::filter( + input: Expression::arrayFieldPath('items'), + cond: Expression::lte( + Expression::variable('item.price'), + 150, + ), + as: 'item', + limit: 2, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FilterLimitAsANumericExpression, $pipeline); + } + + public function testLimitGreaterThanPossibleMatches(): void + { + $pipeline = new Pipeline( + Stage::project( + items: Expression::filter( + input: Expression::arrayFieldPath('items'), + cond: Expression::gte( + Expression::variable('item.price'), + 100, + ), + as: 'item', + limit: 5, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FilterLimitGreaterThanPossibleMatches, $pipeline); + } + + public function testUsingTheLimitField(): void + { + $pipeline = new Pipeline( + Stage::project( + items: Expression::filter( + input: Expression::arrayFieldPath('items'), + cond: Expression::gte( + Expression::variable('item.price'), + 100, + ), + as: 'item', + limit: 1, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FilterUsingTheLimitField, $pipeline); + } +} diff --git a/tests/Builder/Expression/InOperatorTest.php b/tests/Builder/Expression/InOperatorTest.php new file mode 100644 index 000000000..27722f856 --- /dev/null +++ b/tests/Builder/Expression/InOperatorTest.php @@ -0,0 +1,33 @@ + Expression::fieldPath('location'), + 'has bananas' => Expression::in( + 'bananas', + Expression::arrayFieldPath('in_stock'), + ), + ], + ), + ); + + $this->assertSamePipeline(Pipelines::InExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/IndexOfArrayOperatorTest.php b/tests/Builder/Expression/IndexOfArrayOperatorTest.php new file mode 100644 index 000000000..a18343ea2 --- /dev/null +++ b/tests/Builder/Expression/IndexOfArrayOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::IndexOfArrayExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/IsArrayOperatorTest.php b/tests/Builder/Expression/IsArrayOperatorTest.php new file mode 100644 index 000000000..983f4490d --- /dev/null +++ b/tests/Builder/Expression/IsArrayOperatorTest.php @@ -0,0 +1,37 @@ +assertSamePipeline(Pipelines::IsArrayExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/MapOperatorTest.php b/tests/Builder/Expression/MapOperatorTest.php new file mode 100644 index 000000000..347801c3a --- /dev/null +++ b/tests/Builder/Expression/MapOperatorTest.php @@ -0,0 +1,75 @@ + [ + '$$grade', + 2, + ], + ], + ), + ), + ); + + $this->assertSamePipeline(Pipelines::MapAddToEachElementOfAnArray, $pipeline); + } + + public function testConvertCelsiusTemperaturesToFahrenheit(): void + { + $pipeline = new Pipeline( + Stage::addFields( + tempsF: Expression::map( + input: Expression::arrayFieldPath('tempsC'), + as: 'tempInCelsius', + in: Expression::add( + Expression::multiply( + Expression::variable('tempInCelsius'), + 1.8, + ), + 32, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::MapConvertCelsiusTemperaturesToFahrenheit, $pipeline); + } + + public function testTruncateEachArrayElement(): void + { + $pipeline = new Pipeline( + Stage::project( + city: Expression::stringFieldPath('city'), + integerValues: Expression::map( + input: Expression::arrayFieldPath('distances'), + as: 'decimalValue', + in: Expression::trunc( + Expression::variable('decimalValue'), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::MapTruncateEachArrayElement, $pipeline); + } +} diff --git a/tests/Builder/Expression/MaxNOperatorTest.php b/tests/Builder/Expression/MaxNOperatorTest.php new file mode 100644 index 000000000..1f94cd31f --- /dev/null +++ b/tests/Builder/Expression/MaxNOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::MaxNExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/MinNOperatorTest.php b/tests/Builder/Expression/MinNOperatorTest.php new file mode 100644 index 000000000..f6fc34eff --- /dev/null +++ b/tests/Builder/Expression/MinNOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::MinNExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ObjectToArrayOperatorTest.php b/tests/Builder/Expression/ObjectToArrayOperatorTest.php new file mode 100644 index 000000000..0abbde2ef --- /dev/null +++ b/tests/Builder/Expression/ObjectToArrayOperatorTest.php @@ -0,0 +1,53 @@ +assertSamePipeline(Pipelines::ObjectToArrayObjectToArrayExample, $pipeline); + } + + public function testObjectToArrayToSumNestedFields(): void + { + $pipeline = new Pipeline( + Stage::project( + warehouses: Expression::objectToArray( + Expression::objectFieldPath('instock'), + ), + ), + Stage::unwind( + Expression::arrayFieldPath('warehouses'), + ), + Stage::group( + _id: Expression::fieldPath('warehouses.k'), + total: Accumulator::sum( + Expression::fieldPath('warehouses.v'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ObjectToArrayObjectToArrayToSumNestedFields, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index cff869627..ca3835332 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -58,6 +58,247 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayElemAt/#example + */ + case ArrayElemAtExample = <<<'JSON' + [ + { + "$project": { + "name": { + "$numberInt": "1" + }, + "first": { + "$arrayElemAt": [ + "$favorites", + { + "$numberInt": "0" + } + ] + }, + "last": { + "$arrayElemAt": [ + "$favorites", + { + "$numberInt": "-1" + } + ] + } + } + } + ] + JSON; + + /** + * $arrayToObject Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayToObject/#-arraytoobject--example + */ + case ArrayToObjectArrayToObjectExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "dimensions": { + "$arrayToObject": [ + "$dimensions" + ] + } + } + } + ] + JSON; + + /** + * $objectToArray and $arrayToObject Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/arrayToObject/#-objecttoarray----arraytoobject-example + */ + case ArrayToObjectObjectToArrayAndArrayToObjectExample = <<<'JSON' + [ + { + "$addFields": { + "instock": { + "$objectToArray": "$instock" + } + } + }, + { + "$addFields": { + "instock": { + "$concatArrays": [ + "$instock", + [ + { + "k": "total", + "v": { + "$sum": [ + "$instock.v" + ] + } + } + ] + ] + } + } + }, + { + "$addFields": { + "instock": { + "$arrayToObject": [ + "$instock" + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/concatArrays/#example + */ + case ConcatArraysExample = <<<'JSON' + [ + { + "$project": { + "items": { + "$concatArrays": [ + "$instock", + "$ordered" + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/#examples + */ + case FilterExample = <<<'JSON' + [ + { + "$project": { + "items": { + "$filter": { + "input": "$items", + "as": "item", + "cond": { + "$gte": [ + "$$item.price", + { + "$numberInt": "100" + } + ] + } + } + } + } + } + ] + JSON; + + /** + * Using the limit field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/#using-the-limit-field + */ + case FilterUsingTheLimitField = <<<'JSON' + [ + { + "$project": { + "items": { + "$filter": { + "input": "$items", + "cond": { + "$gte": [ + "$$item.price", + { + "$numberInt": "100" + } + ] + }, + "as": "item", + "limit": { + "$numberInt": "1" + } + } + } + } + } + ] + JSON; + + /** + * limit as a Numeric Expression + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/#limit-as-a-numeric-expression + */ + case FilterLimitAsANumericExpression = <<<'JSON' + [ + { + "$project": { + "items": { + "$filter": { + "input": "$items", + "cond": { + "$lte": [ + "$$item.price", + { + "$numberInt": "150" + } + ] + }, + "as": "item", + "limit": { + "$numberInt": "2" + } + } + } + } + } + ] + JSON; + + /** + * limit Greater than Possible Matches + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/#limit-greater-than-possible-matches + */ + case FilterLimitGreaterThanPossibleMatches = <<<'JSON' + [ + { + "$project": { + "items": { + "$filter": { + "input": "$items", + "cond": { + "$gte": [ + "$$item.price", + { + "$numberInt": "100" + } + ] + }, + "as": "item", + "limit": { + "$numberInt": "5" + } + } + } + } + } + ] + JSON; + /** Use in $addFields Stage */ case FirstUseInAddFieldsStage = <<<'JSON' [ @@ -93,6 +334,88 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/in/#example + */ + case InExample = <<<'JSON' + [ + { + "$project": { + "store location": "$location", + "has bananas": { + "$in": [ + "bananas", + "$in_stock" + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfArray/#example + */ + case IndexOfArrayExample = <<<'JSON' + [ + { + "$project": { + "index": { + "$indexOfArray": [ + "$items", + { + "$numberInt": "2" + } + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isArray/#example + */ + case IsArrayExample = <<<'JSON' + [ + { + "$project": { + "items": { + "$cond": { + "if": { + "$and": [ + { + "$isArray": [ + "$instock" + ] + }, + { + "$isArray": [ + "$ordered" + ] + } + ] + }, + "then": { + "$concatArrays": [ + "$instock", + "$ordered" + ] + }, + "else": "One or more fields is not an array." + } + } + } + } + ] + JSON; + /** Use in $addFields Stage */ case LastUseInAddFieldsStage = <<<'JSON' [ @@ -171,41 +494,707 @@ enum Pipelines: string JSON; /** - * $mergeObjects + * Add to Each Element of an Array * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/#-mergeobjects + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/#add-to-each-element-of-an-array */ - case MergeObjectsMergeObjects = <<<'JSON' + case MapAddToEachElementOfAnArray = <<<'JSON' [ { - "$lookup": { - "from": "items", - "localField": "item", - "foreignField": "item", - "as": "fromItems" - } - }, - { - "$replaceRoot": { - "newRoot": { - "$mergeObjects": [ - { - "$arrayElemAt": [ - "$fromItems", + "$project": { + "adjustedGrades": { + "$map": { + "input": "$quizzes", + "as": "grade", + "in": { + "$add": [ + "$$grade", { - "$numberInt": "0" + "$numberInt": "2" } ] - }, - "$$ROOT" - ] + } + } } } - }, + } + ] + JSON; + + /** + * Truncate Each Array Element + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/#truncate-each-array-element + */ + case MapTruncateEachArrayElement = <<<'JSON' + [ { "$project": { - "fromItems": { - "$numberInt": "0" + "city": "$city", + "integerValues": { + "$map": { + "input": "$distances", + "as": "decimalValue", + "in": { + "$trunc": [ + "$$decimalValue" + ] + } + } + } + } + } + ] + JSON; + + /** + * Convert Celsius Temperatures to Fahrenheit + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/#convert-celsius-temperatures-to-fahrenheit + */ + case MapConvertCelsiusTemperaturesToFahrenheit = <<<'JSON' + [ + { + "$addFields": { + "tempsF": { + "$map": { + "input": "$tempsC", + "as": "tempInCelsius", + "in": { + "$add": [ + { + "$multiply": [ + "$$tempInCelsius", + { + "$numberDouble": "1.8000000000000000444" + } + ] + }, + { + "$numberInt": "32" + } + ] + } + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN-array-element/#example + */ + case MaxNExample = <<<'JSON' + [ + { + "$addFields": { + "maxScores": { + "$maxN": { + "n": { + "$numberInt": "2" + }, + "input": "$score" + } + } + } + } + ] + JSON; + + /** + * $mergeObjects + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/#-mergeobjects + */ + case MergeObjectsMergeObjects = <<<'JSON' + [ + { + "$lookup": { + "from": "items", + "localField": "item", + "foreignField": "item", + "as": "fromItems" + } + }, + { + "$replaceRoot": { + "newRoot": { + "$mergeObjects": [ + { + "$arrayElemAt": [ + "$fromItems", + { + "$numberInt": "0" + } + ] + }, + "$$ROOT" + ] + } + } + }, + { + "$project": { + "fromItems": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN-array-element/#example + */ + case MinNExample = <<<'JSON' + [ + { + "$addFields": { + "minScores": { + "$minN": { + "n": { + "$numberInt": "2" + }, + "input": "$score" + } + } + } + } + ] + JSON; + + /** + * $objectToArray Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/#-objecttoarray-example + */ + case ObjectToArrayObjectToArrayExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "dimensions": { + "$objectToArray": "$dimensions" + } + } + } + ] + JSON; + + /** + * $objectToArray to Sum Nested Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/objectToArray/#-objecttoarray-to-sum-nested-fields + */ + case ObjectToArrayObjectToArrayToSumNestedFields = <<<'JSON' + [ + { + "$project": { + "warehouses": { + "$objectToArray": "$instock" + } + } + }, + { + "$unwind": { + "path": "$warehouses" + } + }, + { + "$group": { + "_id": "$warehouses.k", + "total": { + "$sum": "$warehouses.v" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/range/#example + */ + case RangeExample = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "city": { + "$numberInt": "1" + }, + "Rest stops": { + "$range": [ + { + "$numberInt": "0" + }, + "$distance", + { + "$numberInt": "25" + } + ] + } + } + } + ] + JSON; + + /** + * Multiplication + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/#multiplication + */ + case ReduceMultiplication = <<<'JSON' + [ + { + "$group": { + "_id": "$experimentId", + "probabilityArr": { + "$push": "$probability" + } + } + }, + { + "$project": { + "description": { + "$numberInt": "1" + }, + "results": { + "$reduce": { + "input": "$probabilityArr", + "initialValue": { + "$numberInt": "1" + }, + "in": { + "$multiply": [ + "$$value", + "$$this" + ] + } + } + } + } + } + ] + JSON; + + /** + * Discounted Merchandise + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/#discounted-merchandise + */ + case ReduceDiscountedMerchandise = <<<'JSON' + [ + { + "$project": { + "discountedPrice": { + "$reduce": { + "input": "$discounts", + "initialValue": "$price", + "in": { + "$multiply": [ + "$$value", + { + "$subtract": [ + { + "$numberInt": "1" + }, + "$$this" + ] + } + ] + } + } + } + } + } + ] + JSON; + + /** + * String Concatenation + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/#string-concatenation + */ + case ReduceStringConcatenation = <<<'JSON' + [ + { + "$match": { + "hobbies": { + "$gt": [] + } + } + }, + { + "$project": { + "name": { + "$numberInt": "1" + }, + "bio": { + "$reduce": { + "input": "$hobbies", + "initialValue": "My hobbies include:", + "in": { + "$concat": [ + "$$value", + { + "$cond": { + "if": { + "$eq": [ + "$$value", + "My hobbies include:" + ] + }, + "then": " ", + "else": ", " + } + }, + "$$this" + ] + } + } + } + } + } + ] + JSON; + + /** + * Array Concatenation + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/#array-concatenation + */ + case ReduceArrayConcatenation = <<<'JSON' + [ + { + "$project": { + "collapsed": { + "$reduce": { + "input": "$arr", + "initialValue": [], + "in": { + "$concatArrays": [ + "$$value", + "$$this" + ] + } + } + } + } + } + ] + JSON; + + /** + * Computing a Multiple Reductions + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/reduce/#computing-a-multiple-reductions + */ + case ReduceComputingAMultipleReductions = <<<'JSON' + [ + { + "$project": { + "results": { + "$reduce": { + "input": "$arr", + "initialValue": [], + "in": { + "collapsed": { + "$concatArrays": [ + "$$value.collapsed", + "$$this" + ] + }, + "firstValues": { + "$concatArrays": [ + "$$value.firstValues", + { + "$slice": [ + "$$this", + { + "$numberInt": "1" + } + ] + } + ] + } + } + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/reverseArray/#example + */ + case ReverseArrayExample = <<<'JSON' + [ + { + "$project": { + "name": { + "$numberInt": "1" + }, + "reverseFavorites": { + "$reverseArray": "$favorites" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/size/#example + */ + case SizeExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "numberOfColors": { + "$cond": { + "if": { + "$isArray": [ + "$colors" + ] + }, + "then": { + "$size": "$colors" + }, + "else": "NA" + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/slice/#example + */ + case SliceExample = <<<'JSON' + [ + { + "$project": { + "name": { + "$numberInt": "1" + }, + "threeFavorites": { + "$slice": [ + "$favorites", + { + "$numberInt": "3" + } + ] + } + } + } + ] + JSON; + + /** + * Sort on a Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-a-field + */ + case SortArraySortOnAField = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "result": { + "$sortArray": { + "input": "$team", + "sortBy": { + "name": { + "$numberInt": "1" + } + } + } + } + } + } + ] + JSON; + + /** + * Sort on a Subfield + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-a-subfield + */ + case SortArraySortOnASubfield = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "result": { + "$sortArray": { + "input": "$team", + "sortBy": { + "address.city": { + "$numberInt": "-1" + } + } + } + } + } + } + ] + JSON; + + /** + * Sort on Multiple Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-multiple-fields + */ + case SortArraySortOnMultipleFields = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "result": { + "$sortArray": { + "input": "$team", + "sortBy": { + "age": { + "$numberInt": "-1" + }, + "name": { + "$numberInt": "1" + } + } + } + } + } + } + ] + JSON; + + /** + * Sort an Array of Integers + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-an-array-of-integers + */ + case SortArraySortAnArrayOfIntegers = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "result": { + "$sortArray": { + "input": [ + { + "$numberInt": "1" + }, + { + "$numberInt": "4" + }, + { + "$numberInt": "1" + }, + { + "$numberInt": "6" + }, + { + "$numberInt": "12" + }, + { + "$numberInt": "5" + } + ], + "sortBy": { + "$numberInt": "1" + } + } + } + } + } + ] + JSON; + + /** + * Sort on Mixed Type Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-mixed-type-fields + */ + case SortArraySortOnMixedTypeFields = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "result": { + "$sortArray": { + "input": [ + { + "$numberInt": "20" + }, + { + "$numberInt": "4" + }, + { + "a": "Free" + }, + { + "$numberInt": "6" + }, + { + "$numberInt": "21" + }, + { + "$numberInt": "5" + }, + "Gratis", + { + "a": null + }, + { + "a": { + "sale": true, + "price": { + "$numberInt": "19" + } + } + }, + { + "$numberDouble": "10.230000000000000426" + }, + { + "a": "On sale" + } + ], + "sortBy": { + "$numberInt": "1" + } + } } } } diff --git a/tests/Builder/Expression/RangeOperatorTest.php b/tests/Builder/Expression/RangeOperatorTest.php new file mode 100644 index 000000000..be9a1a3a7 --- /dev/null +++ b/tests/Builder/Expression/RangeOperatorTest.php @@ -0,0 +1,36 @@ + Expression::range( + 0, + Expression::intFieldPath('distance'), + 25, + ), + ], + _id: 0, + city: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::RangeExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ReduceOperatorTest.php b/tests/Builder/Expression/ReduceOperatorTest.php new file mode 100644 index 000000000..ee1f70720 --- /dev/null +++ b/tests/Builder/Expression/ReduceOperatorTest.php @@ -0,0 +1,141 @@ +assertSamePipeline(Pipelines::ReduceArrayConcatenation, $pipeline); + } + + public function testComputingAMultipleReductions(): void + { + $pipeline = new Pipeline( + Stage::project( + results: Expression::reduce( + Expression::arrayFieldPath('arr'), + [], + object( + collapsed: Expression::concatArrays( + Expression::variable('value.collapsed'), + Expression::variable('this'), + ), + firstValues: Expression::concatArrays( + Expression::variable('value.firstValues'), + Expression::slice( + Expression::variable('this'), + 1, + ), + ), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReduceComputingAMultipleReductions, $pipeline); + } + + public function testDiscountedMerchandise(): void + { + $pipeline = new Pipeline( + Stage::project( + discountedPrice: Expression::reduce( + input: Expression::arrayFieldPath('discounts'), + initialValue: Expression::numberFieldPath('price'), + in: Expression::multiply( + Expression::variable('value'), + Expression::subtract( + 1, + Expression::variable('this'), + ), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReduceDiscountedMerchandise, $pipeline); + } + + public function testMultiplication(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::objectIdFieldPath('experimentId'), + probabilityArr: Accumulator::push( + Expression::fieldPath('probability'), + ), + ), + Stage::project( + description: 1, + results: Expression::reduce( + input: Expression::arrayFieldPath('probabilityArr'), + initialValue: 1, + in: Expression::multiply( + Expression::variable('value'), + Expression::variable('this'), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReduceMultiplication, $pipeline); + } + + public function testStringConcatenation(): void + { + $pipeline = new Pipeline( + Stage::match( + hobbies: Query::gt([]), + ), + Stage::project( + name: 1, + bio: Expression::reduce( + input: Expression::arrayFieldPath('hobbies'), + initialValue: 'My hobbies include:', + in: Expression::concat( + Expression::variable('value'), + Expression::cond( + if: Expression::eq( + Expression::variable('value'), + 'My hobbies include:', + ), + then: ' ', + else: ', ', + ), + Expression::variable('this'), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReduceStringConcatenation, $pipeline); + } +} diff --git a/tests/Builder/Expression/ReverseArrayOperatorTest.php b/tests/Builder/Expression/ReverseArrayOperatorTest.php new file mode 100644 index 000000000..f620cb4cc --- /dev/null +++ b/tests/Builder/Expression/ReverseArrayOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::ReverseArrayExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SizeOperatorTest.php b/tests/Builder/Expression/SizeOperatorTest.php new file mode 100644 index 000000000..edd2c6cb4 --- /dev/null +++ b/tests/Builder/Expression/SizeOperatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::SizeExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SliceOperatorTest.php b/tests/Builder/Expression/SliceOperatorTest.php new file mode 100644 index 000000000..b6326b051 --- /dev/null +++ b/tests/Builder/Expression/SliceOperatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::SliceExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SortArrayOperatorTest.php b/tests/Builder/Expression/SortArrayOperatorTest.php new file mode 100644 index 000000000..a43887dca --- /dev/null +++ b/tests/Builder/Expression/SortArrayOperatorTest.php @@ -0,0 +1,115 @@ +assertSamePipeline(Pipelines::SortArraySortAnArrayOfIntegers, $pipeline); + } + + public function testSortOnAField(): void + { + $pipeline = new Pipeline( + Stage::project( + _id: 0, + result: Expression::sortArray( + input: Expression::arrayFieldPath('team'), + // @todo This object should be typed as "sort spec" + sortBy: object( + name: 1, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SortArraySortOnAField, $pipeline); + } + + public function testSortOnASubfield(): void + { + $pipeline = new Pipeline( + Stage::project( + _id: 0, + result: Expression::sortArray( + input: Expression::arrayFieldPath('team'), + // @todo This array should be typed as "sort spec" + sortBy: [ + 'address.city' => -1, + ], + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SortArraySortOnASubfield, $pipeline); + } + + public function testSortOnMixedTypeFields(): void + { + $pipeline = new Pipeline( + Stage::project( + _id: 0, + result: Expression::sortArray( + input: [ + 20, + 4, + object(a: 'Free'), + 6, + 21, + 5, + 'Gratis', + ['a' => null], + object(a: object(sale: true, price: 19)), + 10.23, + ['a' => 'On sale'], + ], + sortBy: 1, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SortArraySortOnMixedTypeFields, $pipeline); + } + + public function testSortOnMultipleFields(): void + { + $pipeline = new Pipeline( + Stage::project( + _id: 0, + result: Expression::sortArray( + input: Expression::arrayFieldPath('team'), + // @todo This array should be typed as "sort spec" + sortBy: object( + age: -1, + name: 1, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SortArraySortOnMultipleFields, $pipeline); + } +} From 7b3e2caac32b8c0836bbe98113ac119c59c10969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 16 Jan 2024 11:47:30 +0100 Subject: [PATCH 23/95] PHPLIB-1344 Add tests on Bitwise Operators (#26) --- generator/config/expression/bitAnd.yaml | 21 +++ generator/config/expression/bitNot.yaml | 9 ++ generator/config/expression/bitOr.yaml | 21 +++ generator/config/expression/bitXor.yaml | 11 ++ .../Builder/Expression/BitAndOperatorTest.php | 45 +++++++ .../Builder/Expression/BitNotOperatorTest.php | 29 +++++ .../Builder/Expression/BitOrOperatorTest.php | 45 +++++++ .../Builder/Expression/BitXorOperatorTest.php | 30 +++++ tests/Builder/Expression/Pipelines.php | 121 ++++++++++++++++++ 9 files changed, 332 insertions(+) create mode 100644 tests/Builder/Expression/BitAndOperatorTest.php create mode 100644 tests/Builder/Expression/BitNotOperatorTest.php create mode 100644 tests/Builder/Expression/BitOrOperatorTest.php create mode 100644 tests/Builder/Expression/BitXorOperatorTest.php diff --git a/generator/config/expression/bitAnd.yaml b/generator/config/expression/bitAnd.yaml index 7a412145c..271cc0973 100644 --- a/generator/config/expression/bitAnd.yaml +++ b/generator/config/expression/bitAnd.yaml @@ -15,3 +15,24 @@ arguments: - resolvesToInt - resolvesToLong variadic: array +tests: + - + name: 'Bitwise AND with Two Integers' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitAnd/#bitwise-and-with-two-integers' + pipeline: + - + $project: + result: + $bitAnd: + - '$a' + - '$b' + - + name: 'Bitwise AND with a Long and Integer' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitAnd/#bitwise-and-with-a-long-and-integer' + pipeline: + - + $project: + result: + $bitAnd: + - '$a' + - { "$numberLong": "63" } diff --git a/generator/config/expression/bitNot.yaml b/generator/config/expression/bitNot.yaml index ebd937899..5211fa42a 100644 --- a/generator/config/expression/bitNot.yaml +++ b/generator/config/expression/bitNot.yaml @@ -14,3 +14,12 @@ arguments: type: - resolvesToInt - resolvesToLong +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitNot/#example' + pipeline: + - + $project: + result: + $bitNot: '$a' diff --git a/generator/config/expression/bitOr.yaml b/generator/config/expression/bitOr.yaml index afd187fff..084ac224c 100644 --- a/generator/config/expression/bitOr.yaml +++ b/generator/config/expression/bitOr.yaml @@ -15,3 +15,24 @@ arguments: - resolvesToInt - resolvesToLong variadic: array +tests: + - + name: 'Bitwise OR with Two Integers' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitOr/#bitwise-or-with-two-integers' + pipeline: + - + $project: + result: + $bitOr: + - '$a' + - '$b' + - + name: 'Bitwise OR with a Long and Integer' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitOr/#bitwise-or-with-a-long-and-integer' + pipeline: + - + $project: + result: + $bitOr: + - '$a' + - { "$numberLong": "63" } diff --git a/generator/config/expression/bitXor.yaml b/generator/config/expression/bitXor.yaml index eafd0bd27..f4acc4df4 100644 --- a/generator/config/expression/bitXor.yaml +++ b/generator/config/expression/bitXor.yaml @@ -15,3 +15,14 @@ arguments: - resolvesToInt - resolvesToLong variadic: array +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitXor/#example' + pipeline: + - + $project: + result: + $bitXor: + - '$a' + - '$b' diff --git a/tests/Builder/Expression/BitAndOperatorTest.php b/tests/Builder/Expression/BitAndOperatorTest.php new file mode 100644 index 000000000..e46fbb55a --- /dev/null +++ b/tests/Builder/Expression/BitAndOperatorTest.php @@ -0,0 +1,45 @@ +assertSamePipeline(Pipelines::BitAndBitwiseANDWithALongAndInteger, $pipeline); + } + + public function testBitwiseANDWithTwoIntegers(): void + { + $pipeline = new Pipeline( + Stage::project( + result: Expression::bitAnd( + Expression::intFieldPath('a'), + Expression::intFieldPath('b'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::BitAndBitwiseANDWithTwoIntegers, $pipeline); + } +} diff --git a/tests/Builder/Expression/BitNotOperatorTest.php b/tests/Builder/Expression/BitNotOperatorTest.php new file mode 100644 index 000000000..9407a2861 --- /dev/null +++ b/tests/Builder/Expression/BitNotOperatorTest.php @@ -0,0 +1,29 @@ +assertSamePipeline(Pipelines::BitNotExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/BitOrOperatorTest.php b/tests/Builder/Expression/BitOrOperatorTest.php new file mode 100644 index 000000000..2558432e9 --- /dev/null +++ b/tests/Builder/Expression/BitOrOperatorTest.php @@ -0,0 +1,45 @@ +assertSamePipeline(Pipelines::BitOrBitwiseORWithALongAndInteger, $pipeline); + } + + public function testBitwiseORWithTwoIntegers(): void + { + $pipeline = new Pipeline( + Stage::project( + result: Expression::bitOr( + Expression::intFieldPath('a'), + Expression::intFieldPath('b'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::BitOrBitwiseORWithTwoIntegers, $pipeline); + } +} diff --git a/tests/Builder/Expression/BitXorOperatorTest.php b/tests/Builder/Expression/BitXorOperatorTest.php new file mode 100644 index 000000000..1fffe2c18 --- /dev/null +++ b/tests/Builder/Expression/BitXorOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::BitXorExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index ca3835332..c356589a7 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -158,6 +158,127 @@ enum Pipelines: string ] JSON; + /** + * Bitwise AND with Two Integers + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitAnd/#bitwise-and-with-two-integers + */ + case BitAndBitwiseANDWithTwoIntegers = <<<'JSON' + [ + { + "$project": { + "result": { + "$bitAnd": [ + "$a", + "$b" + ] + } + } + } + ] + JSON; + + /** + * Bitwise AND with a Long and Integer + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitAnd/#bitwise-and-with-a-long-and-integer + */ + case BitAndBitwiseANDWithALongAndInteger = <<<'JSON' + [ + { + "$project": { + "result": { + "$bitAnd": [ + "$a", + { + "$numberLong": "63" + } + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitNot/#example + */ + case BitNotExample = <<<'JSON' + [ + { + "$project": { + "result": { + "$bitNot": "$a" + } + } + } + ] + JSON; + + /** + * Bitwise OR with Two Integers + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitOr/#bitwise-or-with-two-integers + */ + case BitOrBitwiseORWithTwoIntegers = <<<'JSON' + [ + { + "$project": { + "result": { + "$bitOr": [ + "$a", + "$b" + ] + } + } + } + ] + JSON; + + /** + * Bitwise OR with a Long and Integer + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitOr/#bitwise-or-with-a-long-and-integer + */ + case BitOrBitwiseORWithALongAndInteger = <<<'JSON' + [ + { + "$project": { + "result": { + "$bitOr": [ + "$a", + { + "$numberLong": "63" + } + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bitXor/#example + */ + case BitXorExample = <<<'JSON' + [ + { + "$project": { + "result": { + "$bitXor": [ + "$a", + "$b" + ] + } + } + } + ] + JSON; + /** * Example * From cd27d8dccd08f171eafdf52bea6c7129272790e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 16 Jan 2024 11:47:51 +0100 Subject: [PATCH 24/95] PHPLIB-1345 Add tests on Boolean Expression Operators (#27) --- generator/config/expression/and.yaml | 19 ++++ generator/config/expression/not.yaml | 16 ++- generator/config/expression/or.yaml | 18 ++++ src/Builder/Expression/NotOperator.php | 2 +- tests/Builder/Expression/AndOperatorTest.php | 38 +++++++ tests/Builder/Expression/NotOperatorTest.php | 33 ++++++ tests/Builder/Expression/OrOperatorTest.php | 37 +++++++ tests/Builder/Expression/Pipelines.php | 106 +++++++++++++++++++ 8 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 tests/Builder/Expression/AndOperatorTest.php create mode 100644 tests/Builder/Expression/NotOperatorTest.php create mode 100644 tests/Builder/Expression/OrOperatorTest.php diff --git a/generator/config/expression/and.yaml b/generator/config/expression/and.yaml index 511ef776d..96057d249 100644 --- a/generator/config/expression/and.yaml +++ b/generator/config/expression/and.yaml @@ -16,3 +16,22 @@ arguments: - resolvesToString - resolvesToNull variadic: array +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/and/#example' + pipeline: + - + $project: + item: 1 + qty: 1 + result: + $and: + - + $gt: + - '$qty' + - 100 + - + $lt: + - '$qty' + - 250 diff --git a/generator/config/expression/not.yaml b/generator/config/expression/not.yaml index 9c20a5fbc..d4e4c90ea 100644 --- a/generator/config/expression/not.yaml +++ b/generator/config/expression/not.yaml @@ -3,7 +3,7 @@ name: $not link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/not/' type: - resolvesToBool -encode: single +encode: array description: | Returns the boolean value that is the opposite of its argument expression. Accepts a single argument expression. arguments: @@ -12,3 +12,17 @@ arguments: type: - expression - resolvesToBool +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/not/#example' + pipeline: + - + $project: + item: 1 + result: + $not: + - + $gt: + - '$qty' + - 250 diff --git a/generator/config/expression/or.yaml b/generator/config/expression/or.yaml index 6525041f3..2bbce1910 100644 --- a/generator/config/expression/or.yaml +++ b/generator/config/expression/or.yaml @@ -13,3 +13,21 @@ arguments: - expression - resolvesToBool variadic: array +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/or/#example' + pipeline: + - + $project: + item: 1 + result: + $or: + - + $gt: + - '$qty' + - 250 + - + $lt: + - '$qty' + - 200 diff --git a/src/Builder/Expression/NotOperator.php b/src/Builder/Expression/NotOperator.php index 6ed151c0c..9934c35a2 100644 --- a/src/Builder/Expression/NotOperator.php +++ b/src/Builder/Expression/NotOperator.php @@ -21,7 +21,7 @@ */ class NotOperator implements ResolvesToBool, OperatorInterface { - public const ENCODE = Encode::Single; + public const ENCODE = Encode::Array; /** @var ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ public readonly Type|ResolvesToBool|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; diff --git a/tests/Builder/Expression/AndOperatorTest.php b/tests/Builder/Expression/AndOperatorTest.php new file mode 100644 index 000000000..3956d29e0 --- /dev/null +++ b/tests/Builder/Expression/AndOperatorTest.php @@ -0,0 +1,38 @@ +assertSamePipeline(Pipelines::AndExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/NotOperatorTest.php b/tests/Builder/Expression/NotOperatorTest.php new file mode 100644 index 000000000..cd816a0a7 --- /dev/null +++ b/tests/Builder/Expression/NotOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::NotExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/OrOperatorTest.php b/tests/Builder/Expression/OrOperatorTest.php new file mode 100644 index 000000000..feb544c1c --- /dev/null +++ b/tests/Builder/Expression/OrOperatorTest.php @@ -0,0 +1,37 @@ +assertSamePipeline(Pipelines::OrExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index c356589a7..0f58f9572 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -58,6 +58,46 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/and/#example + */ + case AndExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "qty": { + "$numberInt": "1" + }, + "result": { + "$and": [ + { + "$gt": [ + "$qty", + { + "$numberInt": "100" + } + ] + }, + { + "$lt": [ + "$qty", + { + "$numberInt": "250" + } + ] + } + ] + } + } + } + ] + JSON; + /** * Example * @@ -789,6 +829,35 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/not/#example + */ + case NotExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "result": { + "$not": [ + { + "$gt": [ + "$qty", + { + "$numberInt": "250" + } + ] + } + ] + } + } + } + ] + JSON; + /** * $objectToArray Example * @@ -839,6 +908,43 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/or/#example + */ + case OrExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "result": { + "$or": [ + { + "$gt": [ + "$qty", + { + "$numberInt": "250" + } + ] + }, + { + "$lt": [ + "$qty", + { + "$numberInt": "200" + } + ] + } + ] + } + } + } + ] + JSON; + /** * Example * From 3caabb0345ee121b3037cff227c587f4081fdfde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 16 Jan 2024 12:26:30 +0100 Subject: [PATCH 25/95] PHPLIB-1346 Add tests on Comparison Expression Operators (#28) --- generator/config/expression/cmp.yaml | 14 ++ generator/config/expression/eq.yaml | 14 ++ generator/config/expression/gt.yaml | 14 ++ generator/config/expression/gte.yaml | 14 ++ generator/config/expression/lt.yaml | 14 ++ generator/config/expression/lte.yaml | 14 ++ generator/config/expression/ne.yaml | 14 ++ tests/Builder/Expression/CmpOperatorTest.php | 33 +++ tests/Builder/Expression/EqOperatorTest.php | 33 +++ tests/Builder/Expression/GtOperatorTest.php | 33 +++ tests/Builder/Expression/GteOperatorTest.php | 33 +++ tests/Builder/Expression/LtOperatorTest.php | 33 +++ tests/Builder/Expression/LteOperatorTest.php | 33 +++ tests/Builder/Expression/NeOperatorTest.php | 33 +++ tests/Builder/Expression/Pipelines.php | 217 +++++++++++++++++++ 15 files changed, 546 insertions(+) create mode 100644 tests/Builder/Expression/CmpOperatorTest.php create mode 100644 tests/Builder/Expression/EqOperatorTest.php create mode 100644 tests/Builder/Expression/GtOperatorTest.php create mode 100644 tests/Builder/Expression/GteOperatorTest.php create mode 100644 tests/Builder/Expression/LtOperatorTest.php create mode 100644 tests/Builder/Expression/LteOperatorTest.php create mode 100644 tests/Builder/Expression/NeOperatorTest.php diff --git a/generator/config/expression/cmp.yaml b/generator/config/expression/cmp.yaml index 5c70e13f9..dd24f9839 100644 --- a/generator/config/expression/cmp.yaml +++ b/generator/config/expression/cmp.yaml @@ -15,3 +15,17 @@ arguments: name: expression2 type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/cmp/#example' + pipeline: + - + $project: + item: 1 + qty: 1 + cmpTo250: + $cmp: + - '$qty' + - 250 + _id: 0 diff --git a/generator/config/expression/eq.yaml b/generator/config/expression/eq.yaml index 83260d4f6..009280ba1 100644 --- a/generator/config/expression/eq.yaml +++ b/generator/config/expression/eq.yaml @@ -15,3 +15,17 @@ arguments: name: expression2 type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/eq/#example' + pipeline: + - + $project: + item: 1 + qty: 1 + qtyEq250: + $eq: + - '$qty' + - 250 + _id: 0 diff --git a/generator/config/expression/gt.yaml b/generator/config/expression/gt.yaml index 070deba36..ddd4dcdd0 100644 --- a/generator/config/expression/gt.yaml +++ b/generator/config/expression/gt.yaml @@ -15,3 +15,17 @@ arguments: name: expression2 type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/gt/#example' + pipeline: + - + $project: + item: 1 + qty: 1 + qtyGt250: + $gt: + - '$qty' + - 250 + _id: 0 diff --git a/generator/config/expression/gte.yaml b/generator/config/expression/gte.yaml index a62838c82..6b456daf6 100644 --- a/generator/config/expression/gte.yaml +++ b/generator/config/expression/gte.yaml @@ -15,3 +15,17 @@ arguments: name: expression2 type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/gte/#example' + pipeline: + - + $project: + item: 1 + qty: 1 + qtyGte250: + $gte: + - '$qty' + - 250 + _id: 0 diff --git a/generator/config/expression/lt.yaml b/generator/config/expression/lt.yaml index 90e12d2ac..4b7319b97 100644 --- a/generator/config/expression/lt.yaml +++ b/generator/config/expression/lt.yaml @@ -15,3 +15,17 @@ arguments: name: expression2 type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lt/#example' + pipeline: + - + $project: + item: 1 + qty: 1 + qtyLt250: + $lt: + - '$qty' + - 250 + _id: 0 diff --git a/generator/config/expression/lte.yaml b/generator/config/expression/lte.yaml index b71d04617..91c7b1d88 100644 --- a/generator/config/expression/lte.yaml +++ b/generator/config/expression/lte.yaml @@ -15,3 +15,17 @@ arguments: name: expression2 type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lte/#example' + pipeline: + - + $project: + item: 1 + qty: 1 + qtyLte250: + $lte: + - '$qty' + - 250 + _id: 0 diff --git a/generator/config/expression/ne.yaml b/generator/config/expression/ne.yaml index f4e7ab02c..da92f1014 100644 --- a/generator/config/expression/ne.yaml +++ b/generator/config/expression/ne.yaml @@ -15,3 +15,17 @@ arguments: name: expression2 type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/ne/#example' + pipeline: + - + $project: + item: 1 + qty: 1 + qtyNe250: + $ne: + - '$qty' + - 250 + _id: 0 diff --git a/tests/Builder/Expression/CmpOperatorTest.php b/tests/Builder/Expression/CmpOperatorTest.php new file mode 100644 index 000000000..e7bb5bc09 --- /dev/null +++ b/tests/Builder/Expression/CmpOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::CmpExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/EqOperatorTest.php b/tests/Builder/Expression/EqOperatorTest.php new file mode 100644 index 000000000..0c9b72bef --- /dev/null +++ b/tests/Builder/Expression/EqOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::EqExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/GtOperatorTest.php b/tests/Builder/Expression/GtOperatorTest.php new file mode 100644 index 000000000..a8cecfd84 --- /dev/null +++ b/tests/Builder/Expression/GtOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::GtExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/GteOperatorTest.php b/tests/Builder/Expression/GteOperatorTest.php new file mode 100644 index 000000000..abc89743f --- /dev/null +++ b/tests/Builder/Expression/GteOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::GteExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/LtOperatorTest.php b/tests/Builder/Expression/LtOperatorTest.php new file mode 100644 index 000000000..86a7815aa --- /dev/null +++ b/tests/Builder/Expression/LtOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::LtExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/LteOperatorTest.php b/tests/Builder/Expression/LteOperatorTest.php new file mode 100644 index 000000000..4a65cd593 --- /dev/null +++ b/tests/Builder/Expression/LteOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::LteExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/NeOperatorTest.php b/tests/Builder/Expression/NeOperatorTest.php new file mode 100644 index 000000000..b052be640 --- /dev/null +++ b/tests/Builder/Expression/NeOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::NeExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 0f58f9572..192ea4049 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -319,6 +319,37 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/cmp/#example + */ + case CmpExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "qty": { + "$numberInt": "1" + }, + "cmpTo250": { + "$cmp": [ + "$qty", + { + "$numberInt": "250" + } + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + /** * Example * @@ -339,6 +370,37 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/eq/#example + */ + case EqExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "qty": { + "$numberInt": "1" + }, + "qtyEq250": { + "$eq": [ + "$qty", + { + "$numberInt": "250" + } + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + /** * Example * @@ -495,6 +557,68 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/gt/#example + */ + case GtExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "qty": { + "$numberInt": "1" + }, + "qtyGt250": { + "$gt": [ + "$qty", + { + "$numberInt": "250" + } + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/gte/#example + */ + case GteExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "qty": { + "$numberInt": "1" + }, + "qtyGte250": { + "$gte": [ + "$qty", + { + "$numberInt": "250" + } + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + /** * Example * @@ -654,6 +778,68 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lt/#example + */ + case LtExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "qty": { + "$numberInt": "1" + }, + "qtyLt250": { + "$lt": [ + "$qty", + { + "$numberInt": "250" + } + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lte/#example + */ + case LteExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "qty": { + "$numberInt": "1" + }, + "qtyLte250": { + "$lte": [ + "$qty", + { + "$numberInt": "250" + } + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + /** * Add to Each Element of an Array * @@ -829,6 +1015,37 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ne/#example + */ + case NeExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "qty": { + "$numberInt": "1" + }, + "qtyNe250": { + "$ne": [ + "$qty", + { + "$numberInt": "250" + } + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + /** * Example * From 320cc263e6f2c4c242bf1d759c3ac6a668fc0a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 16 Jan 2024 12:26:44 +0100 Subject: [PATCH 26/95] PHPLIB-1336 Add tests on Evaluation Query Operators (#24) --- generator/config/query/jsonSchema.yaml | 26 ++ generator/config/query/mod.yaml | 29 +- generator/config/query/text.yaml | 76 ++++- generator/config/query/where.yaml | 18 ++ generator/config/schema.json | 1 + .../src/Definition/OperatorDefinition.php | 1 + src/Builder/BuilderEncoder.php | 18 ++ src/Builder/Query/FactoryTrait.php | 9 +- src/Builder/Query/ModOperator.php | 16 +- src/Builder/Query/TextOperator.php | 2 +- src/Builder/Type/Encode.php | 5 + .../Builder/Query/JsonSchemaOperatorTest.php | 45 +++ tests/Builder/Query/ModOperatorTest.php | 44 +++ tests/Builder/Query/Pipelines.php | 292 ++++++++++++++++++ tests/Builder/Query/TextOperatorTest.php | 120 +++++++ tests/Builder/Query/WhereOperatorTest.php | 41 +++ 16 files changed, 729 insertions(+), 14 deletions(-) create mode 100644 tests/Builder/Query/JsonSchemaOperatorTest.php create mode 100644 tests/Builder/Query/ModOperatorTest.php create mode 100644 tests/Builder/Query/TextOperatorTest.php create mode 100644 tests/Builder/Query/WhereOperatorTest.php diff --git a/generator/config/query/jsonSchema.yaml b/generator/config/query/jsonSchema.yaml index 88af08c0a..4c1dca6ad 100644 --- a/generator/config/query/jsonSchema.yaml +++ b/generator/config/query/jsonSchema.yaml @@ -11,3 +11,29 @@ arguments: name: schema type: - object +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/jsonSchema/#syntax' + pipeline: + - + $match: + $jsonSchema: + required: + - 'name' + - 'major' + - 'gpa' + - 'address' + properties: + name: + bsonType: 'string' + description: 'must be a string and is required' + address: + bsonType: 'object' + required: + - 'zipcode' + properties: + street: + bsonType: 'string' + zipcode: + bsonType: 'string' diff --git a/generator/config/query/mod.yaml b/generator/config/query/mod.yaml index 291c6a61f..04a187253 100644 --- a/generator/config/query/mod.yaml +++ b/generator/config/query/mod.yaml @@ -10,8 +10,33 @@ arguments: - name: divisor type: - - int + - number - name: remainder type: - - int + - number +tests: + - + name: 'Use $mod to Select Documents' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/mod/#use--mod-to-select-documents' + pipeline: + - + $match: + qty: + $mod: [4, 0] + - + name: 'Floating Point Arguments' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/mod/#floating-point-arguments' + pipeline: + - + $match: + qty: + $mod: [4.0, 0] + - + $match: + qty: + $mod: [4.5, 0] + - + $match: + qty: + $mod: [4.99, 0] diff --git a/generator/config/query/text.yaml b/generator/config/query/text.yaml index c057cee5d..a2709737e 100644 --- a/generator/config/query/text.yaml +++ b/generator/config/query/text.yaml @@ -3,7 +3,7 @@ name: $text link: 'https://www.mongodb.com/docs/manual/reference/operator/query/text/' type: - query -encode: object +encode: dollar_object description: | Performs text search. arguments: @@ -36,3 +36,77 @@ arguments: description: | A boolean flag to enable or disable diacritic sensitive search against version 3 text indexes. Defaults to false; i.e. the search defers to the diacritic insensitivity of the text index. Text searches against earlier versions of the text index are inherently diacritic sensitive and cannot be diacritic insensitive. As such, the $diacriticSensitive option has no effect with earlier versions of the text index. +tests: + - + name: 'Search for a Single Word' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/text/#search-for-a-single-word' + pipeline: + - + $match: + $text: + $search: 'coffee' + - + name: 'Match Any of the Search Terms' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/text/#search-for-a-single-word' + pipeline: + - + $match: + $text: + $search: 'bake coffee cake' + - + name: 'Search a Different Language' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/text/#search-a-different-language' + pipeline: + - + $match: + $text: + $search: 'leche' + $language: 'es' + - + name: 'Case and Diacritic Insensitive Search' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/text/#case-and-diacritic-insensitive-search' + pipeline: + - + $match: + $text: + $search: 'сы́рники CAFÉS' + - + name: 'Perform Case Sensitive Search' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/text/#perform-case-sensitive-search' + pipeline: + - + $match: + $text: + $search: 'Coffee' + $caseSensitive: true + - + $match: + $text: + $search: '\"Café Con Leche\"' + $caseSensitive: true + - + name: 'Diacritic Sensitive Search' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/text/#perform-case-sensitive-search' + pipeline: + - + $match: + $text: + $search: 'CAFÉ' + $diacriticSensitive: true + - + name: 'Text Search Score Examples' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/text/#perform-case-sensitive-search' + pipeline: + - + $match: + $text: + $search: 'CAFÉ' + $diacriticSensitive: true + score: + $meta: 'textScore' + - + $sort: + score: + $meta: 'textScore' + - + $limit: 5 diff --git a/generator/config/query/where.yaml b/generator/config/query/where.yaml index 5f353283c..3c63007ca 100644 --- a/generator/config/query/where.yaml +++ b/generator/config/query/where.yaml @@ -11,3 +11,21 @@ arguments: name: function type: - javascript +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/where/#example' + pipeline: + - + $match: + $where: 'function() { return hex_md5(this.name) == "9b53e667f30cd329dca1ec9e6a83e994" }' + - + $match: + $expr: + $function: + body: |- + function(name) { + return hex_md5(name) == "9b53e667f30cd329dca1ec9e6a83e994"; + } + args: ['$name'] + lang: 'js' diff --git a/generator/config/schema.json b/generator/config/schema.json index 02091080c..7a475a863 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -60,6 +60,7 @@ "enum": [ "array", "object", + "dollar_object", "single", "group" ] diff --git a/generator/src/Definition/OperatorDefinition.php b/generator/src/Definition/OperatorDefinition.php index f97c52212..098d10185 100644 --- a/generator/src/Definition/OperatorDefinition.php +++ b/generator/src/Definition/OperatorDefinition.php @@ -38,6 +38,7 @@ public function __construct( 'single' => Encode::Single, 'array' => Encode::Array, 'object' => Encode::Object, + 'dollar_object' => Encode::DollarObject, 'group' => Encode::Group, default => throw new UnexpectedValueException(sprintf('Unexpected "encode" value for operator "%s". Got "%s"', $name, $encode)), }; diff --git a/src/Builder/BuilderEncoder.php b/src/Builder/BuilderEncoder.php index da4bd0596..ec566e1d8 100644 --- a/src/Builder/BuilderEncoder.php +++ b/src/Builder/BuilderEncoder.php @@ -111,6 +111,9 @@ public function encode($value): stdClass|array|string case Encode::Object: return $this->encodeAsObject($value); + case Encode::DollarObject: + return $this->encodeAsDollarObject($value); + case Encode::Group: assert($value instanceof GroupStage); @@ -170,6 +173,21 @@ private function encodeAsObject(OperatorInterface $value): stdClass return $this->wrap($value, $result); } + private function encodeAsDollarObject(OperatorInterface $value): stdClass + { + $result = new stdClass(); + foreach (get_object_vars($value) as $key => $val) { + // Skip optional arguments. If they have a default value, it is resolved by the server. + if ($val === Optional::Undefined) { + continue; + } + + $result->{'$' . $key} = $this->recursiveEncode($val); + } + + return $this->wrap($value, $result); + } + /** * Get the unique property of the operator as value */ diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index bef68e66b..430297bc2 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -327,10 +327,13 @@ public static function minDistance(Int64|float|int $value): MinDistanceOperator * Performs a modulo operation on the value of a field and selects documents with a specified result. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/mod/ - * @param int $divisor - * @param int $remainder + * @param Decimal128|Int64|float|int $divisor + * @param Decimal128|Int64|float|int $remainder */ - public static function mod(int $divisor, int $remainder): ModOperator + public static function mod( + Decimal128|Int64|float|int $divisor, + Decimal128|Int64|float|int $remainder, + ): ModOperator { return new ModOperator($divisor, $remainder); } diff --git a/src/Builder/Query/ModOperator.php b/src/Builder/Query/ModOperator.php index 752c9aa4c..e608bf9c9 100644 --- a/src/Builder/Query/ModOperator.php +++ b/src/Builder/Query/ModOperator.php @@ -8,6 +8,8 @@ namespace MongoDB\Builder\Query; +use MongoDB\BSON\Decimal128; +use MongoDB\BSON\Int64; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\OperatorInterface; @@ -21,17 +23,17 @@ class ModOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Array; - /** @var int $divisor */ - public readonly int $divisor; + /** @var Decimal128|Int64|float|int $divisor */ + public readonly Decimal128|Int64|float|int $divisor; - /** @var int $remainder */ - public readonly int $remainder; + /** @var Decimal128|Int64|float|int $remainder */ + public readonly Decimal128|Int64|float|int $remainder; /** - * @param int $divisor - * @param int $remainder + * @param Decimal128|Int64|float|int $divisor + * @param Decimal128|Int64|float|int $remainder */ - public function __construct(int $divisor, int $remainder) + public function __construct(Decimal128|Int64|float|int $divisor, Decimal128|Int64|float|int $remainder) { $this->divisor = $divisor; $this->remainder = $remainder; diff --git a/src/Builder/Query/TextOperator.php b/src/Builder/Query/TextOperator.php index de6bb6cb9..1fbdd65e1 100644 --- a/src/Builder/Query/TextOperator.php +++ b/src/Builder/Query/TextOperator.php @@ -20,7 +20,7 @@ */ class TextOperator implements QueryInterface, OperatorInterface { - public const ENCODE = Encode::Object; + public const ENCODE = Encode::DollarObject; /** @var non-empty-string $search A string of terms that MongoDB parses and uses to query the text index. MongoDB performs a logical OR search of the terms unless specified as a phrase. */ public readonly string $search; diff --git a/src/Builder/Type/Encode.php b/src/Builder/Type/Encode.php index 69f1aba7d..d5ec60327 100644 --- a/src/Builder/Type/Encode.php +++ b/src/Builder/Type/Encode.php @@ -23,6 +23,11 @@ enum Encode */ case Object; + /** + * Parameters are encoded as an object with keys matching the parameter names prefixed with a dollar sign ($) + */ + case DollarObject; + /** * Get the single parameter value */ diff --git a/tests/Builder/Query/JsonSchemaOperatorTest.php b/tests/Builder/Query/JsonSchemaOperatorTest.php new file mode 100644 index 000000000..0d4b1a92f --- /dev/null +++ b/tests/Builder/Query/JsonSchemaOperatorTest.php @@ -0,0 +1,45 @@ +assertSamePipeline(Pipelines::JsonSchemaExample, $pipeline); + } +} diff --git a/tests/Builder/Query/ModOperatorTest.php b/tests/Builder/Query/ModOperatorTest.php new file mode 100644 index 000000000..e4bbf90dc --- /dev/null +++ b/tests/Builder/Query/ModOperatorTest.php @@ -0,0 +1,44 @@ +assertSamePipeline(Pipelines::ModFloatingPointArguments, $pipeline); + } + + public function testUseModToSelectDocuments(): void + { + $pipeline = new Pipeline( + Stage::match( + qty: Query::mod(4, 0), + ), + ); + + $this->assertSamePipeline(Pipelines::ModUseModToSelectDocuments, $pipeline); + } +} diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 644f6dc35..4a91c366b 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -516,6 +516,48 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/jsonSchema/#syntax + */ + case JsonSchemaExample = <<<'JSON' + [ + { + "$match": { + "$jsonSchema": { + "required": [ + "name", + "major", + "gpa", + "address" + ], + "properties": { + "name": { + "bsonType": "string", + "description": "must be a string and is required" + }, + "address": { + "bsonType": "object", + "required": [ + "zipcode" + ], + "properties": { + "street": { + "bsonType": "string" + }, + "zipcode": { + "bsonType": "string" + } + } + } + } + } + } + } + ] + JSON; + /** * Match Document Fields * @@ -554,6 +596,82 @@ enum Pipelines: string ] JSON; + /** + * Use $mod to Select Documents + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/mod/#use--mod-to-select-documents + */ + case ModUseModToSelectDocuments = <<<'JSON' + [ + { + "$match": { + "qty": { + "$mod": [ + { + "$numberInt": "4" + }, + { + "$numberInt": "0" + } + ] + } + } + } + ] + JSON; + + /** + * Floating Point Arguments + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/mod/#floating-point-arguments + */ + case ModFloatingPointArguments = <<<'JSON' + [ + { + "$match": { + "qty": { + "$mod": [ + { + "$numberDouble": "4.0" + }, + { + "$numberInt": "0" + } + ] + } + } + }, + { + "$match": { + "qty": { + "$mod": [ + { + "$numberDouble": "4.5" + }, + { + "$numberInt": "0" + } + ] + } + } + }, + { + "$match": { + "qty": { + "$mod": [ + { + "$numberDouble": "4.9900000000000002132" + }, + { + "$numberInt": "0" + } + ] + } + } + } + ] + JSON; + /** * Match Document Fields * @@ -859,4 +977,178 @@ enum Pipelines: string } ] JSON; + + /** + * Search for a Single Word + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/#search-for-a-single-word + */ + case TextSearchForASingleWord = <<<'JSON' + [ + { + "$match": { + "$text": { + "$search": "coffee" + } + } + } + ] + JSON; + + /** + * Match Any of the Search Terms + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/#search-for-a-single-word + */ + case TextMatchAnyOfTheSearchTerms = <<<'JSON' + [ + { + "$match": { + "$text": { + "$search": "bake coffee cake" + } + } + } + ] + JSON; + + /** + * Search a Different Language + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/#search-a-different-language + */ + case TextSearchADifferentLanguage = <<<'JSON' + [ + { + "$match": { + "$text": { + "$search": "leche", + "$language": "es" + } + } + } + ] + JSON; + + /** + * Case and Diacritic Insensitive Search + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/#case-and-diacritic-insensitive-search + */ + case TextCaseAndDiacriticInsensitiveSearch = <<<'JSON' + [ + { + "$match": { + "$text": { + "$search": "\u0441\u044b\u0301\u0440\u043d\u0438\u043a\u0438 CAF\u00c9S" + } + } + } + ] + JSON; + + /** + * Perform Case Sensitive Search + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/#perform-case-sensitive-search + */ + case TextPerformCaseSensitiveSearch = <<<'JSON' + [ + { + "$match": { + "$text": { + "$search": "Coffee", + "$caseSensitive": true + } + } + }, + { + "$match": { + "$text": { + "$search": "\\\"Caf\u00e9 Con Leche\\\"", + "$caseSensitive": true + } + } + } + ] + JSON; + + /** + * Diacritic Sensitive Search + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/#perform-case-sensitive-search + */ + case TextDiacriticSensitiveSearch = <<<'JSON' + [ + { + "$match": { + "$text": { + "$search": "CAF\u00c9", + "$diacriticSensitive": true + } + } + } + ] + JSON; + + /** + * Text Search Score Examples + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/#perform-case-sensitive-search + */ + case TextTextSearchScoreExamples = <<<'JSON' + [ + { + "$match": { + "$text": { + "$search": "CAF\u00c9", + "$diacriticSensitive": true + }, + "score": { + "$meta": "textScore" + } + } + }, + { + "$sort": { + "score": { + "$meta": "textScore" + } + } + }, + { + "$limit": { + "$numberInt": "5" + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/where/#example + */ + case WhereExample = <<<'JSON' + [ + { + "$match": { + "$where": "function() { return hex_md5(this.name) == \"9b53e667f30cd329dca1ec9e6a83e994\" }" + } + }, + { + "$match": { + "$expr": { + "$function": { + "body": "function(name) {\n return hex_md5(name) == \"9b53e667f30cd329dca1ec9e6a83e994\";\n}", + "args": [ + "$name" + ], + "lang": "js" + } + } + } + } + ] + JSON; } diff --git a/tests/Builder/Query/TextOperatorTest.php b/tests/Builder/Query/TextOperatorTest.php new file mode 100644 index 000000000..ebce1172e --- /dev/null +++ b/tests/Builder/Query/TextOperatorTest.php @@ -0,0 +1,120 @@ +assertSamePipeline(Pipelines::TextCaseAndDiacriticInsensitiveSearch, $pipeline); + } + + public function testDiacriticSensitiveSearch(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::text( + search: 'CAFÉ', + diacriticSensitive: true, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::TextDiacriticSensitiveSearch, $pipeline); + } + + public function testMatchAnyOfTheSearchTerms(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::text('bake coffee cake'), + ), + ); + + $this->assertSamePipeline(Pipelines::TextMatchAnyOfTheSearchTerms, $pipeline); + } + + public function testPerformCaseSensitiveSearch(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::text( + search: 'Coffee', + caseSensitive: true, + ), + ), + Stage::match( + Query::text( + search: '\"Café Con Leche\"', + caseSensitive: true, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::TextPerformCaseSensitiveSearch, $pipeline); + } + + public function testSearchADifferentLanguage(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::text( + search: 'leche', + language: 'es', + ), + ), + ); + + $this->assertSamePipeline(Pipelines::TextSearchADifferentLanguage, $pipeline); + } + + public function testSearchForASingleWord(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::text('coffee'), + ), + ); + + $this->assertSamePipeline(Pipelines::TextSearchForASingleWord, $pipeline); + } + + public function testTextSearchScoreExamples(): void + { + /** + * @todo: add support for $meta: "textScore" + * @todo: add support for $sort spec with object + */ + $pipeline = new Pipeline( + Stage::match( + Query::text( + search: 'CAFÉ', + diacriticSensitive: true, + ), + score: ['$meta' => 'textScore'], + ), + Stage::sort( + ['score' => ['$meta' => 'textScore']], + ), + Stage::limit(5), + ); + + $this->assertSamePipeline(Pipelines::TextTextSearchScoreExamples, $pipeline); + } +} diff --git a/tests/Builder/Query/WhereOperatorTest.php b/tests/Builder/Query/WhereOperatorTest.php new file mode 100644 index 000000000..f8a7b9c44 --- /dev/null +++ b/tests/Builder/Query/WhereOperatorTest.php @@ -0,0 +1,41 @@ +assertSamePipeline(Pipelines::WhereExample, $pipeline); + } +} From f665eb3877ef7c981961ba052ba503e34e8d63b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 16 Jan 2024 12:28:06 +0100 Subject: [PATCH 27/95] PHPLIB-1335 Add tests on Element Query Operators (#23) * PHPLIB-1335 Add tests on Element Query Operators * Make $type query operator variadic --- generator/config/query/exists.yaml | 18 ++ generator/config/query/type.yaml | 75 ++++++++- src/Builder/Query/FactoryTrait.php | 7 +- src/Builder/Query/TypeOperator.php | 20 ++- tests/Builder/Query/ExistsOperatorTest.php | 43 +++++ tests/Builder/Query/Pipelines.php | 182 +++++++++++++++++++++ tests/Builder/Query/TypeOperatorTest.php | 78 +++++++++ 7 files changed, 410 insertions(+), 13 deletions(-) create mode 100644 tests/Builder/Query/ExistsOperatorTest.php create mode 100644 tests/Builder/Query/TypeOperatorTest.php diff --git a/generator/config/query/exists.yaml b/generator/config/query/exists.yaml index 014e31db1..c1124ca3f 100644 --- a/generator/config/query/exists.yaml +++ b/generator/config/query/exists.yaml @@ -11,3 +11,21 @@ arguments: name: exists type: - bool +tests: + - + name: 'Exists and Not Equal To' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/exists/#exists-and-not-equal-to' + pipeline: + - + $match: + qty: + $exists: true + $nin: [5, 15] + - + name: 'Null Values' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/exists/#null-values' + pipeline: + - + $match: + qty: + $exists: true diff --git a/generator/config/query/type.yaml b/generator/config/query/type.yaml index 769aea726..bedec7719 100644 --- a/generator/config/query/type.yaml +++ b/generator/config/query/type.yaml @@ -12,4 +12,77 @@ arguments: type: - int - string - - array # of int or string + variadic: array +tests: + - + name: 'Querying by Data Type' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/type/#querying-by-data-type' + pipeline: + - + $match: + zipCode: + # Example uses the short form, the builder always generated the verbose form + # $type: 2 + $type: [2] + - + $match: + zipCode: + # Example uses the short form, the builder always generated the verbose form + # $type: 'string' + $type: ['string'] + - + $match: + zipCode: + # Example uses the short form, the builder always generated the verbose form + # $type: 1 + $type: [1] + - + $match: + zipCode: + # Example uses the short form, the builder always generated the verbose form + # $type: 'double' + $type: ['double'] + - + $match: + zipCode: + # Example uses the short form, the builder always generated the verbose form + # $type: 'number' + $type: ['number'] + - + name: 'Querying by Multiple Data Type' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/type/#querying-by-multiple-data-type' + pipeline: + - + $match: + zipCode: + $type: [2, 1] + - + $match: + zipCode: + $type: ['string', 'double'] + - + name: 'Querying by MinKey and MaxKey' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/type/#querying-by-minkey-and-maxkey' + pipeline: + - + $match: + zipCode: + # Example uses the short form, the builder always generated the verbose form + # $type: 'minKey' + $type: ['minKey'] + - + $match: + zipCode: + # Example uses the short form, the builder always generated the verbose form + # $type: 'maxKey' + $type: ['maxKey'] + - + name: 'Querying by Array Type' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/type/#querying-by-array-type' + pipeline: + - + $match: + zipCode: + # Example uses the short form, the builder always generated the verbose form + # $type: 'array' + $type: ['array'] diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index 430297bc2..3905b200f 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -509,11 +509,12 @@ public static function text( * Selects documents if a field is of the specified type. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/type/ - * @param BSONArray|PackedArray|array|int|non-empty-string $type + * @no-named-arguments + * @param int|non-empty-string ...$type */ - public static function type(PackedArray|BSONArray|array|int|string $type): TypeOperator + public static function type(int|string ...$type): TypeOperator { - return new TypeOperator($type); + return new TypeOperator(...$type); } /** diff --git a/src/Builder/Query/TypeOperator.php b/src/Builder/Query/TypeOperator.php index f9b2cb03d..d59e899e2 100644 --- a/src/Builder/Query/TypeOperator.php +++ b/src/Builder/Query/TypeOperator.php @@ -8,15 +8,12 @@ namespace MongoDB\Builder\Query; -use MongoDB\BSON\PackedArray; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Exception\InvalidArgumentException; -use MongoDB\Model\BSONArray; use function array_is_list; -use function is_array; /** * Selects documents if a field is of the specified type. @@ -27,16 +24,21 @@ class TypeOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var BSONArray|PackedArray|array|int|non-empty-string $type */ - public readonly PackedArray|BSONArray|array|int|string $type; + /** @var list $type */ + public readonly array $type; /** - * @param BSONArray|PackedArray|array|int|non-empty-string $type + * @param int|non-empty-string ...$type + * @no-named-arguments */ - public function __construct(PackedArray|BSONArray|array|int|string $type) + public function __construct(int|string ...$type) { - if (is_array($type) && ! array_is_list($type)) { - throw new InvalidArgumentException('Expected $type argument to be a list, got an associative array.'); + if (\count($type) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $type, got %d.', 1, \count($type))); + } + + if (! array_is_list($type)) { + throw new InvalidArgumentException('Expected $type arguments to be a list (array), named arguments are not supported'); } $this->type = $type; diff --git a/tests/Builder/Query/ExistsOperatorTest.php b/tests/Builder/Query/ExistsOperatorTest.php new file mode 100644 index 000000000..33366162c --- /dev/null +++ b/tests/Builder/Query/ExistsOperatorTest.php @@ -0,0 +1,43 @@ +assertSamePipeline(Pipelines::ExistsExistsAndNotEqualTo, $pipeline); + } + + public function testNullValues(): void + { + $pipeline = new Pipeline( + Stage::match( + qty: [ + Query::exists(true), + ], + ), + ); + + $this->assertSamePipeline(Pipelines::ExistsNullValues, $pipeline); + } +} diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 4a91c366b..55acfde4e 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -359,6 +359,48 @@ enum Pipelines: string ] JSON; + /** + * Exists and Not Equal To + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/exists/#exists-and-not-equal-to + */ + case ExistsExistsAndNotEqualTo = <<<'JSON' + [ + { + "$match": { + "qty": { + "$exists": true, + "$nin": [ + { + "$numberInt": "5" + }, + { + "$numberInt": "15" + } + ] + } + } + } + ] + JSON; + + /** + * Null Values + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/exists/#null-values + */ + case ExistsNullValues = <<<'JSON' + [ + { + "$match": { + "qty": { + "$exists": true + } + } + } + ] + JSON; + /** * Compare Two Fields from A Single Document * @@ -1124,6 +1166,146 @@ enum Pipelines: string ] JSON; + /** + * Querying by Data Type + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/type/#querying-by-data-type + */ + case TypeQueryingByDataType = <<<'JSON' + [ + { + "$match": { + "zipCode": { + "$type": [ + { + "$numberInt": "2" + } + ] + } + } + }, + { + "$match": { + "zipCode": { + "$type": [ + "string" + ] + } + } + }, + { + "$match": { + "zipCode": { + "$type": [ + { + "$numberInt": "1" + } + ] + } + } + }, + { + "$match": { + "zipCode": { + "$type": [ + "double" + ] + } + } + }, + { + "$match": { + "zipCode": { + "$type": [ + "number" + ] + } + } + } + ] + JSON; + + /** + * Querying by Multiple Data Type + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/type/#querying-by-multiple-data-type + */ + case TypeQueryingByMultipleDataType = <<<'JSON' + [ + { + "$match": { + "zipCode": { + "$type": [ + { + "$numberInt": "2" + }, + { + "$numberInt": "1" + } + ] + } + } + }, + { + "$match": { + "zipCode": { + "$type": [ + "string", + "double" + ] + } + } + } + ] + JSON; + + /** + * Querying by MinKey and MaxKey + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/type/#querying-by-minkey-and-maxkey + */ + case TypeQueryingByMinKeyAndMaxKey = <<<'JSON' + [ + { + "$match": { + "zipCode": { + "$type": [ + "minKey" + ] + } + } + }, + { + "$match": { + "zipCode": { + "$type": [ + "maxKey" + ] + } + } + } + ] + JSON; + + /** + * Querying by Array Type + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/type/#querying-by-array-type + */ + case TypeQueryingByArrayType = <<<'JSON' + [ + { + "$match": { + "zipCode": { + "$type": [ + "array" + ] + } + } + } + ] + JSON; + /** * Example * diff --git a/tests/Builder/Query/TypeOperatorTest.php b/tests/Builder/Query/TypeOperatorTest.php new file mode 100644 index 000000000..21d727414 --- /dev/null +++ b/tests/Builder/Query/TypeOperatorTest.php @@ -0,0 +1,78 @@ +assertSamePipeline(Pipelines::TypeQueryingByArrayType, $pipeline); + } + + public function testQueryingByDataType(): void + { + $pipeline = new Pipeline( + Stage::match( + zipCode: Query::type(2), + ), + Stage::match( + zipCode: Query::type('string'), + ), + Stage::match( + zipCode: Query::type(1), + ), + Stage::match( + zipCode: Query::type('double'), + ), + Stage::match( + zipCode: Query::type('number'), + ), + ); + + $this->assertSamePipeline(Pipelines::TypeQueryingByDataType, $pipeline); + } + + public function testQueryingByMinKeyAndMaxKey(): void + { + $pipeline = new Pipeline( + Stage::match( + zipCode: Query::type('minKey'), + ), + Stage::match( + zipCode: Query::type('maxKey'), + ), + ); + + $this->assertSamePipeline(Pipelines::TypeQueryingByMinKeyAndMaxKey, $pipeline); + } + + public function testQueryingByMultipleDataType(): void + { + $pipeline = new Pipeline( + Stage::match( + zipCode: Query::type(2, 1), + ), + Stage::match( + zipCode: Query::type('string', 'double'), + ), + ); + + $this->assertSamePipeline(Pipelines::TypeQueryingByMultipleDataType, $pipeline); + } +} From 118184d16e45bd75b19f4fea3c7ac44b2398b0d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 16 Jan 2024 15:18:31 +0100 Subject: [PATCH 28/95] PHPLIB-1337 Add tests on Geospatial Query Operators (#25) * PHPLIB-1337 Add tests on Geospatial Query Operators * Fix nested $geometry --- generator/config/query/geoIntersects.yaml | 39 ++ generator/config/query/geoWithin.yaml | 39 ++ generator/config/query/geometry.yaml | 1 + generator/config/query/near.yaml | 22 +- generator/config/query/nearSphere.yaml | 20 +- src/Builder/BuilderEncoder.php | 15 +- src/Builder/Query/FactoryTrait.php | 20 +- src/Builder/Query/GeometryOperator.php | 9 +- src/Builder/Query/NearOperator.php | 20 +- src/Builder/Query/NearSphereOperator.php | 20 +- .../Query/GeoIntersectsOperatorTest.php | 56 +++ tests/Builder/Query/GeoWithinOperatorTest.php | 56 +++ tests/Builder/Query/NearOperatorTest.php | 34 ++ .../Builder/Query/NearSphereOperatorTest.php | 34 ++ tests/Builder/Query/Pipelines.php | 396 ++++++++++++++++++ 15 files changed, 742 insertions(+), 39 deletions(-) create mode 100644 tests/Builder/Query/GeoIntersectsOperatorTest.php create mode 100644 tests/Builder/Query/GeoWithinOperatorTest.php create mode 100644 tests/Builder/Query/NearOperatorTest.php create mode 100644 tests/Builder/Query/NearSphereOperatorTest.php diff --git a/generator/config/query/geoIntersects.yaml b/generator/config/query/geoIntersects.yaml index 45a4ac53a..ca1eda02f 100644 --- a/generator/config/query/geoIntersects.yaml +++ b/generator/config/query/geoIntersects.yaml @@ -11,3 +11,42 @@ arguments: name: geometry type: - geometry +tests: + - + name: 'Intersects a Polygon' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/geoIntersects/#intersects-a-polygon' + pipeline: + - + $match: + loc: + $geoIntersects: + $geometry: + type: 'Polygon' + coordinates: + - + - [ 0, 0 ] + - [ 3, 6 ] + - [ 6, 1 ] + - [ 0, 0 ] + - + name: 'Intersects a Big Polygon' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/geoIntersects/#intersects-a--big--polygon' + pipeline: + - + $match: + loc: + $geoIntersects: + $geometry: + type: 'Polygon' + coordinates: + - + - [ -100, 60 ] + - [ -100, 0 ] + - [ -100, -60 ] + - [ 100, -60 ] + - [ 100, 60 ] + - [ -100, 60 ] + crs: + type: 'name' + properties: + name: 'urn:x-mongodb:crs:strictwinding:EPSG:4326' diff --git a/generator/config/query/geoWithin.yaml b/generator/config/query/geoWithin.yaml index 12944b42c..3a87e4b72 100644 --- a/generator/config/query/geoWithin.yaml +++ b/generator/config/query/geoWithin.yaml @@ -11,3 +11,42 @@ arguments: name: geometry type: - geometry +tests: + - + name: 'Within a Polygon' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/#within-a-polygon' + pipeline: + - + $match: + loc: + $geoWithin: + $geometry: + type: 'Polygon' + coordinates: + - + - [ 0, 0 ] + - [ 3, 6 ] + - [ 6, 1 ] + - [ 0, 0 ] + - + name: 'Within a Big Polygon' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/#within-a--big--polygon' + pipeline: + - + $match: + loc: + $geoWithin: + $geometry: + type: 'Polygon' + coordinates: + - + - [ -100, 60 ] + - [ -100, 0 ] + - [ -100, -60 ] + - [ 100, -60 ] + - [ 100, 60 ] + - [ -100, 60 ] + crs: + type: 'name' + properties: + name: 'urn:x-mongodb:crs:strictwinding:EPSG:4326' diff --git a/generator/config/query/geometry.yaml b/generator/config/query/geometry.yaml index a08325396..40b18dc99 100644 --- a/generator/config/query/geometry.yaml +++ b/generator/config/query/geometry.yaml @@ -19,3 +19,4 @@ arguments: name: crs type: - object + optional: true diff --git a/generator/config/query/near.yaml b/generator/config/query/near.yaml index 36945657b..f2c791630 100644 --- a/generator/config/query/near.yaml +++ b/generator/config/query/near.yaml @@ -3,7 +3,7 @@ name: $near link: 'https://www.mongodb.com/docs/manual/reference/operator/query/near/' type: - fieldQuery -encode: object +encode: dollar_object description: | Returns geospatial objects in proximity to a point. Requires a geospatial index. The 2dsphere and 2d indexes support $near. arguments: @@ -14,14 +14,30 @@ arguments: - name: maxDistance type: - - int + - number optional: true description: | Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. - name: minDistance type: - - int + - number optional: true description: | Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. +tests: + - + name: 'Query on GeoJSON Data' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/near/#query-on-geojson-data' + pipeline: + - + $match: + location: + $near: + $geometry: + type: 'Point' + coordinates: + - -73.9667 + - 40.78 + $minDistance: 1000 + $maxDistance: 5000 diff --git a/generator/config/query/nearSphere.yaml b/generator/config/query/nearSphere.yaml index bc9a48e6b..3411127ad 100644 --- a/generator/config/query/nearSphere.yaml +++ b/generator/config/query/nearSphere.yaml @@ -3,7 +3,7 @@ name: $nearSphere link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/' type: - fieldQuery -encode: object +encode: dollar_object description: | Returns geospatial objects in proximity to a point on a sphere. Requires a geospatial index. The 2dsphere and 2d indexes support $nearSphere. arguments: @@ -14,14 +14,28 @@ arguments: - name: maxDistance type: - - int + - number optional: true description: | Distance in meters. - name: minDistance type: - - int + - number optional: true description: | Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. +tests: + - + name: 'Specify Center Point Using GeoJSON' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/#specify-center-point-using-geojson' + pipeline: + - + $match: + location: + $nearSphere: + $geometry: + type: 'Point' + coordinates: [-73.9667, 40.78] + $minDistance: 1000 + $maxDistance: 5000 diff --git a/src/Builder/BuilderEncoder.php b/src/Builder/BuilderEncoder.php index ec566e1d8..c7c0b278c 100644 --- a/src/Builder/BuilderEncoder.php +++ b/src/Builder/BuilderEncoder.php @@ -26,6 +26,7 @@ use MongoDB\Exception\UnsupportedValueException; use stdClass; +use function array_key_exists; use function array_key_first; use function assert; use function get_debug_type; @@ -182,7 +183,19 @@ private function encodeAsDollarObject(OperatorInterface $value): stdClass continue; } - $result->{'$' . $key} = $this->recursiveEncode($val); + $val = $this->recursiveEncode($val); + + if ($key === 'geometry') { + if (is_object($val) && property_exists($val, '$geometry')) { + $result->{'$geometry'} = $val->{'$geometry'}; + } elseif (is_array($val) && array_key_exists('$geometry', $val)) { + $result->{'$geometry'} = $val->{'$geometry'}; + } else { + $result->{'$geometry'} = $val; + } + } else { + $result->{'$' . $key} = $val; + } } return $this->wrap($value, $result); diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index 3905b200f..c12895e31 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -211,12 +211,12 @@ public static function geoIntersects( * @see https://www.mongodb.com/docs/manual/reference/operator/query/geometry/ * @param non-empty-string $type * @param BSONArray|PackedArray|array $coordinates - * @param Document|Serializable|array|stdClass $crs + * @param Optional|Document|Serializable|array|stdClass $crs */ public static function geometry( string $type, PackedArray|BSONArray|array $coordinates, - Document|Serializable|stdClass|array $crs, + Optional|Document|Serializable|stdClass|array $crs = Optional::Undefined, ): GeometryOperator { return new GeometryOperator($type, $coordinates, $crs); @@ -364,13 +364,13 @@ public static function ne(Type|stdClass|array|bool|float|int|null|string $value) * * @see https://www.mongodb.com/docs/manual/reference/operator/query/near/ * @param Document|GeometryInterface|Serializable|array|stdClass $geometry - * @param Optional|int $maxDistance Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. - * @param Optional|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. + * @param Optional|Decimal128|Int64|float|int $maxDistance Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. + * @param Optional|Decimal128|Int64|float|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ public static function near( Document|Serializable|GeometryInterface|stdClass|array $geometry, - Optional|int $maxDistance = Optional::Undefined, - Optional|int $minDistance = Optional::Undefined, + Optional|Decimal128|Int64|float|int $maxDistance = Optional::Undefined, + Optional|Decimal128|Int64|float|int $minDistance = Optional::Undefined, ): NearOperator { return new NearOperator($geometry, $maxDistance, $minDistance); @@ -381,13 +381,13 @@ public static function near( * * @see https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/ * @param Document|GeometryInterface|Serializable|array|stdClass $geometry - * @param Optional|int $maxDistance Distance in meters. - * @param Optional|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. + * @param Optional|Decimal128|Int64|float|int $maxDistance Distance in meters. + * @param Optional|Decimal128|Int64|float|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ public static function nearSphere( Document|Serializable|GeometryInterface|stdClass|array $geometry, - Optional|int $maxDistance = Optional::Undefined, - Optional|int $minDistance = Optional::Undefined, + Optional|Decimal128|Int64|float|int $maxDistance = Optional::Undefined, + Optional|Decimal128|Int64|float|int $minDistance = Optional::Undefined, ): NearSphereOperator { return new NearSphereOperator($geometry, $maxDistance, $minDistance); diff --git a/src/Builder/Query/GeometryOperator.php b/src/Builder/Query/GeometryOperator.php index a1bd4bc3c..b9970da12 100644 --- a/src/Builder/Query/GeometryOperator.php +++ b/src/Builder/Query/GeometryOperator.php @@ -14,6 +14,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\GeometryInterface; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\Optional; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; use stdClass; @@ -36,18 +37,18 @@ class GeometryOperator implements GeometryInterface, OperatorInterface /** @var BSONArray|PackedArray|array $coordinates */ public readonly PackedArray|BSONArray|array $coordinates; - /** @var Document|Serializable|array|stdClass $crs */ - public readonly Document|Serializable|stdClass|array $crs; + /** @var Optional|Document|Serializable|array|stdClass $crs */ + public readonly Optional|Document|Serializable|stdClass|array $crs; /** * @param non-empty-string $type * @param BSONArray|PackedArray|array $coordinates - * @param Document|Serializable|array|stdClass $crs + * @param Optional|Document|Serializable|array|stdClass $crs */ public function __construct( string $type, PackedArray|BSONArray|array $coordinates, - Document|Serializable|stdClass|array $crs, + Optional|Document|Serializable|stdClass|array $crs = Optional::Undefined, ) { $this->type = $type; if (is_array($coordinates) && ! array_is_list($coordinates)) { diff --git a/src/Builder/Query/NearOperator.php b/src/Builder/Query/NearOperator.php index b204f0ad7..68b99aa5e 100644 --- a/src/Builder/Query/NearOperator.php +++ b/src/Builder/Query/NearOperator.php @@ -8,7 +8,9 @@ namespace MongoDB\Builder\Query; +use MongoDB\BSON\Decimal128; use MongoDB\BSON\Document; +use MongoDB\BSON\Int64; use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\FieldQueryInterface; @@ -24,26 +26,26 @@ */ class NearOperator implements FieldQueryInterface, OperatorInterface { - public const ENCODE = Encode::Object; + public const ENCODE = Encode::DollarObject; /** @var Document|GeometryInterface|Serializable|array|stdClass $geometry */ public readonly Document|Serializable|GeometryInterface|stdClass|array $geometry; - /** @var Optional|int $maxDistance Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. */ - public readonly Optional|int $maxDistance; + /** @var Optional|Decimal128|Int64|float|int $maxDistance Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. */ + public readonly Optional|Decimal128|Int64|float|int $maxDistance; - /** @var Optional|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ - public readonly Optional|int $minDistance; + /** @var Optional|Decimal128|Int64|float|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ + public readonly Optional|Decimal128|Int64|float|int $minDistance; /** * @param Document|GeometryInterface|Serializable|array|stdClass $geometry - * @param Optional|int $maxDistance Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. - * @param Optional|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. + * @param Optional|Decimal128|Int64|float|int $maxDistance Distance in meters. Limits the results to those documents that are at most the specified distance from the center point. + * @param Optional|Decimal128|Int64|float|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ public function __construct( Document|Serializable|GeometryInterface|stdClass|array $geometry, - Optional|int $maxDistance = Optional::Undefined, - Optional|int $minDistance = Optional::Undefined, + Optional|Decimal128|Int64|float|int $maxDistance = Optional::Undefined, + Optional|Decimal128|Int64|float|int $minDistance = Optional::Undefined, ) { $this->geometry = $geometry; $this->maxDistance = $maxDistance; diff --git a/src/Builder/Query/NearSphereOperator.php b/src/Builder/Query/NearSphereOperator.php index 1df48f6b1..aab04903d 100644 --- a/src/Builder/Query/NearSphereOperator.php +++ b/src/Builder/Query/NearSphereOperator.php @@ -8,7 +8,9 @@ namespace MongoDB\Builder\Query; +use MongoDB\BSON\Decimal128; use MongoDB\BSON\Document; +use MongoDB\BSON\Int64; use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\FieldQueryInterface; @@ -24,26 +26,26 @@ */ class NearSphereOperator implements FieldQueryInterface, OperatorInterface { - public const ENCODE = Encode::Object; + public const ENCODE = Encode::DollarObject; /** @var Document|GeometryInterface|Serializable|array|stdClass $geometry */ public readonly Document|Serializable|GeometryInterface|stdClass|array $geometry; - /** @var Optional|int $maxDistance Distance in meters. */ - public readonly Optional|int $maxDistance; + /** @var Optional|Decimal128|Int64|float|int $maxDistance Distance in meters. */ + public readonly Optional|Decimal128|Int64|float|int $maxDistance; - /** @var Optional|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ - public readonly Optional|int $minDistance; + /** @var Optional|Decimal128|Int64|float|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ + public readonly Optional|Decimal128|Int64|float|int $minDistance; /** * @param Document|GeometryInterface|Serializable|array|stdClass $geometry - * @param Optional|int $maxDistance Distance in meters. - * @param Optional|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. + * @param Optional|Decimal128|Int64|float|int $maxDistance Distance in meters. + * @param Optional|Decimal128|Int64|float|int $minDistance Distance in meters. Limits the results to those documents that are at least the specified distance from the center point. */ public function __construct( Document|Serializable|GeometryInterface|stdClass|array $geometry, - Optional|int $maxDistance = Optional::Undefined, - Optional|int $minDistance = Optional::Undefined, + Optional|Decimal128|Int64|float|int $maxDistance = Optional::Undefined, + Optional|Decimal128|Int64|float|int $minDistance = Optional::Undefined, ) { $this->geometry = $geometry; $this->maxDistance = $maxDistance; diff --git a/tests/Builder/Query/GeoIntersectsOperatorTest.php b/tests/Builder/Query/GeoIntersectsOperatorTest.php new file mode 100644 index 000000000..92fc88c83 --- /dev/null +++ b/tests/Builder/Query/GeoIntersectsOperatorTest.php @@ -0,0 +1,56 @@ +assertSamePipeline(Pipelines::GeoIntersectsIntersectsABigPolygon, $pipeline); + } + + public function testIntersectsAPolygon(): void + { + $pipeline = new Pipeline( + Stage::match( + loc: Query::geoIntersects( + Query::geometry( + type: 'Polygon', + coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::GeoIntersectsIntersectsAPolygon, $pipeline); + } +} diff --git a/tests/Builder/Query/GeoWithinOperatorTest.php b/tests/Builder/Query/GeoWithinOperatorTest.php new file mode 100644 index 000000000..d510b6cea --- /dev/null +++ b/tests/Builder/Query/GeoWithinOperatorTest.php @@ -0,0 +1,56 @@ +assertSamePipeline(Pipelines::GeoWithinWithinABigPolygon, $pipeline); + } + + public function testWithinAPolygon(): void + { + $pipeline = new Pipeline( + Stage::match( + loc: Query::geoWithin( + Query::geometry( + type: 'Polygon', + coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::GeoWithinWithinAPolygon, $pipeline); + } +} diff --git a/tests/Builder/Query/NearOperatorTest.php b/tests/Builder/Query/NearOperatorTest.php new file mode 100644 index 000000000..3ca7a16c7 --- /dev/null +++ b/tests/Builder/Query/NearOperatorTest.php @@ -0,0 +1,34 @@ +assertSamePipeline(Pipelines::NearQueryOnGeoJSONData, $pipeline); + } +} diff --git a/tests/Builder/Query/NearSphereOperatorTest.php b/tests/Builder/Query/NearSphereOperatorTest.php new file mode 100644 index 000000000..40ef97a6c --- /dev/null +++ b/tests/Builder/Query/NearSphereOperatorTest.php @@ -0,0 +1,34 @@ +assertSamePipeline(Pipelines::NearSphereSpecifyCenterPointUsingGeoJSON, $pipeline); + } +} diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 55acfde4e..107132373 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -470,6 +470,278 @@ enum Pipelines: string ] JSON; + /** + * Intersects a Polygon + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/geoIntersects/#intersects-a-polygon + */ + case GeoIntersectsIntersectsAPolygon = <<<'JSON' + [ + { + "$match": { + "loc": { + "$geoIntersects": { + "$geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + { + "$numberInt": "0" + }, + { + "$numberInt": "0" + } + ], + [ + { + "$numberInt": "3" + }, + { + "$numberInt": "6" + } + ], + [ + { + "$numberInt": "6" + }, + { + "$numberInt": "1" + } + ], + [ + { + "$numberInt": "0" + }, + { + "$numberInt": "0" + } + ] + ] + ] + } + } + } + } + } + ] + JSON; + + /** + * Intersects a Big Polygon + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/geoIntersects/#intersects-a--big--polygon + */ + case GeoIntersectsIntersectsABigPolygon = <<<'JSON' + [ + { + "$match": { + "loc": { + "$geoIntersects": { + "$geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + { + "$numberInt": "-100" + }, + { + "$numberInt": "60" + } + ], + [ + { + "$numberInt": "-100" + }, + { + "$numberInt": "0" + } + ], + [ + { + "$numberInt": "-100" + }, + { + "$numberInt": "-60" + } + ], + [ + { + "$numberInt": "100" + }, + { + "$numberInt": "-60" + } + ], + [ + { + "$numberInt": "100" + }, + { + "$numberInt": "60" + } + ], + [ + { + "$numberInt": "-100" + }, + { + "$numberInt": "60" + } + ] + ] + ], + "crs": { + "type": "name", + "properties": { + "name": "urn:x-mongodb:crs:strictwinding:EPSG:4326" + } + } + } + } + } + } + } + ] + JSON; + + /** + * Within a Polygon + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/#within-a-polygon + */ + case GeoWithinWithinAPolygon = <<<'JSON' + [ + { + "$match": { + "loc": { + "$geoWithin": { + "$geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + { + "$numberInt": "0" + }, + { + "$numberInt": "0" + } + ], + [ + { + "$numberInt": "3" + }, + { + "$numberInt": "6" + } + ], + [ + { + "$numberInt": "6" + }, + { + "$numberInt": "1" + } + ], + [ + { + "$numberInt": "0" + }, + { + "$numberInt": "0" + } + ] + ] + ] + } + } + } + } + } + ] + JSON; + + /** + * Within a Big Polygon + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/geoWithin/#within-a--big--polygon + */ + case GeoWithinWithinABigPolygon = <<<'JSON' + [ + { + "$match": { + "loc": { + "$geoWithin": { + "$geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + { + "$numberInt": "-100" + }, + { + "$numberInt": "60" + } + ], + [ + { + "$numberInt": "-100" + }, + { + "$numberInt": "0" + } + ], + [ + { + "$numberInt": "-100" + }, + { + "$numberInt": "-60" + } + ], + [ + { + "$numberInt": "100" + }, + { + "$numberInt": "-60" + } + ], + [ + { + "$numberInt": "100" + }, + { + "$numberInt": "60" + } + ], + [ + { + "$numberInt": "-100" + }, + { + "$numberInt": "60" + } + ] + ] + ], + "crs": { + "type": "name", + "properties": { + "name": "urn:x-mongodb:crs:strictwinding:EPSG:4326" + } + } + } + } + } + } + } + ] + JSON; + /** * Match Document Fields * @@ -733,6 +1005,130 @@ enum Pipelines: string ] JSON; + /** + * Query on GeoJSON Data + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/near/#query-on-geojson-data + */ + case NearQueryOnGeoJSONData = <<<'JSON' + [ + { + "$match": { + "location": { + "$near": { + "$geometry": { + "type": "Point", + "coordinates": [ + { + "$numberDouble": "-73.966700000000003001" + }, + { + "$numberDouble": "40.780000000000001137" + } + ] + }, + "$minDistance": { + "$numberInt": "1000" + }, + "$maxDistance": { + "$numberInt": "5000" + } + } + } + } + } + ] + JSON; + + /** + * Query on Legacy Coordinates + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/near/#query-on-legacy-coordinates + */ + case NearQueryOnLegacyCoordinates = <<<'JSON' + [ + { + "$match": { + "location": { + "$near": [ + { + "$numberDouble": "-73.966700000000003001" + }, + { + "$numberDouble": "40.780000000000001137" + } + ], + "$maxDistance": { + "$numberDouble": "0.10000000000000000555" + } + } + } + } + ] + JSON; + + /** + * Specify Center Point Using GeoJSON + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/#specify-center-point-using-geojson + */ + case NearSphereSpecifyCenterPointUsingGeoJSON = <<<'JSON' + [ + { + "$match": { + "location": { + "$nearSphere": { + "$geometry": { + "type": "Point", + "coordinates": [ + { + "$numberDouble": "-73.966700000000003001" + }, + { + "$numberDouble": "40.780000000000001137" + } + ] + }, + "$minDistance": { + "$numberInt": "1000" + }, + "$maxDistance": { + "$numberInt": "5000" + } + } + } + } + } + ] + JSON; + + /** + * Specify Center Point Using Legacy Coordinates + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/#specify-center-point-using-legacy-coordinates + */ + case NearSphereSpecifyCenterPointUsingLegacyCoordinates = <<<'JSON' + [ + { + "$match": { + "location": { + "$nearSphere": [ + { + "$numberDouble": "-73.966700000000003001" + }, + { + "$numberDouble": "40.780000000000001137" + } + ], + "$maxDistance": { + "$numberDouble": "0.10000000000000000555" + } + } + } + } + ] + JSON; + /** * Select on Unmatching Documents * From 7deb80237f46276fdbf7768c8c3fc41d2a5025f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 16 Jan 2024 20:21:25 +0100 Subject: [PATCH 29/95] PHPLIB-1347 Add tests on Conditional Expression Operators (#30) --- generator/config/expression/cond.yaml | 16 ++ generator/config/expression/ifNull.yaml | 24 +++ generator/config/expression/switch.yaml | 44 +++++ tests/Builder/Expression/CondOperatorTest.php | 35 ++++ .../Builder/Expression/IfNullOperatorTest.php | 47 +++++ tests/Builder/Expression/Pipelines.php | 167 ++++++++++++++++++ .../Builder/Expression/SwitchOperatorTest.php | 69 ++++++++ tests/Builder/Query/Pipelines.php | 54 ------ 8 files changed, 402 insertions(+), 54 deletions(-) create mode 100644 tests/Builder/Expression/CondOperatorTest.php create mode 100644 tests/Builder/Expression/IfNullOperatorTest.php create mode 100644 tests/Builder/Expression/SwitchOperatorTest.php diff --git a/generator/config/expression/cond.yaml b/generator/config/expression/cond.yaml index 386705fcb..e2fd66ad7 100644 --- a/generator/config/expression/cond.yaml +++ b/generator/config/expression/cond.yaml @@ -19,3 +19,19 @@ arguments: name: else type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/#example' + pipeline: + - + $project: + item: 1 + discount: + $cond: + if: + $gte: + - '$qty' + - 250 + then: 30 + else: 20 diff --git a/generator/config/expression/ifNull.yaml b/generator/config/expression/ifNull.yaml index 3cbbb72ae..9be8e9044 100644 --- a/generator/config/expression/ifNull.yaml +++ b/generator/config/expression/ifNull.yaml @@ -12,3 +12,27 @@ arguments: type: - expression variadic: array +tests: + - + name: 'Single Input Expression' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/ifNull/#single-input-expression' + pipeline: + - + $project: + item: 1 + description: + $ifNull: + - '$description' + - 'Unspecified' + - + name: 'Multiple Input Expressions' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/ifNull/#multiple-input-expressions' + pipeline: + - + $project: + item: 1 + value: + $ifNull: + - '$description' + - '$quantity' + - 'Unspecified' diff --git a/generator/config/expression/switch.yaml b/generator/config/expression/switch.yaml index 2ade8d502..e7b13061b 100644 --- a/generator/config/expression/switch.yaml +++ b/generator/config/expression/switch.yaml @@ -24,3 +24,47 @@ arguments: description: | The path to take if no branch case expression evaluates to true. Although optional, if default is unspecified and no branch case evaluates to true, $switch returns an error. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/switch/#example' + pipeline: + - + $project: + name: 1 + summary: + $switch: + branches: + - + case: + $gte: + - + #$avg: '$scores' + $avg: [ '$scores' ] + - 90 + then: 'Doing great!' + - + case: + $and: + - + $gte: + - + #$avg: '$scores' + $avg: [ '$scores' ] + - 80 + - + $lt: + - + #$avg: '$scores' + $avg: [ '$scores' ] + - 90 + then: 'Doing pretty well.' + - + case: + $lt: + - + #$avg: '$scores' + $avg: [ '$scores' ] + - 80 + then: 'Needs improvement.' + default: 'No scores found.' diff --git a/tests/Builder/Expression/CondOperatorTest.php b/tests/Builder/Expression/CondOperatorTest.php new file mode 100644 index 000000000..725ab1c50 --- /dev/null +++ b/tests/Builder/Expression/CondOperatorTest.php @@ -0,0 +1,35 @@ +assertSamePipeline(Pipelines::CondExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/IfNullOperatorTest.php b/tests/Builder/Expression/IfNullOperatorTest.php new file mode 100644 index 000000000..2bd046e8c --- /dev/null +++ b/tests/Builder/Expression/IfNullOperatorTest.php @@ -0,0 +1,47 @@ +assertSamePipeline(Pipelines::IfNullMultipleInputExpressions, $pipeline); + } + + public function testSingleInputExpression(): void + { + $pipeline = new Pipeline( + Stage::project( + item: 1, + description: Expression::ifNull( + Expression::fieldPath('description'), + 'Unspecified', + ), + ), + ); + + $this->assertSamePipeline(Pipelines::IfNullSingleInputExpression, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 192ea4049..31be57265 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -370,6 +370,41 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/#example + */ + case CondExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "discount": { + "$cond": { + "if": { + "$gte": [ + "$qty", + { + "$numberInt": "250" + } + ] + }, + "then": { + "$numberInt": "30" + }, + "else": { + "$numberInt": "20" + } + } + } + } + } + ] + JSON; + /** * Example * @@ -619,6 +654,53 @@ enum Pipelines: string ] JSON; + /** + * Single Input Expression + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ifNull/#single-input-expression + */ + case IfNullSingleInputExpression = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "description": { + "$ifNull": [ + "$description", + "Unspecified" + ] + } + } + } + ] + JSON; + + /** + * Multiple Input Expressions + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ifNull/#multiple-input-expressions + */ + case IfNullMultipleInputExpressions = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "value": { + "$ifNull": [ + "$description", + "$quantity", + "Unspecified" + ] + } + } + } + ] + JSON; + /** * Example * @@ -1721,6 +1803,91 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/switch/#example + */ + case SwitchExample = <<<'JSON' + [ + { + "$project": { + "name": { + "$numberInt": "1" + }, + "summary": { + "$switch": { + "branches": [ + { + "case": { + "$gte": [ + { + "$avg": [ + "$scores" + ] + }, + { + "$numberInt": "90" + } + ] + }, + "then": "Doing great!" + }, + { + "case": { + "$and": [ + { + "$gte": [ + { + "$avg": [ + "$scores" + ] + }, + { + "$numberInt": "80" + } + ] + }, + { + "$lt": [ + { + "$avg": [ + "$scores" + ] + }, + { + "$numberInt": "90" + } + ] + } + ] + }, + "then": "Doing pretty well." + }, + { + "case": { + "$lt": [ + { + "$avg": [ + "$scores" + ] + }, + { + "$numberInt": "80" + } + ] + }, + "then": "Needs improvement." + } + ], + "default": "No scores found." + } + } + } + } + ] + JSON; + /** * Matrix Transposition * diff --git a/tests/Builder/Expression/SwitchOperatorTest.php b/tests/Builder/Expression/SwitchOperatorTest.php new file mode 100644 index 000000000..bf45fabae --- /dev/null +++ b/tests/Builder/Expression/SwitchOperatorTest.php @@ -0,0 +1,69 @@ +assertSamePipeline(Pipelines::SwitchExample, $pipeline); + } +} diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 107132373..727713f71 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -1040,33 +1040,6 @@ enum Pipelines: string ] JSON; - /** - * Query on Legacy Coordinates - * - * @see https://www.mongodb.com/docs/manual/reference/operator/query/near/#query-on-legacy-coordinates - */ - case NearQueryOnLegacyCoordinates = <<<'JSON' - [ - { - "$match": { - "location": { - "$near": [ - { - "$numberDouble": "-73.966700000000003001" - }, - { - "$numberDouble": "40.780000000000001137" - } - ], - "$maxDistance": { - "$numberDouble": "0.10000000000000000555" - } - } - } - } - ] - JSON; - /** * Specify Center Point Using GeoJSON * @@ -1102,33 +1075,6 @@ enum Pipelines: string ] JSON; - /** - * Specify Center Point Using Legacy Coordinates - * - * @see https://www.mongodb.com/docs/manual/reference/operator/query/nearSphere/#specify-center-point-using-legacy-coordinates - */ - case NearSphereSpecifyCenterPointUsingLegacyCoordinates = <<<'JSON' - [ - { - "$match": { - "location": { - "$nearSphere": [ - { - "$numberDouble": "-73.966700000000003001" - }, - { - "$numberDouble": "40.780000000000001137" - } - ], - "$maxDistance": { - "$numberDouble": "0.10000000000000000555" - } - } - } - } - ] - JSON; - /** * Select on Unmatching Documents * From abe57b578f3cb2fbafe22149c036f0d77a30f8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 17 Jan 2024 13:58:23 +0100 Subject: [PATCH 30/95] PHPLIB-1349 Add tests on Data Size Operators (#31) --- generator/config/expression/binarySize.yaml | 10 +++ generator/config/expression/bsonSize.yaml | 34 +++++++ .../Expression/BinarySizeOperatorTest.php | 30 +++++++ .../Expression/BsonSizeOperatorTest.php | 67 ++++++++++++++ tests/Builder/Expression/Pipelines.php | 88 +++++++++++++++++++ 5 files changed, 229 insertions(+) create mode 100644 tests/Builder/Expression/BinarySizeOperatorTest.php create mode 100644 tests/Builder/Expression/BsonSizeOperatorTest.php diff --git a/generator/config/expression/binarySize.yaml b/generator/config/expression/binarySize.yaml index a4f15e6bd..eb0146f8c 100644 --- a/generator/config/expression/binarySize.yaml +++ b/generator/config/expression/binarySize.yaml @@ -13,3 +13,13 @@ arguments: - resolvesToString - resolvesToBinData - resolvesToNull +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/binarySize/#example' + pipeline: + - + $project: + name: '$name' + imageSize: + $binarySize: '$binary' diff --git a/generator/config/expression/bsonSize.yaml b/generator/config/expression/bsonSize.yaml index 0009a27a3..712188c52 100644 --- a/generator/config/expression/bsonSize.yaml +++ b/generator/config/expression/bsonSize.yaml @@ -12,3 +12,37 @@ arguments: type: - resolvesToObject - resolvesToNull +tests: + - + name: 'Return Sizes of Documents' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bsonSize/#return-sizes-of-documents' + pipeline: + - + $project: + name: 1 + object_size: + $bsonSize: '$$ROOT' + - + name: 'Return Combined Size of All Documents in a Collection' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bsonSize/#return-combined-size-of-all-documents-in-a-collection' + pipeline: + - + $group: + _id: ~ + combined_object_size: + $sum: + $bsonSize: '$$ROOT' + - + name: 'Return Document with Largest Specified Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bsonSize/#return-document-with-largest-specified-field' + pipeline: + - + $project: + name: '$name' + task_object_size: + $bsonSize: '$current_task' + - + $sort: + task_object_size: -1 + - + $limit: 1 diff --git a/tests/Builder/Expression/BinarySizeOperatorTest.php b/tests/Builder/Expression/BinarySizeOperatorTest.php new file mode 100644 index 000000000..8b0653315 --- /dev/null +++ b/tests/Builder/Expression/BinarySizeOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::BinarySizeExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/BsonSizeOperatorTest.php b/tests/Builder/Expression/BsonSizeOperatorTest.php new file mode 100644 index 000000000..8da3239d7 --- /dev/null +++ b/tests/Builder/Expression/BsonSizeOperatorTest.php @@ -0,0 +1,67 @@ +assertSamePipeline(Pipelines::BsonSizeReturnCombinedSizeOfAllDocumentsInACollection, $pipeline); + } + + public function testReturnDocumentWithLargestSpecifiedField(): void + { + $pipeline = new Pipeline( + Stage::project( + name: Expression::stringFieldPath('name'), + task_object_size: Expression::bsonSize( + Expression::objectFieldPath('current_task'), + ), + ), + Stage::sort( + object(task_object_size: -1), + ), + Stage::limit(1), + ); + + $this->assertSamePipeline(Pipelines::BsonSizeReturnDocumentWithLargestSpecifiedField, $pipeline); + } + + public function testReturnSizesOfDocuments(): void + { + $pipeline = new Pipeline( + Stage::project( + name: 1, + object_size: Expression::bsonSize( + Expression::variable('ROOT'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::BsonSizeReturnSizesOfDocuments, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 31be57265..138905d1c 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -198,6 +198,24 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/binarySize/#example + */ + case BinarySizeExample = <<<'JSON' + [ + { + "$project": { + "name": "$name", + "imageSize": { + "$binarySize": "$binary" + } + } + } + ] + JSON; + /** * Bitwise AND with Two Integers * @@ -319,6 +337,76 @@ enum Pipelines: string ] JSON; + /** + * Return Sizes of Documents + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bsonSize/#return-sizes-of-documents + */ + case BsonSizeReturnSizesOfDocuments = <<<'JSON' + [ + { + "$project": { + "name": { + "$numberInt": "1" + }, + "object_size": { + "$bsonSize": "$$ROOT" + } + } + } + ] + JSON; + + /** + * Return Combined Size of All Documents in a Collection + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bsonSize/#return-combined-size-of-all-documents-in-a-collection + */ + case BsonSizeReturnCombinedSizeOfAllDocumentsInACollection = <<<'JSON' + [ + { + "$group": { + "_id": null, + "combined_object_size": { + "$sum": { + "$bsonSize": "$$ROOT" + } + } + } + } + ] + JSON; + + /** + * Return Document with Largest Specified Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bsonSize/#return-document-with-largest-specified-field + */ + case BsonSizeReturnDocumentWithLargestSpecifiedField = <<<'JSON' + [ + { + "$project": { + "name": "$name", + "task_object_size": { + "$bsonSize": "$current_task" + } + } + }, + { + "$sort": { + "task_object_size": { + "$numberInt": "-1" + } + } + }, + { + "$limit": { + "$numberInt": "1" + } + } + ] + JSON; + /** * Example * From defeaeb30041aadc797f3222adba195f4b6d3257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 18 Jan 2024 11:22:10 +0100 Subject: [PATCH 31/95] Fix generator: builder dependency and nowdoc incompatibility (#33) * Add mongodb/builder package required by the generator * Upgrade nette/php-generator to support nowdoc, used in WhereOperatorTest --- generator/composer.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/generator/composer.json b/generator/composer.json index 1416655c4..f5bb8c4be 100644 --- a/generator/composer.json +++ b/generator/composer.json @@ -15,9 +15,10 @@ "require": { "php": ">=8.1", "ext-mongodb": "^1.17.0", + "mongodb/builder": "@dev", "mongodb/mongodb": "^1.17.0", - "nette/php-generator": "^4", - "nikic/php-parser": "^4.17", + "nette/php-generator": "dev-master#933bb0368df508c759c8c9a047873741fcd9fe17", + "nikic/php-parser": "^5", "symfony/console": "^6.3|^7.0", "symfony/finder": "^6.3|^7.0", "symfony/yaml": "^6.3|^7.0" From 2660e6d77aa27fad51defb36b3d13845c4fd8e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 18 Jan 2024 11:59:16 +0100 Subject: [PATCH 32/95] PHPLIB-1359 Add tests on Accumulators ($group, $bucket, $bucketAuto, $setWindowFields) (#29) --- generator/config/accumulator/avg.yaml | 30 + generator/config/accumulator/bottom.yaml | 32 + generator/config/accumulator/bottomN.yaml | 55 + generator/config/accumulator/count.yaml | 26 + generator/config/accumulator/firstN.yaml | 65 + generator/config/accumulator/max.yaml | 31 + generator/config/accumulator/maxN.yaml | 51 + generator/config/accumulator/median.yaml | 34 + .../config/accumulator/mergeObjects.yaml | 11 +- generator/config/accumulator/min.yaml | 26 + generator/config/accumulator/percentile.yaml | 65 + generator/config/accumulator/push.yaml | 38 + generator/config/accumulator/stdDevPop.yaml | 25 + generator/config/accumulator/stdDevSamp.yaml | 28 + generator/config/accumulator/sum.yaml | 39 + generator/config/accumulator/top.yaml | 32 + generator/config/accumulator/topN.yaml | 55 + generator/config/expression/avg.yaml | 19 + generator/config/expression/firstN.yaml | 18 +- generator/config/expression/max.yaml | 19 + generator/config/expression/median.yaml | 17 + generator/config/expression/min.yaml | 19 + generator/config/expression/percentile.yaml | 15 + generator/config/expression/stdDevPop.yaml | 10 + generator/config/expression/sum.yaml | 19 + generator/config/stage/setWindowFields.yaml | 13 +- .../src/Definition/OperatorDefinition.php | 8 +- generator/src/Definition/YamlReader.php | 8 +- src/Builder/Accumulator/FactoryTrait.php | 7 +- .../Accumulator/MergeObjectsAccumulator.php | 20 +- .../Accumulator/StdDevPopAccumulator.php | 3 +- .../Accumulator/StdDevSampAccumulator.php | 3 +- src/Builder/Expression/FactoryTrait.php | 11 +- src/Builder/Expression/MedianOperator.php | 22 +- src/Builder/Expression/PercentileOperator.php | 12 +- src/Builder/Stage/FactoryTrait.php | 6 +- src/Builder/Stage/SetWindowFieldsStage.php | 13 +- .../Accumulator/AvgAccumulatorTest.php | 61 + .../Accumulator/BottomAccumulatorTest.php | 62 + .../Accumulator/BottomNAccumulatorTest.php | 91 + .../Accumulator/CountAccumulatorTest.php | 51 + .../Accumulator/FirstNAccumulatorTest.php | 86 + .../Accumulator/MaxAccumulatorTest.php | 61 + .../Accumulator/MaxNAccumulatorTest.php | 85 + .../Accumulator/MedianAccumulatorTest.php | 61 + .../MergeObjectsAccumulatorTest.php | 31 + .../Accumulator/MinAccumulatorTest.php | 55 + .../Accumulator/PercentileAccumulatorTest.php | 94 ++ tests/Builder/Accumulator/Pipelines.php | 1481 +++++++++++++++-- .../Accumulator/PushAccumulatorTest.php | 71 + .../Accumulator/StdDevPopAccumulatorTest.php | 55 + .../Accumulator/StdDevSampAccumulatorTest.php | 56 + .../Accumulator/SumAccumulatorTest.php | 66 + .../Accumulator/TopAccumulatorTest.php | 62 + .../Accumulator/TopNAccumulatorTest.php | 91 + tests/Builder/Expression/AvgOperatorTest.php | 36 + .../Builder/Expression/FirstNOperatorTest.php | 21 + tests/Builder/Expression/MaxOperatorTest.php | 36 + .../Builder/Expression/MedianOperatorTest.php | 36 + tests/Builder/Expression/MinOperatorTest.php | 36 + .../Expression/PercentileOperatorTest.php | 37 + tests/Builder/Expression/Pipelines.php | 249 +++ .../Expression/StdDevPopOperatorTest.php | 29 + tests/Builder/Expression/SumOperatorTest.php | 36 + 64 files changed, 3887 insertions(+), 154 deletions(-) create mode 100644 tests/Builder/Accumulator/AvgAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/BottomAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/BottomNAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/CountAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/MaxAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/MaxNAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/MedianAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/MergeObjectsAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/MinAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/PercentileAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/PushAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/StdDevPopAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/StdDevSampAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/SumAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/TopAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/TopNAccumulatorTest.php create mode 100644 tests/Builder/Expression/AvgOperatorTest.php create mode 100644 tests/Builder/Expression/MaxOperatorTest.php create mode 100644 tests/Builder/Expression/MedianOperatorTest.php create mode 100644 tests/Builder/Expression/MinOperatorTest.php create mode 100644 tests/Builder/Expression/PercentileOperatorTest.php create mode 100644 tests/Builder/Expression/StdDevPopOperatorTest.php create mode 100644 tests/Builder/Expression/SumOperatorTest.php diff --git a/generator/config/accumulator/avg.yaml b/generator/config/accumulator/avg.yaml index 7854f3d99..3777bbf98 100644 --- a/generator/config/accumulator/avg.yaml +++ b/generator/config/accumulator/avg.yaml @@ -13,3 +13,33 @@ arguments: name: expression type: - resolvesToNumber +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/avg/#use-in--group-stage' + pipeline: + - $group: + _id: '$item' + avgAmount: + $avg: + $multiply: + - '$price' + - '$quantity' + avgQuantity: + $avg: '$quantity' + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/avg/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + averageQuantityForState: + $avg: '$quantity' + window: + documents: + - 'unbounded' + - 'current' diff --git a/generator/config/accumulator/bottom.yaml b/generator/config/accumulator/bottom.yaml index bddfa2274..4ebb897ea 100644 --- a/generator/config/accumulator/bottom.yaml +++ b/generator/config/accumulator/bottom.yaml @@ -21,3 +21,35 @@ arguments: - expression description: | Represents the output for each element in the group and can be any expression. +tests: + - + name: 'Find the Bottom Score' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottom/#find-the-bottom-score' + pipeline: + - + $match: + gameId: 'G1' + - + $group: + _id: '$gameId' + playerId: + $bottom: + output: + - '$playerId' + - '$score' + sortBy: + score: -1 + - + name: 'Finding the Bottom Score Across Multiple Games' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottom/#finding-the-bottom-score-across-multiple-games' + pipeline: + - + $group: + _id: '$gameId' + playerId: + $bottom: + output: + - '$playerId' + - '$score' + sortBy: + score: -1 diff --git a/generator/config/accumulator/bottomN.yaml b/generator/config/accumulator/bottomN.yaml index 84e48cc18..30bca678c 100644 --- a/generator/config/accumulator/bottomN.yaml +++ b/generator/config/accumulator/bottomN.yaml @@ -28,3 +28,58 @@ arguments: - expression description: | Represents the output for each element in the group and can be any expression. +tests: + - + name: 'Find the Three Lowest Scores' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottomN/#find-the-three-lowest-scores' + pipeline: + - + $match: + gameId: 'G1' + - + $group: + _id: '$gameId' + playerId: + $bottomN: + output: + - '$playerId' + - '$score' + sortBy: + score: -1 + n: 3 + - + name: 'Finding the Three Lowest Score Documents Across Multiple Games' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottomN/#finding-the-three-lowest-score-documents-across-multiple-games' + pipeline: + - + $group: + _id: '$gameId' + playerId: + $bottomN: + output: + - '$playerId' + - '$score' + sortBy: + score: -1 + n: 3 + - + name: 'Computing n Based on the Group Key for $group' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottomN/#computing-n-based-on-the-group-key-for--group' + pipeline: + - + $group: + _id: + gameId: '$gameId' + gamescores: + $bottomN: + output: '$score' + n: + $cond: + if: + $eq: + - '$gameId' + - 'G2' + then: 1 + else: 3 + sortBy: + score: -1 diff --git a/generator/config/accumulator/count.yaml b/generator/config/accumulator/count.yaml index 158d0a9d8..d9819056d 100644 --- a/generator/config/accumulator/count.yaml +++ b/generator/config/accumulator/count.yaml @@ -9,3 +9,29 @@ description: | Returns the number of documents in the group or window. Distinct from the $count pipeline stage. New in MongoDB 5.0. +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/count-accumulator/#use-in--group-stage' + pipeline: + - + $group: + _id: '$state' + countNumberOfDocumentsForState: + $count: {} + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/count-accumulator/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + countNumberOfDocumentsForState: + $count: {} + window: + documents: + - 'unbounded' + - 'current' diff --git a/generator/config/accumulator/firstN.yaml b/generator/config/accumulator/firstN.yaml index 85676ea1c..cb7a6e96c 100644 --- a/generator/config/accumulator/firstN.yaml +++ b/generator/config/accumulator/firstN.yaml @@ -55,3 +55,68 @@ tests: $firstN: input: '$score' n: 5 + - + name: 'Find the First Three Player Scores for a Single Game' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#find-the-first-three-player-scores-for-a-single-game' + pipeline: + - + $match: + gameId: 'G1' + - + $group: + _id: '$gameId' + firstThreeScores: + $firstN: + input: + - '$playerId' + - '$score' + n: 3 + - + name: 'Finding the First Three Player Scores Across Multiple Games' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#finding-the-first-three-player-scores-across-multiple-games' + pipeline: + - + $group: + _id: '$gameId' + playerId: + $firstN: + input: + - '$playerId' + - '$score' + n: 3 + - + name: 'Using $sort With $firstN' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#using--sort-with--firstn' + pipeline: + - + $sort: + score: -1 + - + $group: + _id: '$gameId' + playerId: + $firstN: + input: + - '$playerId' + - '$score' + n: 3 + - + name: 'Computing n Based on the Group Key for $group' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#computing-n-based-on-the-group-key-for--group' + pipeline: + - + $group: + _id: + gameId: '$gameId' + gamescores: + $firstN: + input: '$score' + n: + $cond: + if: + $eq: + - '$gameId' + - 'G2' + then: 1 + else: 3 + diff --git a/generator/config/accumulator/max.yaml b/generator/config/accumulator/max.yaml index e183d86a8..165cefc43 100644 --- a/generator/config/accumulator/max.yaml +++ b/generator/config/accumulator/max.yaml @@ -13,3 +13,34 @@ arguments: name: expression type: - expression +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/#use-in--group-stage' + pipeline: + - + $group: + _id: '$item' + maxTotalAmount: + $max: + $multiply: + - '$price' + - '$quantity' + maxQuantity: + $max: '$quantity' + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + maximumQuantityForState: + $max: '$quantity' + window: + documents: + - 'unbounded' + - 'current' diff --git a/generator/config/accumulator/maxN.yaml b/generator/config/accumulator/maxN.yaml index ba0941cf8..4014782a8 100644 --- a/generator/config/accumulator/maxN.yaml +++ b/generator/config/accumulator/maxN.yaml @@ -20,3 +20,54 @@ arguments: - resolvesToInt description: | An expression that resolves to a positive integer. The integer specifies the number of array elements that $maxN returns. +tests: + - + name: 'Find the Maximum Three Scores for a Single Game' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN/#find-the-maximum-three-scores-for-a-single-game' + pipeline: + - + $match: + gameId: 'G1' + - + $group: + _id: '$gameId' + maxThreeScores: + $maxN: + input: + - '$score' + - '$playerId' + n: 3 + - + name: 'Finding the Maximum Three Scores Across Multiple Games' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN/#finding-the-maximum-three-scores-across-multiple-games' + pipeline: + - + $group: + _id: '$gameId' + maxScores: + $maxN: + input: + - '$score' + - '$playerId' + n: 3 + - + name: 'Computing n Based on the Group Key for $group' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN/#computing-n-based-on-the-group-key-for--group' + pipeline: + - + $group: + _id: + gameId: '$gameId' + gamescores: + $maxN: + input: + - '$score' + - '$playerId' + n: + $cond: + if: + $eq: + - '$gameId' + - 'G2' + then: 1 + else: 3 diff --git a/generator/config/accumulator/median.yaml b/generator/config/accumulator/median.yaml index 0cef61fef..708ea3502 100644 --- a/generator/config/accumulator/median.yaml +++ b/generator/config/accumulator/median.yaml @@ -25,3 +25,37 @@ arguments: - string # AccumulatorPercentile description: | The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. +tests: + - + name: 'Use $median as an Accumulator' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/#use-operatorname-as-an-accumulator' + pipeline: + - + $group: + _id: ~ + test01_median: + $median: + input: '$test01' + method: 'approximate' + - + name: 'Use $median in a $setWindowField Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/#use-operatorname-in-a--setwindowfield-stage' + pipeline: + - + $setWindowFields: + sortBy: + test01: 1 + output: + test01_median: + $median: + input: '$test01' + method: 'approximate' + window: + range: + - -3 + - 3 + - + $project: + _id: 0 + studentId: 1 + test01_median: 1 diff --git a/generator/config/accumulator/mergeObjects.yaml b/generator/config/accumulator/mergeObjects.yaml index bddb1db6f..d68728001 100644 --- a/generator/config/accumulator/mergeObjects.yaml +++ b/generator/config/accumulator/mergeObjects.yaml @@ -11,6 +11,15 @@ arguments: name: document type: - resolvesToObject - variadic: array description: | Any valid expression that resolves to a document. +tests: + - + name: '$mergeObjects as an Accumulator' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/#-mergeobjects-as-an-accumulator' + pipeline: + - + $group: + _id: '$item' + mergedSales: + $mergeObjects: '$quantity' diff --git a/generator/config/accumulator/min.yaml b/generator/config/accumulator/min.yaml index 346cf3ff9..226d56ec8 100644 --- a/generator/config/accumulator/min.yaml +++ b/generator/config/accumulator/min.yaml @@ -13,3 +13,29 @@ arguments: name: expression type: - expression +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/#use-in--group-stage' + pipeline: + - + $group: + _id: '$item' + minQuantity: + $min: '$quantity' + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + minimumQuantityForState: + $min: '$quantity' + window: + documents: + - 'unbounded' + - 'current' diff --git a/generator/config/accumulator/percentile.yaml b/generator/config/accumulator/percentile.yaml index c3195347c..b2a6147d4 100644 --- a/generator/config/accumulator/percentile.yaml +++ b/generator/config/accumulator/percentile.yaml @@ -35,3 +35,68 @@ arguments: - string # AccumulatorPercentile description: | The method that mongod uses to calculate the percentile value. The method must be 'approximate'. +tests: + - + name: 'Calculate a Single Value as an Accumulator' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/#calculate-a-single-value-as-an-accumulator' + pipeline: + - + $group: + _id: ~ + test01_percentiles: + $percentile: + input: '$test01' + p: + - 0.95 + method: 'approximate' + - + name: 'Calculate Multiple Values as an Accumulator' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/#calculate-multiple-values-as-an-accumulator' + pipeline: + - + $group: + _id: ~ + test01_percentiles: + $percentile: + input: '$test01' + p: [0.5, 0.75, 0.9, 0.95] + method: 'approximate' + test02_percentiles: + $percentile: + input: '$test02' + p: [0.5, 0.75, 0.9, 0.95] + method: 'approximate' + test03_percentiles: + $percentile: + input: '$test03' + p: [0.5, 0.75, 0.9, 0.95] + method: 'approximate' + test03_percent_alt: + $percentile: + input: '$test03' + p: [0.9, 0.5, 0.75, 0.95] + method: 'approximate' + - + name: 'Use $percentile in a $setWindowField Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/#use-operatorname-in-a--setwindowfield-stage' + pipeline: + - + $setWindowFields: + sortBy: + test01: 1 + output: + test01_95percentile: + $percentile: + input: '$test01' + p: + - 0.95 + method: 'approximate' + window: + range: + - -3 + - 3 + - + $project: + _id: 0 + studentId: 1 + test01_95percentile: 1 diff --git a/generator/config/accumulator/push.yaml b/generator/config/accumulator/push.yaml index c026d4b4c..1e705ea1a 100644 --- a/generator/config/accumulator/push.yaml +++ b/generator/config/accumulator/push.yaml @@ -13,3 +13,41 @@ arguments: name: expression type: - expression +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/push/#use-in--group-stage' + pipeline: + - + $sort: + date: 1 + item: 1 + - + $group: + _id: + day: + # $dayOfYear: '$date' + $dayOfYear: + date: '$date' + year: + # $year: '$date' + $year: + date: '$date' + itemsSold: + $push: + item: '$item' + quantity: '$quantity' + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/push/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + quantitiesForState: + $push: '$quantity' + window: + documents: ['unbounded', 'current'] diff --git a/generator/config/accumulator/stdDevPop.yaml b/generator/config/accumulator/stdDevPop.yaml index 836c4310b..8916456d4 100644 --- a/generator/config/accumulator/stdDevPop.yaml +++ b/generator/config/accumulator/stdDevPop.yaml @@ -3,6 +3,7 @@ name: $stdDevPop link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/' type: - accumulator + - window encode: single description: | Calculates the population standard deviation of the input values. Use if the values encompass the entire population of data you want to represent and do not wish to generalize about a larger population. $stdDevPop ignores non-numeric values. @@ -13,3 +14,27 @@ arguments: name: expression type: - resolvesToNumber +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/#use-in--group-stage' + pipeline: + - + $group: + _id: '$quiz' + stdDev: + $stdDevPop: '$score' + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + stdDevPopQuantityForState: + $stdDevPop: '$quantity' + window: + documents: ['unbounded', 'current'] diff --git a/generator/config/accumulator/stdDevSamp.yaml b/generator/config/accumulator/stdDevSamp.yaml index 64f6799f2..94ac33d15 100644 --- a/generator/config/accumulator/stdDevSamp.yaml +++ b/generator/config/accumulator/stdDevSamp.yaml @@ -3,6 +3,7 @@ name: $stdDevSamp link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevSamp/' type: - accumulator + - window encode: single description: | Calculates the sample standard deviation of the input values. Use if the values encompass a sample of a population of data from which to generalize about the population. $stdDevSamp ignores non-numeric values. @@ -13,3 +14,30 @@ arguments: name: expression type: - resolvesToNumber +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevSamp/#use-in--group-stage' + pipeline: + - + $sample: + size: 100 + - + $group: + _id: ~ + ageStdDev: + $stdDevSamp: '$age' + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevSamp/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + stdDevSampQuantityForState: + $stdDevSamp: '$quantity' + window: + documents: ['unbounded', 'current'] diff --git a/generator/config/accumulator/sum.yaml b/generator/config/accumulator/sum.yaml index 8dc498358..fb4a03ef3 100644 --- a/generator/config/accumulator/sum.yaml +++ b/generator/config/accumulator/sum.yaml @@ -13,3 +13,42 @@ arguments: name: expression type: - resolvesToNumber +tests: + - + name: 'Use in $group Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/#use-in--group-stage' + pipeline: + - + $group: + _id: + day: + # $dayOfYear: '$date' + $dayOfYear: + date: '$date' + year: + # $year: '$date' + $year: + date: '$date' + totalAmount: + $sum: + $multiply: + - '$price' + - '$quantity' + count: + $sum: 1 + - + name: 'Use in $setWindowFields Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/#use-in--setwindowfields-stage' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + sumQuantityForState: + $sum: '$quantity' + window: + documents: + - 'unbounded' + - 'current' diff --git a/generator/config/accumulator/top.yaml b/generator/config/accumulator/top.yaml index 3f298fe55..2f4deefa2 100644 --- a/generator/config/accumulator/top.yaml +++ b/generator/config/accumulator/top.yaml @@ -22,3 +22,35 @@ arguments: - expression description: | Represents the output for each element in the group and can be any expression. +tests: + - + name: 'Find the Top Score' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/top/#find-the-top-score' + pipeline: + - + $match: + gameId: 'G1' + - + $group: + _id: '$gameId' + playerId: + $top: + output: + - '$playerId' + - '$score' + sortBy: + score: -1 + - + name: 'Find the Top Score Across Multiple Games' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/top/#find-the-top-score-across-multiple-games' + pipeline: + - + $group: + _id: '$gameId' + playerId: + $top: + output: + - '$playerId' + - '$score' + sortBy: + score: -1 diff --git a/generator/config/accumulator/topN.yaml b/generator/config/accumulator/topN.yaml index a3fa45d6f..c8073e09f 100644 --- a/generator/config/accumulator/topN.yaml +++ b/generator/config/accumulator/topN.yaml @@ -28,3 +28,58 @@ arguments: - expression description: | Represents the output for each element in the group and can be any expression. +tests: + - + name: 'Find the Three Highest Scores' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/topN/#find-the-three-highest-scores' + pipeline: + - + $match: + gameId: 'G1' + - + $group: + _id: '$gameId' + playerId: + $topN: + output: + - '$playerId' + - '$score' + sortBy: + score: -1 + n: 3 + - + name: 'Finding the Three Highest Score Documents Across Multiple Games' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/topN/#finding-the-three-highest-score-documents-across-multiple-games' + pipeline: + - + $group: + _id: '$gameId' + playerId: + $topN: + output: + - '$playerId' + - '$score' + sortBy: + score: -1 + n: 3 + - + name: 'Computing n Based on the Group Key for $group' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/topN/#computing-n-based-on-the-group-key-for--group' + pipeline: + - + $group: + _id: + gameId: '$gameId' + gamescores: + $topN: + output: '$score' + n: + $cond: + if: + $eq: + - '$gameId' + - 'G2' + then: 1 + else: 3 + sortBy: + score: -1 diff --git a/generator/config/expression/avg.yaml b/generator/config/expression/avg.yaml index 554e8037a..c647e083a 100644 --- a/generator/config/expression/avg.yaml +++ b/generator/config/expression/avg.yaml @@ -13,3 +13,22 @@ arguments: type: - resolvesToNumber variadic: array +tests: + - + name: 'Use in $project Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/avg/#use-in--project-stage' + pipeline: + - + $project: + quizAvg: + # $avg: '$quizzes' + $avg: + - '$quizzes' + labAvg: + # $avg: '$labs' + $avg: + - '$labs' + examAvg: + $avg: + - '$final' + - '$midterm' diff --git a/generator/config/expression/firstN.yaml b/generator/config/expression/firstN.yaml index 23a67ab74..e914ff88c 100644 --- a/generator/config/expression/firstN.yaml +++ b/generator/config/expression/firstN.yaml @@ -22,8 +22,8 @@ arguments: tests: - - name: Example - link: https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN-array-element/#example + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN-array-element/#example' pipeline: - $addFields: @@ -31,3 +31,17 @@ tests: $firstN: n: 3 input: '$score' + - + name: 'Using $firstN as an Aggregation Expression' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#using--firstn-as-an-aggregation-expression' + pipeline: + - + $documents: + - + array: [10, 20, 30, 40] + - + $project: + firstThreeElements: + $firstN: + input: '$array' + n: 3 diff --git a/generator/config/expression/max.yaml b/generator/config/expression/max.yaml index dfb7a20fd..4f6588c96 100644 --- a/generator/config/expression/max.yaml +++ b/generator/config/expression/max.yaml @@ -13,3 +13,22 @@ arguments: type: - expression variadic: array +tests: + - + name: 'Use in $project Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/#use-in--project-stage' + pipeline: + - + $project: + quizMax: + # $max: '$quizzes' + $max: + - '$quizzes' + labMax: + # $max: '$labs' + $max: + - '$labs' + examMax: + $max: + - '$final' + - '$midterm' diff --git a/generator/config/expression/median.yaml b/generator/config/expression/median.yaml index 43512db54..2708f7f16 100644 --- a/generator/config/expression/median.yaml +++ b/generator/config/expression/median.yaml @@ -16,6 +16,7 @@ arguments: name: input type: - resolvesToNumber + - array # of number description: | $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. - @@ -24,3 +25,19 @@ arguments: - string # AccumulatorPercentile description: | The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. +tests: + - + name: 'Use $median in a $project Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/#use-operatorname-in-a--project-stage' + pipeline: + - + $project: + _id: 0 + studentId: 1 + testMedians: + $median: + input: + - '$test01' + - '$test02' + - '$test03' + method: 'approximate' diff --git a/generator/config/expression/min.yaml b/generator/config/expression/min.yaml index a0265aa50..f7d3df725 100644 --- a/generator/config/expression/min.yaml +++ b/generator/config/expression/min.yaml @@ -13,3 +13,22 @@ arguments: type: - expression variadic: array +tests: + - + name: 'Use in $project Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/#use-in--project-stage' + pipeline: + - + $project: + quizMin: + # $min: '$quizzes' + $min: + - '$quizzes' + labMin: + # $min: '$labs' + $min: + - '$labs' + examMin: + $min: + - '$final' + - '$midterm' diff --git a/generator/config/expression/percentile.yaml b/generator/config/expression/percentile.yaml index 9847ffd59..8c601cc83 100644 --- a/generator/config/expression/percentile.yaml +++ b/generator/config/expression/percentile.yaml @@ -19,6 +19,7 @@ arguments: name: input type: - resolvesToNumber + - array # of number description: | $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. - @@ -34,3 +35,17 @@ arguments: - string # AccumulatorPercentile description: | The method that mongod uses to calculate the percentile value. The method must be 'approximate'. +tests: + - + name: 'Use $percentile in a $project Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/#use-operatorname-in-a--project-stage' + pipeline: + - + $project: + _id: 0 + studentId: 1 + testPercentiles: + $percentile: + input: ['$test01', '$test02', '$test03'] + p: [0.5, 0.95] + method: 'approximate' diff --git a/generator/config/expression/stdDevPop.yaml b/generator/config/expression/stdDevPop.yaml index e0063e502..f5d4d91a6 100644 --- a/generator/config/expression/stdDevPop.yaml +++ b/generator/config/expression/stdDevPop.yaml @@ -14,3 +14,13 @@ arguments: type: - resolvesToNumber variadic: array +tests: + - + name: 'Use in $project Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/#use-in--project-stage' + pipeline: + - + $project: + stdDev: + # $stdDevPop: '$scores.score' + $stdDevPop: ['$scores.score'] diff --git a/generator/config/expression/sum.yaml b/generator/config/expression/sum.yaml index f1887f6ba..430f82c4b 100644 --- a/generator/config/expression/sum.yaml +++ b/generator/config/expression/sum.yaml @@ -13,3 +13,22 @@ arguments: type: - resolvesToNumber variadic: array +tests: + - + name: 'Use in $project Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/#use-in--project-stage' + pipeline: + - + $project: + quizTotal: + # $sum: '$quizzes' + $sum: + - '$quizzes' + labTotal: + # $sum: '$labs' + $sum: + - '$labs' + examTotal: + $sum: + - '$final' + - '$midterm' diff --git a/generator/config/stage/setWindowFields.yaml b/generator/config/stage/setWindowFields.yaml index 7afc5c990..844968608 100644 --- a/generator/config/stage/setWindowFields.yaml +++ b/generator/config/stage/setWindowFields.yaml @@ -8,12 +8,6 @@ description: | Groups documents into windows and applies one or more operators to the documents in each window. New in MongoDB 5.0. arguments: - - - name: partitionBy - type: - - expression - description: | - Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. - name: sortBy type: @@ -27,3 +21,10 @@ arguments: description: | Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. + - + name: partitionBy + type: + - expression + description: | + Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. + optional: true diff --git a/generator/src/Definition/OperatorDefinition.php b/generator/src/Definition/OperatorDefinition.php index 098d10185..50dd3db28 100644 --- a/generator/src/Definition/OperatorDefinition.php +++ b/generator/src/Definition/OperatorDefinition.php @@ -12,6 +12,7 @@ use function array_values; use function assert; use function count; +use function get_object_vars; use function sprintf; final class OperatorDefinition @@ -47,7 +48,7 @@ public function __construct( // Optional arguments must be after required arguments $requiredArgs = $optionalArgs = []; foreach ($arguments as $arg) { - $arg = new ArgumentDefinition(...$arg); + $arg = new ArgumentDefinition(...get_object_vars($arg)); if ($arg->optional) { $optionalArgs[] = $arg; } else { @@ -63,6 +64,9 @@ public function __construct( $this->arguments = array_merge($requiredArgs, $optionalArgs); - $this->tests = array_map(static fn (array $test): TestDefinition => new TestDefinition(...$test), array_values($tests)); + $this->tests = array_map( + static fn (object $test): TestDefinition => new TestDefinition(...get_object_vars($test)), + array_values($tests), + ); } } diff --git a/generator/src/Definition/YamlReader.php b/generator/src/Definition/YamlReader.php index 7cc2d620c..751d6a2e7 100644 --- a/generator/src/Definition/YamlReader.php +++ b/generator/src/Definition/YamlReader.php @@ -7,8 +7,7 @@ use Symfony\Component\Finder\Finder; use Symfony\Component\Yaml\Yaml; -use function assert; -use function is_array; +use function get_object_vars; final class YamlReader { @@ -20,9 +19,8 @@ public function read(string $dirname): array $definitions = []; foreach ($finder as $file) { - $operator = Yaml::parseFile($file->getPathname()); - assert(is_array($operator)); - $definitions[] = new OperatorDefinition(...$operator); + $operator = Yaml::parseFile($file->getPathname(), Yaml::PARSE_OBJECT | Yaml::PARSE_OBJECT_FOR_MAP); + $definitions[] = new OperatorDefinition(...get_object_vars($operator)); } return $definitions; diff --git a/src/Builder/Accumulator/FactoryTrait.php b/src/Builder/Accumulator/FactoryTrait.php index edae0228d..af211520f 100644 --- a/src/Builder/Accumulator/FactoryTrait.php +++ b/src/Builder/Accumulator/FactoryTrait.php @@ -337,14 +337,13 @@ public static function median( * Combines multiple documents into a single document. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/ - * @no-named-arguments - * @param Document|ResolvesToObject|Serializable|array|stdClass ...$document Any valid expression that resolves to a document. + * @param Document|ResolvesToObject|Serializable|array|stdClass $document Any valid expression that resolves to a document. */ public static function mergeObjects( - Document|Serializable|ResolvesToObject|stdClass|array ...$document, + Document|Serializable|ResolvesToObject|stdClass|array $document, ): MergeObjectsAccumulator { - return new MergeObjectsAccumulator(...$document); + return new MergeObjectsAccumulator($document); } /** diff --git a/src/Builder/Accumulator/MergeObjectsAccumulator.php b/src/Builder/Accumulator/MergeObjectsAccumulator.php index 8f1ee3d5f..7f163e023 100644 --- a/src/Builder/Accumulator/MergeObjectsAccumulator.php +++ b/src/Builder/Accumulator/MergeObjectsAccumulator.php @@ -14,11 +14,8 @@ use MongoDB\Builder\Type\AccumulatorInterface; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; -use MongoDB\Exception\InvalidArgumentException; use stdClass; -use function array_is_list; - /** * Combines multiple documents into a single document. * @@ -28,23 +25,14 @@ class MergeObjectsAccumulator implements AccumulatorInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $document Any valid expression that resolves to a document. */ - public readonly array $document; + /** @var Document|ResolvesToObject|Serializable|array|stdClass $document Any valid expression that resolves to a document. */ + public readonly Document|Serializable|ResolvesToObject|stdClass|array $document; /** - * @param Document|ResolvesToObject|Serializable|array|stdClass ...$document Any valid expression that resolves to a document. - * @no-named-arguments + * @param Document|ResolvesToObject|Serializable|array|stdClass $document Any valid expression that resolves to a document. */ - public function __construct(Document|Serializable|ResolvesToObject|stdClass|array ...$document) + public function __construct(Document|Serializable|ResolvesToObject|stdClass|array $document) { - if (\count($document) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $document, got %d.', 1, \count($document))); - } - - if (! array_is_list($document)) { - throw new InvalidArgumentException('Expected $document arguments to be a list (array), named arguments are not supported'); - } - $this->document = $document; } diff --git a/src/Builder/Accumulator/StdDevPopAccumulator.php b/src/Builder/Accumulator/StdDevPopAccumulator.php index fce7f5fdc..01fe39dff 100644 --- a/src/Builder/Accumulator/StdDevPopAccumulator.php +++ b/src/Builder/Accumulator/StdDevPopAccumulator.php @@ -14,6 +14,7 @@ use MongoDB\Builder\Type\AccumulatorInterface; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\WindowInterface; /** * Calculates the population standard deviation of the input values. Use if the values encompass the entire population of data you want to represent and do not wish to generalize about a larger population. $stdDevPop ignores non-numeric values. @@ -22,7 +23,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/ */ -class StdDevPopAccumulator implements AccumulatorInterface, OperatorInterface +class StdDevPopAccumulator implements AccumulatorInterface, WindowInterface, OperatorInterface { public const ENCODE = Encode::Single; diff --git a/src/Builder/Accumulator/StdDevSampAccumulator.php b/src/Builder/Accumulator/StdDevSampAccumulator.php index b3a83434e..54adb4170 100644 --- a/src/Builder/Accumulator/StdDevSampAccumulator.php +++ b/src/Builder/Accumulator/StdDevSampAccumulator.php @@ -14,6 +14,7 @@ use MongoDB\Builder\Type\AccumulatorInterface; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\WindowInterface; /** * Calculates the sample standard deviation of the input values. Use if the values encompass a sample of a population of data from which to generalize about the population. $stdDevSamp ignores non-numeric values. @@ -22,7 +23,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevSamp/ */ -class StdDevSampAccumulator implements AccumulatorInterface, OperatorInterface +class StdDevSampAccumulator implements AccumulatorInterface, WindowInterface, OperatorInterface { public const ENCODE = Encode::Single; diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index cca50e5b1..4112ec732 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -1250,10 +1250,13 @@ public static function maxN( * It is also available as an aggregation expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/ - * @param Decimal128|Int64|ResolvesToNumber|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. + * @param BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. * @param non-empty-string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ - public static function median(Decimal128|Int64|ResolvesToNumber|float|int $input, string $method): MedianOperator + public static function median( + Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input, + string $method, + ): MedianOperator { return new MedianOperator($input, $method); } @@ -1453,13 +1456,13 @@ public static function or( * It is also available as an aggregation expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/ - * @param Decimal128|Int64|ResolvesToNumber|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. + * @param BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. * @param BSONArray|PackedArray|ResolvesToArray|array $p $percentile calculates a percentile value for each element in p. The elements represent percentages and must evaluate to numeric values in the range 0.0 to 1.0, inclusive. * $percentile returns results in the same order as the elements in p. * @param non-empty-string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. */ public static function percentile( - Decimal128|Int64|ResolvesToNumber|float|int $input, + Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input, PackedArray|ResolvesToArray|BSONArray|array $p, string $method, ): PercentileOperator diff --git a/src/Builder/Expression/MedianOperator.php b/src/Builder/Expression/MedianOperator.php index 0ba14a0d8..cc5a2ff5a 100644 --- a/src/Builder/Expression/MedianOperator.php +++ b/src/Builder/Expression/MedianOperator.php @@ -10,8 +10,14 @@ use MongoDB\BSON\Decimal128; use MongoDB\BSON\Int64; +use MongoDB\BSON\PackedArray; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Exception\InvalidArgumentException; +use MongoDB\Model\BSONArray; + +use function array_is_list; +use function is_array; /** * Returns an approximation of the median, the 50th percentile, as a scalar value. @@ -27,18 +33,24 @@ class MedianOperator implements ResolvesToDouble, OperatorInterface { public const ENCODE = Encode::Object; - /** @var Decimal128|Int64|ResolvesToNumber|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. */ - public readonly Decimal128|Int64|ResolvesToNumber|float|int $input; + /** @var BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. */ + public readonly Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input; /** @var non-empty-string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ public readonly string $method; /** - * @param Decimal128|Int64|ResolvesToNumber|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. + * @param BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. * @param non-empty-string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ - public function __construct(Decimal128|Int64|ResolvesToNumber|float|int $input, string $method) - { + public function __construct( + Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input, + string $method, + ) { + if (is_array($input) && ! array_is_list($input)) { + throw new InvalidArgumentException('Expected $input argument to be a list, got an associative array.'); + } + $this->input = $input; $this->method = $method; } diff --git a/src/Builder/Expression/PercentileOperator.php b/src/Builder/Expression/PercentileOperator.php index 2eadeb6b4..e4f8929c2 100644 --- a/src/Builder/Expression/PercentileOperator.php +++ b/src/Builder/Expression/PercentileOperator.php @@ -36,8 +36,8 @@ class PercentileOperator implements ResolvesToArray, OperatorInterface { public const ENCODE = Encode::Object; - /** @var Decimal128|Int64|ResolvesToNumber|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. */ - public readonly Decimal128|Int64|ResolvesToNumber|float|int $input; + /** @var BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. */ + public readonly Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input; /** * @var BSONArray|PackedArray|ResolvesToArray|array $p $percentile calculates a percentile value for each element in p. The elements represent percentages and must evaluate to numeric values in the range 0.0 to 1.0, inclusive. @@ -49,16 +49,20 @@ class PercentileOperator implements ResolvesToArray, OperatorInterface public readonly string $method; /** - * @param Decimal128|Int64|ResolvesToNumber|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. + * @param BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. * @param BSONArray|PackedArray|ResolvesToArray|array $p $percentile calculates a percentile value for each element in p. The elements represent percentages and must evaluate to numeric values in the range 0.0 to 1.0, inclusive. * $percentile returns results in the same order as the elements in p. * @param non-empty-string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. */ public function __construct( - Decimal128|Int64|ResolvesToNumber|float|int $input, + Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input, PackedArray|ResolvesToArray|BSONArray|array $p, string $method, ) { + if (is_array($input) && ! array_is_list($input)) { + throw new InvalidArgumentException('Expected $input argument to be a list, got an associative array.'); + } + $this->input = $input; if (is_array($p) && ! array_is_list($p)) { throw new InvalidArgumentException('Expected $p argument to be a list, got an associative array.'); diff --git a/src/Builder/Stage/FactoryTrait.php b/src/Builder/Stage/FactoryTrait.php index c96b8c564..4441381c5 100644 --- a/src/Builder/Stage/FactoryTrait.php +++ b/src/Builder/Stage/FactoryTrait.php @@ -581,18 +581,18 @@ public static function set(Type|ExpressionInterface|stdClass|array|bool|float|in * New in MongoDB 5.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. * @param Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. * @param Document|Serializable|array|stdClass $output Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. * A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. */ public static function setWindowFields( - Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy, Document|Serializable|stdClass|array $sortBy, Document|Serializable|stdClass|array $output, + Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy = Optional::Undefined, ): SetWindowFieldsStage { - return new SetWindowFieldsStage($partitionBy, $sortBy, $output); + return new SetWindowFieldsStage($sortBy, $output, $partitionBy); } /** diff --git a/src/Builder/Stage/SetWindowFieldsStage.php b/src/Builder/Stage/SetWindowFieldsStage.php index 400c1c795..4537e45a6 100644 --- a/src/Builder/Stage/SetWindowFieldsStage.php +++ b/src/Builder/Stage/SetWindowFieldsStage.php @@ -14,6 +14,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\StageInterface; use stdClass; @@ -27,9 +28,6 @@ class SetWindowFieldsStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. */ - public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy; - /** @var Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. */ public readonly Document|Serializable|stdClass|array $sortBy; @@ -39,20 +37,23 @@ class SetWindowFieldsStage implements StageInterface, OperatorInterface */ public readonly Document|Serializable|stdClass|array $output; + /** @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. */ + public readonly Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy; + /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. * @param Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. * @param Document|Serializable|array|stdClass $output Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. * A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. */ public function __construct( - Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy, Document|Serializable|stdClass|array $sortBy, Document|Serializable|stdClass|array $output, + Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy = Optional::Undefined, ) { - $this->partitionBy = $partitionBy; $this->sortBy = $sortBy; $this->output = $output; + $this->partitionBy = $partitionBy; } public function getOperator(): string diff --git a/tests/Builder/Accumulator/AvgAccumulatorTest.php b/tests/Builder/Accumulator/AvgAccumulatorTest.php new file mode 100644 index 000000000..9fd0eb3ed --- /dev/null +++ b/tests/Builder/Accumulator/AvgAccumulatorTest.php @@ -0,0 +1,61 @@ +assertSamePipeline(Pipelines::AvgUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + averageQuantityForState: Accumulator::outputWindow( + Accumulator::avg( + Expression::intFieldPath('quantity'), + ), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::AvgUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/BottomAccumulatorTest.php b/tests/Builder/Accumulator/BottomAccumulatorTest.php new file mode 100644 index 000000000..447e352e8 --- /dev/null +++ b/tests/Builder/Accumulator/BottomAccumulatorTest.php @@ -0,0 +1,62 @@ +assertSamePipeline(Pipelines::BottomFindTheBottomScore, $pipeline); + } + + public function testFindingTheBottomScoreAcrossMultipleGames(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('gameId'), + playerId: Accumulator::bottom( + output: [ + Expression::fieldPath('playerId'), + Expression::fieldPath('score'), + ], + sortBy: object( + score: -1, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::BottomFindingTheBottomScoreAcrossMultipleGames, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/BottomNAccumulatorTest.php b/tests/Builder/Accumulator/BottomNAccumulatorTest.php new file mode 100644 index 000000000..3cf7276ab --- /dev/null +++ b/tests/Builder/Accumulator/BottomNAccumulatorTest.php @@ -0,0 +1,91 @@ +assertSamePipeline(Pipelines::BottomNComputingNBasedOnTheGroupKeyForGroup, $pipeline); + } + + public function testFindTheThreeLowestScores(): void + { + $pipeline = new Pipeline( + Stage::match( + gameId: 'G1', + ), + Stage::group( + _id: Expression::fieldPath('gameId'), + playerId: Accumulator::bottomN( + output: [ + Expression::fieldPath('playerId'), + Expression::fieldPath('score'), + ], + sortBy: object( + score: -1, + ), + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::BottomNFindTheThreeLowestScores, $pipeline); + } + + public function testFindingTheThreeLowestScoreDocumentsAcrossMultipleGames(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('gameId'), + playerId: Accumulator::bottomN( + output: [ + Expression::fieldPath('playerId'), + Expression::fieldPath('score'), + ], + sortBy: object( + score: -1, + ), + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::BottomNFindingTheThreeLowestScoreDocumentsAcrossMultipleGames, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/CountAccumulatorTest.php b/tests/Builder/Accumulator/CountAccumulatorTest.php new file mode 100644 index 000000000..49e05f472 --- /dev/null +++ b/tests/Builder/Accumulator/CountAccumulatorTest.php @@ -0,0 +1,51 @@ +assertSamePipeline(Pipelines::CountUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + countNumberOfDocumentsForState: Accumulator::outputWindow( + Accumulator::count(), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::CountUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/FirstNAccumulatorTest.php b/tests/Builder/Accumulator/FirstNAccumulatorTest.php index 58821d2dd..58879b836 100644 --- a/tests/Builder/Accumulator/FirstNAccumulatorTest.php +++ b/tests/Builder/Accumulator/FirstNAccumulatorTest.php @@ -17,6 +17,69 @@ */ class FirstNAccumulatorTest extends PipelineTestCase { + public function testComputingNBasedOnTheGroupKeyForGroup(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: object( + gameId: Expression::fieldPath('gameId'), + ), + gamescores: Accumulator::firstN( + input: Expression::fieldPath('score'), + n: Expression::cond( + if: Expression::eq( + Expression::fieldPath('gameId'), + 'G2', + ), + then: 1, + else: 3, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FirstNComputingNBasedOnTheGroupKeyForGroup, $pipeline); + } + + public function testFindTheFirstThreePlayerScoresForASingleGame(): void + { + $pipeline = new Pipeline( + Stage::match( + gameId: 'G1', + ), + Stage::group( + _id: Expression::fieldPath('gameId'), + firstThreeScores: Accumulator::firstN( + input: [ + Expression::fieldPath('playerId'), + Expression::fieldPath('score'), + ], + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FirstNFindTheFirstThreePlayerScoresForASingleGame, $pipeline); + } + + public function testFindingTheFirstThreePlayerScoresAcrossMultipleGames(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('gameId'), + playerId: Accumulator::firstN( + input: [ + Expression::fieldPath('playerId'), + Expression::fieldPath('score'), + ], + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FirstNFindingTheFirstThreePlayerScoresAcrossMultipleGames, $pipeline); + } + public function testNullAndMissingValues(): void { $pipeline = new Pipeline( @@ -38,4 +101,27 @@ public function testNullAndMissingValues(): void $this->assertSamePipeline(Pipelines::FirstNNullAndMissingValues, $pipeline); } + + public function testUsingSortWithFirstN(): void + { + $pipeline = new Pipeline( + Stage::sort( + object( + score: -1, + ), + ), + Stage::group( + _id: Expression::fieldPath('gameId'), + playerId: Accumulator::firstN( + input: [ + Expression::fieldPath('playerId'), + Expression::fieldPath('score'), + ], + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FirstNUsingSortWithFirstN, $pipeline); + } } diff --git a/tests/Builder/Accumulator/MaxAccumulatorTest.php b/tests/Builder/Accumulator/MaxAccumulatorTest.php new file mode 100644 index 000000000..cb948d903 --- /dev/null +++ b/tests/Builder/Accumulator/MaxAccumulatorTest.php @@ -0,0 +1,61 @@ +assertSamePipeline(Pipelines::MaxUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + maximumQuantityForState: Accumulator::outputWindow( + Accumulator::max( + Expression::intFieldPath('quantity'), + ), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::MaxUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/MaxNAccumulatorTest.php b/tests/Builder/Accumulator/MaxNAccumulatorTest.php new file mode 100644 index 000000000..19617834c --- /dev/null +++ b/tests/Builder/Accumulator/MaxNAccumulatorTest.php @@ -0,0 +1,85 @@ +assertSamePipeline(Pipelines::MaxNComputingNBasedOnTheGroupKeyForGroup, $pipeline); + } + + public function testFindTheMaximumThreeScoresForASingleGame(): void + { + $pipeline = new Pipeline( + Stage::match( + gameId: 'G1', + ), + Stage::group( + _id: Expression::fieldPath('gameId'), + maxThreeScores: Accumulator::maxN( + input: [ + Expression::fieldPath('score'), + Expression::fieldPath('playerId'), + ], + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::MaxNFindTheMaximumThreeScoresForASingleGame, $pipeline); + } + + public function testFindingTheMaximumThreeScoresAcrossMultipleGames(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('gameId'), + maxScores: Accumulator::maxN( + input: [ + Expression::fieldPath('score'), + Expression::fieldPath('playerId'), + ], + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::MaxNFindingTheMaximumThreeScoresAcrossMultipleGames, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/MedianAccumulatorTest.php b/tests/Builder/Accumulator/MedianAccumulatorTest.php new file mode 100644 index 000000000..21460eef4 --- /dev/null +++ b/tests/Builder/Accumulator/MedianAccumulatorTest.php @@ -0,0 +1,61 @@ +assertSamePipeline(Pipelines::MedianUseMedianAsAnAccumulator, $pipeline); + } + + public function testUseMedianInASetWindowFieldStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + sortBy: object( + test01: 1, + ), + output: object( + test01_median: Accumulator::outputWindow( + Accumulator::median( + input: Expression::intFieldPath('test01'), + method: 'approximate', + ), + range: [-3, 3], + ), + ), + ), + Stage::project( + _id: 0, + studentId: 1, + test01_median: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::MedianUseMedianInASetWindowFieldStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/MergeObjectsAccumulatorTest.php b/tests/Builder/Accumulator/MergeObjectsAccumulatorTest.php new file mode 100644 index 000000000..62fabc4f1 --- /dev/null +++ b/tests/Builder/Accumulator/MergeObjectsAccumulatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::MergeObjectsMergeObjectsAsAnAccumulator, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/MinAccumulatorTest.php b/tests/Builder/Accumulator/MinAccumulatorTest.php new file mode 100644 index 000000000..f71d26e6c --- /dev/null +++ b/tests/Builder/Accumulator/MinAccumulatorTest.php @@ -0,0 +1,55 @@ +assertSamePipeline(Pipelines::MinUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + minimumQuantityForState: Accumulator::outputWindow( + Accumulator::min( + Expression::intFieldPath('quantity'), + ), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::MinUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/PercentileAccumulatorTest.php b/tests/Builder/Accumulator/PercentileAccumulatorTest.php new file mode 100644 index 000000000..4c2f58541 --- /dev/null +++ b/tests/Builder/Accumulator/PercentileAccumulatorTest.php @@ -0,0 +1,94 @@ +assertSamePipeline(Pipelines::PercentileCalculateASingleValueAsAnAccumulator, $pipeline); + } + + public function testCalculateMultipleValuesAsAnAccumulator(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: null, + test01_percentiles: Accumulator::percentile( + input: Expression::numberFieldPath('test01'), + p: [0.5, 0.75, 0.9, 0.95], + method: 'approximate', + ), + test02_percentiles: Accumulator::percentile( + input: Expression::numberFieldPath('test02'), + p: [0.5, 0.75, 0.9, 0.95], + method: 'approximate', + ), + test03_percentiles: Accumulator::percentile( + input: Expression::numberFieldPath('test03'), + p: [0.5, 0.75, 0.9, 0.95], + method: 'approximate', + ), + test03_percent_alt: Accumulator::percentile( + input: Expression::numberFieldPath('test03'), + p: [0.9, 0.5, 0.75, 0.95], + method: 'approximate', + ), + ), + ); + + $this->assertSamePipeline(Pipelines::PercentileCalculateMultipleValuesAsAnAccumulator, $pipeline); + } + + public function testUsePercentileInASetWindowFieldStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + sortBy: object( + test01: 1, + ), + output: object( + test01_95percentile: Accumulator::outputWindow( + Accumulator::percentile( + input: Expression::numberFieldPath('test01'), + p: [0.95], + method: 'approximate', + ), + range: [-3, 3], + ), + ), + ), + Stage::project( + _id: 0, + studentId: 1, + test01_95percentile: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::PercentileUsePercentileInASetWindowFieldStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/Pipelines.php b/tests/Builder/Accumulator/Pipelines.php index 0057da9fa..e499a083a 100644 --- a/tests/Builder/Accumulator/Pipelines.php +++ b/tests/Builder/Accumulator/Pipelines.php @@ -146,28 +146,1219 @@ enum Pipelines: string ] JSON; + /** + * Use in $group Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/avg/#use-in--group-stage + */ + case AvgUseInGroupStage = <<<'JSON' + [ + { + "$group": { + "_id": "$item", + "avgAmount": { + "$avg": { + "$multiply": [ + "$price", + "$quantity" + ] + } + }, + "avgQuantity": { + "$avg": "$quantity" + } + } + } + ] + JSON; + + /** + * Use in $setWindowFields Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/avg/#use-in--setwindowfields-stage + */ + case AvgUseInSetWindowFieldsStage = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "averageQuantityForState": { + "$avg": "$quantity", + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } + } + } + } + ] + JSON; + + /** + * Find the Bottom Score + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottom/#find-the-bottom-score + */ + case BottomFindTheBottomScore = <<<'JSON' + [ + { + "$match": { + "gameId": "G1" + } + }, + { + "$group": { + "_id": "$gameId", + "playerId": { + "$bottom": { + "output": [ + "$playerId", + "$score" + ], + "sortBy": { + "score": { + "$numberInt": "-1" + } + } + } + } + } + } + ] + JSON; + + /** + * Finding the Bottom Score Across Multiple Games + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottom/#finding-the-bottom-score-across-multiple-games + */ + case BottomFindingTheBottomScoreAcrossMultipleGames = <<<'JSON' + [ + { + "$group": { + "_id": "$gameId", + "playerId": { + "$bottom": { + "output": [ + "$playerId", + "$score" + ], + "sortBy": { + "score": { + "$numberInt": "-1" + } + } + } + } + } + } + ] + JSON; + + /** + * Find the Three Lowest Scores + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottomN/#find-the-three-lowest-scores + */ + case BottomNFindTheThreeLowestScores = <<<'JSON' + [ + { + "$match": { + "gameId": "G1" + } + }, + { + "$group": { + "_id": "$gameId", + "playerId": { + "$bottomN": { + "output": [ + "$playerId", + "$score" + ], + "sortBy": { + "score": { + "$numberInt": "-1" + } + }, + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Finding the Three Lowest Score Documents Across Multiple Games + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottomN/#finding-the-three-lowest-score-documents-across-multiple-games + */ + case BottomNFindingTheThreeLowestScoreDocumentsAcrossMultipleGames = <<<'JSON' + [ + { + "$group": { + "_id": "$gameId", + "playerId": { + "$bottomN": { + "output": [ + "$playerId", + "$score" + ], + "sortBy": { + "score": { + "$numberInt": "-1" + } + }, + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Computing n Based on the Group Key for $group + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottomN/#computing-n-based-on-the-group-key-for--group + */ + case BottomNComputingNBasedOnTheGroupKeyForGroup = <<<'JSON' + [ + { + "$group": { + "_id": { + "gameId": "$gameId" + }, + "gamescores": { + "$bottomN": { + "output": "$score", + "n": { + "$cond": { + "if": { + "$eq": [ + "$gameId", + "G2" + ] + }, + "then": { + "$numberInt": "1" + }, + "else": { + "$numberInt": "3" + } + } + }, + "sortBy": { + "score": { + "$numberInt": "-1" + } + } + } + } + } + } + ] + JSON; + + /** + * Use in $group Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/count-accumulator/#use-in--group-stage + */ + case CountUseInGroupStage = <<<'JSON' + [ + { + "$group": { + "_id": "$state", + "countNumberOfDocumentsForState": { + "$count": {} + } + } + } + ] + JSON; + + /** + * Use in $setWindowFields Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/count-accumulator/#use-in--setwindowfields-stage + */ + case CountUseInSetWindowFieldsStage = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "countNumberOfDocumentsForState": { + "$count": {}, + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } + } + } + } + ] + JSON; + /** * Use in $group Stage * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/#use-in--group-stage */ - case FirstUseInGroupStage = <<<'JSON' + case FirstUseInGroupStage = <<<'JSON' + [ + { + "$sort": { + "item": { + "$numberInt": "1" + }, + "date": { + "$numberInt": "1" + } + } + }, + { + "$group": { + "_id": "$item", + "firstSale": { + "$first": "$date" + } + } + } + ] + JSON; + + /** + * Use in $setWindowFields Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/#use-in--setwindowfields-stage + */ + case FirstUseInSetWindowFieldsStage = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "firstOrderTypeForState": { + "$first": "$type", + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } + } + } + } + ] + JSON; + + /** + * Null and Missing Values + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#null-and-missing-values + */ + case FirstNNullAndMissingValues = <<<'JSON' + [ + { + "$documents": [ + { + "playerId": "PlayerA", + "gameId": "G1", + "score": { + "$numberInt": "1" + } + }, + { + "playerId": "PlayerB", + "gameId": "G1", + "score": { + "$numberInt": "2" + } + }, + { + "playerId": "PlayerC", + "gameId": "G1", + "score": { + "$numberInt": "3" + } + }, + { + "playerId": "PlayerD", + "gameId": "G1" + }, + { + "playerId": "PlayerE", + "gameId": "G1", + "score": null + } + ] + }, + { + "$group": { + "_id": "$gameId", + "firstFiveScores": { + "$firstN": { + "input": "$score", + "n": { + "$numberInt": "5" + } + } + } + } + } + ] + JSON; + + /** + * Find the First Three Player Scores for a Single Game + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#find-the-first-three-player-scores-for-a-single-game + */ + case FirstNFindTheFirstThreePlayerScoresForASingleGame = <<<'JSON' + [ + { + "$match": { + "gameId": "G1" + } + }, + { + "$group": { + "_id": "$gameId", + "firstThreeScores": { + "$firstN": { + "input": [ + "$playerId", + "$score" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Finding the First Three Player Scores Across Multiple Games + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#finding-the-first-three-player-scores-across-multiple-games + */ + case FirstNFindingTheFirstThreePlayerScoresAcrossMultipleGames = <<<'JSON' + [ + { + "$group": { + "_id": "$gameId", + "playerId": { + "$firstN": { + "input": [ + "$playerId", + "$score" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Using $sort With $firstN + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#using--sort-with--firstn + */ + case FirstNUsingSortWithFirstN = <<<'JSON' + [ + { + "$sort": { + "score": { + "$numberInt": "-1" + } + } + }, + { + "$group": { + "_id": "$gameId", + "playerId": { + "$firstN": { + "input": [ + "$playerId", + "$score" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Computing n Based on the Group Key for $group + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#computing-n-based-on-the-group-key-for--group + */ + case FirstNComputingNBasedOnTheGroupKeyForGroup = <<<'JSON' + [ + { + "$group": { + "_id": { + "gameId": "$gameId" + }, + "gamescores": { + "$firstN": { + "input": "$score", + "n": { + "$cond": { + "if": { + "$eq": [ + "$gameId", + "G2" + ] + }, + "then": { + "$numberInt": "1" + }, + "else": { + "$numberInt": "3" + } + } + } + } + } + } + } + ] + JSON; + + /** + * Use in $group Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/ + */ + case LastUseInGroupStage = <<<'JSON' + [ + { + "$sort": { + "item": { + "$numberInt": "1" + }, + "date": { + "$numberInt": "1" + } + } + }, + { + "$group": { + "_id": "$item", + "lastSalesDate": { + "$last": "$date" + } + } + } + ] + JSON; + + /** + * Use in $setWindowFields Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/#use-in--setwindowfields-stage + */ + case LastUseInSetWindowFieldsStage = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "lastOrderTypeForState": { + "$last": "$type", + "window": { + "documents": [ + "current", + "unbounded" + ] + } + } + } + } + } + ] + JSON; + + /** + * Find the Last Three Player Scores for a Single Game + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#find-the-last-three-player-scores-for-a-single-game + */ + case LastNFindTheLastThreePlayerScoresForASingleGame = <<<'JSON' + [ + { + "$match": { + "gameId": "G1" + } + }, + { + "$group": { + "_id": "$gameId", + "lastThreeScores": { + "$lastN": { + "input": [ + "$playerId", + "$score" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Finding the Last Three Player Scores Across Multiple Games + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#finding-the-last-three-player-scores-across-multiple-games + */ + case LastNFindingTheLastThreePlayerScoresAcrossMultipleGames = <<<'JSON' + [ + { + "$group": { + "_id": "$gameId", + "playerId": { + "$lastN": { + "input": [ + "$playerId", + "$score" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Using $sort With $lastN + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#using--sort-with--lastn + */ + case LastNUsingSortWithLastN = <<<'JSON' + [ + { + "$sort": { + "score": { + "$numberInt": "-1" + } + } + }, + { + "$group": { + "_id": "$gameId", + "playerId": { + "$lastN": { + "input": [ + "$playerId", + "$score" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Computing n Based on the Group Key for $group + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#computing-n-based-on-the-group-key-for--group + */ + case LastNComputingNBasedOnTheGroupKeyForGroup = <<<'JSON' + [ + { + "$group": { + "_id": { + "gameId": "$gameId" + }, + "gamescores": { + "$lastN": { + "input": "$score", + "n": { + "$cond": { + "if": { + "$eq": [ + "$gameId", + "G2" + ] + }, + "then": { + "$numberInt": "1" + }, + "else": { + "$numberInt": "3" + } + } + } + } + } + } + } + ] + JSON; + + /** + * Use in $group Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/#use-in--group-stage + */ + case MaxUseInGroupStage = <<<'JSON' + [ + { + "$group": { + "_id": "$item", + "maxTotalAmount": { + "$max": { + "$multiply": [ + "$price", + "$quantity" + ] + } + }, + "maxQuantity": { + "$max": "$quantity" + } + } + } + ] + JSON; + + /** + * Use in $setWindowFields Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/#use-in--setwindowfields-stage + */ + case MaxUseInSetWindowFieldsStage = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "maximumQuantityForState": { + "$max": "$quantity", + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } + } + } + } + ] + JSON; + + /** + * Find the Maximum Three Scores for a Single Game + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN/#find-the-maximum-three-scores-for-a-single-game + */ + case MaxNFindTheMaximumThreeScoresForASingleGame = <<<'JSON' + [ + { + "$match": { + "gameId": "G1" + } + }, + { + "$group": { + "_id": "$gameId", + "maxThreeScores": { + "$maxN": { + "input": [ + "$score", + "$playerId" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Finding the Maximum Three Scores Across Multiple Games + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN/#finding-the-maximum-three-scores-across-multiple-games + */ + case MaxNFindingTheMaximumThreeScoresAcrossMultipleGames = <<<'JSON' + [ + { + "$group": { + "_id": "$gameId", + "maxScores": { + "$maxN": { + "input": [ + "$score", + "$playerId" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Computing n Based on the Group Key for $group + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/maxN/#computing-n-based-on-the-group-key-for--group + */ + case MaxNComputingNBasedOnTheGroupKeyForGroup = <<<'JSON' + [ + { + "$group": { + "_id": { + "gameId": "$gameId" + }, + "gamescores": { + "$maxN": { + "input": [ + "$score", + "$playerId" + ], + "n": { + "$cond": { + "if": { + "$eq": [ + "$gameId", + "G2" + ] + }, + "then": { + "$numberInt": "1" + }, + "else": { + "$numberInt": "3" + } + } + } + } + } + } + } + ] + JSON; + + /** + * Use $median as an Accumulator + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/#use-operatorname-as-an-accumulator + */ + case MedianUseMedianAsAnAccumulator = <<<'JSON' + [ + { + "$group": { + "_id": null, + "test01_median": { + "$median": { + "input": "$test01", + "method": "approximate" + } + } + } + } + ] + JSON; + + /** + * Use $median in a $setWindowField Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/#use-operatorname-in-a--setwindowfield-stage + */ + case MedianUseMedianInASetWindowFieldStage = <<<'JSON' + [ + { + "$setWindowFields": { + "sortBy": { + "test01": { + "$numberInt": "1" + } + }, + "output": { + "test01_median": { + "$median": { + "input": "$test01", + "method": "approximate" + }, + "window": { + "range": [ + { + "$numberInt": "-3" + }, + { + "$numberInt": "3" + } + ] + } + } + } + } + }, + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "studentId": { + "$numberInt": "1" + }, + "test01_median": { + "$numberInt": "1" + } + } + } + ] + JSON; + + /** + * $mergeObjects as an Accumulator + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/mergeObjects/#-mergeobjects-as-an-accumulator + */ + case MergeObjectsMergeObjectsAsAnAccumulator = <<<'JSON' + [ + { + "$group": { + "_id": "$item", + "mergedSales": { + "$mergeObjects": "$quantity" + } + } + } + ] + JSON; + + /** + * Use in $group Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/#use-in--group-stage + */ + case MinUseInGroupStage = <<<'JSON' + [ + { + "$group": { + "_id": "$item", + "minQuantity": { + "$min": "$quantity" + } + } + } + ] + JSON; + + /** + * Use in $setWindowFields Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/#use-in--setwindowfields-stage + */ + case MinUseInSetWindowFieldsStage = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "minimumQuantityForState": { + "$min": "$quantity", + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } + } + } + } + ] + JSON; + + /** + * Calculate a Single Value as an Accumulator + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/#calculate-a-single-value-as-an-accumulator + */ + case PercentileCalculateASingleValueAsAnAccumulator = <<<'JSON' + [ + { + "$group": { + "_id": null, + "test01_percentiles": { + "$percentile": { + "input": "$test01", + "p": [ + { + "$numberDouble": "0.94999999999999995559" + } + ], + "method": "approximate" + } + } + } + } + ] + JSON; + + /** + * Calculate Multiple Values as an Accumulator + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/#calculate-multiple-values-as-an-accumulator + */ + case PercentileCalculateMultipleValuesAsAnAccumulator = <<<'JSON' + [ + { + "$group": { + "_id": null, + "test01_percentiles": { + "$percentile": { + "input": "$test01", + "p": [ + { + "$numberDouble": "0.5" + }, + { + "$numberDouble": "0.75" + }, + { + "$numberDouble": "0.9000000000000000222" + }, + { + "$numberDouble": "0.94999999999999995559" + } + ], + "method": "approximate" + } + }, + "test02_percentiles": { + "$percentile": { + "input": "$test02", + "p": [ + { + "$numberDouble": "0.5" + }, + { + "$numberDouble": "0.75" + }, + { + "$numberDouble": "0.9000000000000000222" + }, + { + "$numberDouble": "0.94999999999999995559" + } + ], + "method": "approximate" + } + }, + "test03_percentiles": { + "$percentile": { + "input": "$test03", + "p": [ + { + "$numberDouble": "0.5" + }, + { + "$numberDouble": "0.75" + }, + { + "$numberDouble": "0.9000000000000000222" + }, + { + "$numberDouble": "0.94999999999999995559" + } + ], + "method": "approximate" + } + }, + "test03_percent_alt": { + "$percentile": { + "input": "$test03", + "p": [ + { + "$numberDouble": "0.9000000000000000222" + }, + { + "$numberDouble": "0.5" + }, + { + "$numberDouble": "0.75" + }, + { + "$numberDouble": "0.94999999999999995559" + } + ], + "method": "approximate" + } + } + } + } + ] + JSON; + + /** + * Use $percentile in a $setWindowField Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/#use-operatorname-in-a--setwindowfield-stage + */ + case PercentileUsePercentileInASetWindowFieldStage = <<<'JSON' + [ + { + "$setWindowFields": { + "sortBy": { + "test01": { + "$numberInt": "1" + } + }, + "output": { + "test01_95percentile": { + "$percentile": { + "input": "$test01", + "p": [ + { + "$numberDouble": "0.94999999999999995559" + } + ], + "method": "approximate" + }, + "window": { + "range": [ + { + "$numberInt": "-3" + }, + { + "$numberInt": "3" + } + ] + } + } + } + } + }, + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "studentId": { + "$numberInt": "1" + }, + "test01_95percentile": { + "$numberInt": "1" + } + } + } + ] + JSON; + + /** + * Use in $group Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/push/#use-in--group-stage + */ + case PushUseInGroupStage = <<<'JSON' [ { "$sort": { - "item": { + "date": { "$numberInt": "1" }, - "date": { + "item": { "$numberInt": "1" } } }, { "$group": { - "_id": "$item", - "firstSale": { - "$first": "$date" + "_id": { + "day": { + "$dayOfYear": { + "date": "$date" + } + }, + "year": { + "$year": { + "date": "$date" + } + } + }, + "itemsSold": { + "$push": { + "item": "$item", + "quantity": "$quantity" + } } } } @@ -177,9 +1368,9 @@ enum Pipelines: string /** * Use in $setWindowFields Stage * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/#use-in--setwindowfields-stage + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/push/#use-in--setwindowfields-stage */ - case FirstUseInSetWindowFieldsStage = <<<'JSON' + case PushUseInSetWindowFieldsStage = <<<'JSON' [ { "$setWindowFields": { @@ -190,8 +1381,8 @@ enum Pipelines: string } }, "output": { - "firstOrderTypeForState": { - "$first": "$type", + "quantitiesForState": { + "$push": "$quantity", "window": { "documents": [ "unbounded", @@ -206,56 +1397,73 @@ enum Pipelines: string JSON; /** - * Null and Missing Values + * Use in $group Stage * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#null-and-missing-values + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/#use-in--group-stage */ - case FirstNNullAndMissingValues = <<<'JSON' + case StdDevPopUseInGroupStage = <<<'JSON' [ { - "$documents": [ - { - "playerId": "PlayerA", - "gameId": "G1", - "score": { + "$group": { + "_id": "$quiz", + "stdDev": { + "$stdDevPop": "$score" + } + } + } + ] + JSON; + + /** + * Use in $setWindowFields Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/#use-in--setwindowfields-stage + */ + case StdDevPopUseInSetWindowFieldsStage = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { "$numberInt": "1" } }, - { - "playerId": "PlayerB", - "gameId": "G1", - "score": { - "$numberInt": "2" - } - }, - { - "playerId": "PlayerC", - "gameId": "G1", - "score": { - "$numberInt": "3" + "output": { + "stdDevPopQuantityForState": { + "$stdDevPop": "$quantity", + "window": { + "documents": [ + "unbounded", + "current" + ] + } } - }, - { - "playerId": "PlayerD", - "gameId": "G1" - }, - { - "playerId": "PlayerE", - "gameId": "G1", - "score": null } - ] + } + } + ] + JSON; + + /** + * Use in $group Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevSamp/#use-in--group-stage + */ + case StdDevSampUseInGroupStage = <<<'JSON' + [ + { + "$sample": { + "size": { + "$numberInt": "100" + } + } }, { "$group": { - "_id": "$gameId", - "firstFiveScores": { - "$firstN": { - "input": "$score", - "n": { - "$numberInt": "5" - } - } + "_id": null, + "ageStdDev": { + "$stdDevSamp": "$age" } } } @@ -263,27 +1471,69 @@ enum Pipelines: string JSON; /** - * Use in $group Stage + * Use in $setWindowFields Stage * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/ + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevSamp/#use-in--setwindowfields-stage */ - case LastUseInGroupStage = <<<'JSON' + case StdDevSampUseInSetWindowFieldsStage = <<<'JSON' [ { - "$sort": { - "item": { - "$numberInt": "1" + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } }, - "date": { - "$numberInt": "1" + "output": { + "stdDevSampQuantityForState": { + "$stdDevSamp": "$quantity", + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } } } - }, + } + ] + JSON; + + /** + * Use in $group Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/#use-in--group-stage + */ + case SumUseInGroupStage = <<<'JSON' + [ { "$group": { - "_id": "$item", - "lastSalesDate": { - "$last": "$date" + "_id": { + "day": { + "$dayOfYear": { + "date": "$date" + } + }, + "year": { + "$year": { + "date": "$date" + } + } + }, + "totalAmount": { + "$sum": { + "$multiply": [ + "$price", + "$quantity" + ] + } + }, + "count": { + "$sum": { + "$numberInt": "1" + } } } } @@ -293,9 +1543,9 @@ enum Pipelines: string /** * Use in $setWindowFields Stage * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/#use-in--setwindowfields-stage + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/#use-in--setwindowfields-stage */ - case LastUseInSetWindowFieldsStage = <<<'JSON' + case SumUseInSetWindowFieldsStage = <<<'JSON' [ { "$setWindowFields": { @@ -306,12 +1556,12 @@ enum Pipelines: string } }, "output": { - "lastOrderTypeForState": { - "$last": "$type", + "sumQuantityForState": { + "$sum": "$quantity", "window": { "documents": [ - "current", - "unbounded" + "unbounded", + "current" ] } } @@ -322,11 +1572,11 @@ enum Pipelines: string JSON; /** - * Find the Last Three Player Scores for a Single Game + * Find the Top Score * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#find-the-last-three-player-scores-for-a-single-game + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/top/#find-the-top-score */ - case LastNFindTheLastThreePlayerScoresForASingleGame = <<<'JSON' + case TopFindTheTopScore = <<<'JSON' [ { "$match": { @@ -336,14 +1586,16 @@ enum Pipelines: string { "$group": { "_id": "$gameId", - "lastThreeScores": { - "$lastN": { - "input": [ + "playerId": { + "$top": { + "output": [ "$playerId", "$score" ], - "n": { - "$numberInt": "3" + "sortBy": { + "score": { + "$numberInt": "-1" + } } } } @@ -353,23 +1605,25 @@ enum Pipelines: string JSON; /** - * Finding the Last Three Player Scores Across Multiple Games + * Find the Top Score Across Multiple Games * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#finding-the-last-three-player-scores-across-multiple-games + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/top/#find-the-top-score-across-multiple-games */ - case LastNFindingTheLastThreePlayerScoresAcrossMultipleGames = <<<'JSON' + case TopFindTheTopScoreAcrossMultipleGames = <<<'JSON' [ { "$group": { "_id": "$gameId", "playerId": { - "$lastN": { - "input": [ + "$top": { + "output": [ "$playerId", "$score" ], - "n": { - "$numberInt": "3" + "sortBy": { + "score": { + "$numberInt": "-1" + } } } } @@ -379,28 +1633,62 @@ enum Pipelines: string JSON; /** - * Using $sort With $lastN + * Find the Three Highest Scores * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#using--sort-with--lastn + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/topN/#find-the-three-highest-scores */ - case LastNUsingSortWithLastN = <<<'JSON' + case TopNFindTheThreeHighestScores = <<<'JSON' [ { - "$sort": { - "score": { - "$numberInt": "-1" - } + "$match": { + "gameId": "G1" } }, { "$group": { "_id": "$gameId", "playerId": { - "$lastN": { - "input": [ + "$topN": { + "output": [ + "$playerId", + "$score" + ], + "sortBy": { + "score": { + "$numberInt": "-1" + } + }, + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Finding the Three Highest Score Documents Across Multiple Games + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/topN/#finding-the-three-highest-score-documents-across-multiple-games + */ + case TopNFindingTheThreeHighestScoreDocumentsAcrossMultipleGames = <<<'JSON' + [ + { + "$group": { + "_id": "$gameId", + "playerId": { + "$topN": { + "output": [ "$playerId", "$score" ], + "sortBy": { + "score": { + "$numberInt": "-1" + } + }, "n": { "$numberInt": "3" } @@ -414,9 +1702,9 @@ enum Pipelines: string /** * Computing n Based on the Group Key for $group * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lastN/#computing-n-based-on-the-group-key-for--group + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/topN/#computing-n-based-on-the-group-key-for--group */ - case LastNComputingNBasedOnTheGroupKeyForGroup = <<<'JSON' + case TopNComputingNBasedOnTheGroupKeyForGroup = <<<'JSON' [ { "$group": { @@ -424,8 +1712,8 @@ enum Pipelines: string "gameId": "$gameId" }, "gamescores": { - "$lastN": { - "input": "$score", + "$topN": { + "output": "$score", "n": { "$cond": { "if": { @@ -441,6 +1729,11 @@ enum Pipelines: string "$numberInt": "3" } } + }, + "sortBy": { + "score": { + "$numberInt": "-1" + } } } } diff --git a/tests/Builder/Accumulator/PushAccumulatorTest.php b/tests/Builder/Accumulator/PushAccumulatorTest.php new file mode 100644 index 000000000..25a2327f2 --- /dev/null +++ b/tests/Builder/Accumulator/PushAccumulatorTest.php @@ -0,0 +1,71 @@ +assertSamePipeline(Pipelines::PushUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + quantitiesForState: Accumulator::outputWindow( + Accumulator::push( + Expression::numberFieldPath('quantity'), + ), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::PushUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/StdDevPopAccumulatorTest.php b/tests/Builder/Accumulator/StdDevPopAccumulatorTest.php new file mode 100644 index 000000000..78943d9ad --- /dev/null +++ b/tests/Builder/Accumulator/StdDevPopAccumulatorTest.php @@ -0,0 +1,55 @@ +assertSamePipeline(Pipelines::StdDevPopUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + stdDevPopQuantityForState: Accumulator::outputWindow( + Accumulator::stdDevPop( + Expression::numberFieldPath('quantity'), + ), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::StdDevPopUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/StdDevSampAccumulatorTest.php b/tests/Builder/Accumulator/StdDevSampAccumulatorTest.php new file mode 100644 index 000000000..704eab827 --- /dev/null +++ b/tests/Builder/Accumulator/StdDevSampAccumulatorTest.php @@ -0,0 +1,56 @@ +assertSamePipeline(Pipelines::StdDevSampUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + stdDevSampQuantityForState: Accumulator::outputWindow( + Accumulator::stdDevSamp( + Expression::numberFieldPath('quantity'), + ), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::StdDevSampUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/SumAccumulatorTest.php b/tests/Builder/Accumulator/SumAccumulatorTest.php new file mode 100644 index 000000000..83a9ef147 --- /dev/null +++ b/tests/Builder/Accumulator/SumAccumulatorTest.php @@ -0,0 +1,66 @@ +assertSamePipeline(Pipelines::SumUseInGroupStage, $pipeline); + } + + public function testUseInSetWindowFieldsStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::fieldPath('state'), + sortBy: object( + orderDate: 1, + ), + output: object( + sumQuantityForState: Accumulator::outputWindow( + Accumulator::sum( + Expression::intFieldPath('quantity'), + ), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SumUseInSetWindowFieldsStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/TopAccumulatorTest.php b/tests/Builder/Accumulator/TopAccumulatorTest.php new file mode 100644 index 000000000..48fd0f782 --- /dev/null +++ b/tests/Builder/Accumulator/TopAccumulatorTest.php @@ -0,0 +1,62 @@ +assertSamePipeline(Pipelines::TopFindTheTopScore, $pipeline); + } + + public function testFindTheTopScoreAcrossMultipleGames(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('gameId'), + playerId: Accumulator::top( + output: [ + Expression::fieldPath('playerId'), + Expression::fieldPath('score'), + ], + sortBy: object( + score: -1, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::TopFindTheTopScoreAcrossMultipleGames, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/TopNAccumulatorTest.php b/tests/Builder/Accumulator/TopNAccumulatorTest.php new file mode 100644 index 000000000..c286c0610 --- /dev/null +++ b/tests/Builder/Accumulator/TopNAccumulatorTest.php @@ -0,0 +1,91 @@ +assertSamePipeline(Pipelines::TopNComputingNBasedOnTheGroupKeyForGroup, $pipeline); + } + + public function testFindTheThreeHighestScores(): void + { + $pipeline = new Pipeline( + Stage::match( + gameId: 'G1', + ), + Stage::group( + _id: Expression::fieldPath('gameId'), + playerId: Accumulator::topN( + output: [ + Expression::fieldPath('playerId'), + Expression::fieldPath('score'), + ], + sortBy: object( + score: -1, + ), + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::TopNFindTheThreeHighestScores, $pipeline); + } + + public function testFindingTheThreeHighestScoreDocumentsAcrossMultipleGames(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('gameId'), + playerId: Accumulator::topN( + output: [ + Expression::fieldPath('playerId'), + Expression::fieldPath('score'), + ], + sortBy: object( + score: -1, + ), + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::TopNFindingTheThreeHighestScoreDocumentsAcrossMultipleGames, $pipeline); + } +} diff --git a/tests/Builder/Expression/AvgOperatorTest.php b/tests/Builder/Expression/AvgOperatorTest.php new file mode 100644 index 000000000..595c49f1b --- /dev/null +++ b/tests/Builder/Expression/AvgOperatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::AvgUseInProjectStage, $pipeline); + } +} diff --git a/tests/Builder/Expression/FirstNOperatorTest.php b/tests/Builder/Expression/FirstNOperatorTest.php index 65ef27364..a09c356d7 100644 --- a/tests/Builder/Expression/FirstNOperatorTest.php +++ b/tests/Builder/Expression/FirstNOperatorTest.php @@ -9,6 +9,8 @@ use MongoDB\Builder\Stage; use MongoDB\Tests\Builder\PipelineTestCase; +use function MongoDB\object; + /** * Test $firstN expression */ @@ -24,4 +26,23 @@ public function testExample(): void $this->assertSamePipeline(Pipelines::FirstNExample, $pipeline); } + + public function testUsingFirstNAsAnAggregationExpression(): void + { + $pipeline = new Pipeline( + Stage::documents([ + object( + array: [10, 20, 30, 40], + ), + ]), + Stage::project( + firstThreeElements: Expression::firstN( + input: Expression::arrayFieldPath('array'), + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FirstNUsingFirstNAsAnAggregationExpression, $pipeline); + } } diff --git a/tests/Builder/Expression/MaxOperatorTest.php b/tests/Builder/Expression/MaxOperatorTest.php new file mode 100644 index 000000000..eb88daa85 --- /dev/null +++ b/tests/Builder/Expression/MaxOperatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::MaxUseInProjectStage, $pipeline); + } +} diff --git a/tests/Builder/Expression/MedianOperatorTest.php b/tests/Builder/Expression/MedianOperatorTest.php new file mode 100644 index 000000000..f231344f3 --- /dev/null +++ b/tests/Builder/Expression/MedianOperatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::MedianUseMedianInAProjectStage, $pipeline); + } +} diff --git a/tests/Builder/Expression/MinOperatorTest.php b/tests/Builder/Expression/MinOperatorTest.php new file mode 100644 index 000000000..11e1416a5 --- /dev/null +++ b/tests/Builder/Expression/MinOperatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::MinUseInProjectStage, $pipeline); + } +} diff --git a/tests/Builder/Expression/PercentileOperatorTest.php b/tests/Builder/Expression/PercentileOperatorTest.php new file mode 100644 index 000000000..9d1538bce --- /dev/null +++ b/tests/Builder/Expression/PercentileOperatorTest.php @@ -0,0 +1,37 @@ +assertSamePipeline(Pipelines::PercentileUsePercentileInAProjectStage, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 138905d1c..04690e9f1 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -198,6 +198,36 @@ enum Pipelines: string ] JSON; + /** + * Use in $project Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/avg/#use-in--project-stage + */ + case AvgUseInProjectStage = <<<'JSON' + [ + { + "$project": { + "quizAvg": { + "$avg": [ + "$quizzes" + ] + }, + "labAvg": { + "$avg": [ + "$labs" + ] + }, + "examAvg": { + "$avg": [ + "$final", + "$midterm" + ] + } + } + } + ] + JSON; + /** * Example * @@ -680,6 +710,48 @@ enum Pipelines: string ] JSON; + /** + * Using $firstN as an Aggregation Expression + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/#using--firstn-as-an-aggregation-expression + */ + case FirstNUsingFirstNAsAnAggregationExpression = <<<'JSON' + [ + { + "$documents": [ + { + "array": [ + { + "$numberInt": "10" + }, + { + "$numberInt": "20" + }, + { + "$numberInt": "30" + }, + { + "$numberInt": "40" + } + ] + } + ] + }, + { + "$project": { + "firstThreeElements": { + "$firstN": { + "input": "$array", + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + /** * Example * @@ -1099,6 +1171,36 @@ enum Pipelines: string ] JSON; + /** + * Use in $project Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/#use-in--project-stage + */ + case MaxUseInProjectStage = <<<'JSON' + [ + { + "$project": { + "quizMax": { + "$max": [ + "$quizzes" + ] + }, + "labMax": { + "$max": [ + "$labs" + ] + }, + "examMax": { + "$max": [ + "$final", + "$midterm" + ] + } + } + } + ] + JSON; + /** * Example * @@ -1121,6 +1223,36 @@ enum Pipelines: string ] JSON; + /** + * Use $median in a $project Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/#use-operatorname-in-a--project-stage + */ + case MedianUseMedianInAProjectStage = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "studentId": { + "$numberInt": "1" + }, + "testMedians": { + "$median": { + "input": [ + "$test01", + "$test02", + "$test03" + ], + "method": "approximate" + } + } + } + } + ] + JSON; + /** * $mergeObjects * @@ -1163,6 +1295,36 @@ enum Pipelines: string ] JSON; + /** + * Use in $project Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/#use-in--project-stage + */ + case MinUseInProjectStage = <<<'JSON' + [ + { + "$project": { + "quizMin": { + "$min": [ + "$quizzes" + ] + }, + "labMin": { + "$min": [ + "$labs" + ] + }, + "examMin": { + "$min": [ + "$final", + "$midterm" + ] + } + } + } + ] + JSON; + /** * Example * @@ -1332,6 +1494,44 @@ enum Pipelines: string ] JSON; + /** + * Use $percentile in a $project Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/percentile/#use-operatorname-in-a--project-stage + */ + case PercentileUsePercentileInAProjectStage = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "studentId": { + "$numberInt": "1" + }, + "testPercentiles": { + "$percentile": { + "input": [ + "$test01", + "$test02", + "$test03" + ], + "p": [ + { + "$numberDouble": "0.5" + }, + { + "$numberDouble": "0.94999999999999995559" + } + ], + "method": "approximate" + } + } + } + } + ] + JSON; + /** * Example * @@ -1815,6 +2015,25 @@ enum Pipelines: string ] JSON; + /** + * Use in $project Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/#use-in--project-stage + */ + case StdDevPopUseInProjectStage = <<<'JSON' + [ + { + "$project": { + "stdDev": { + "$stdDevPop": [ + "$scores.score" + ] + } + } + } + ] + JSON; + /** * Subtract Numbers * @@ -1891,6 +2110,36 @@ enum Pipelines: string ] JSON; + /** + * Use in $project Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/#use-in--project-stage + */ + case SumUseInProjectStage = <<<'JSON' + [ + { + "$project": { + "quizTotal": { + "$sum": [ + "$quizzes" + ] + }, + "labTotal": { + "$sum": [ + "$labs" + ] + }, + "examTotal": { + "$sum": [ + "$final", + "$midterm" + ] + } + } + } + ] + JSON; + /** * Example * diff --git a/tests/Builder/Expression/StdDevPopOperatorTest.php b/tests/Builder/Expression/StdDevPopOperatorTest.php new file mode 100644 index 000000000..fdff25f99 --- /dev/null +++ b/tests/Builder/Expression/StdDevPopOperatorTest.php @@ -0,0 +1,29 @@ +assertSamePipeline(Pipelines::StdDevPopUseInProjectStage, $pipeline); + } +} diff --git a/tests/Builder/Expression/SumOperatorTest.php b/tests/Builder/Expression/SumOperatorTest.php new file mode 100644 index 000000000..923162a20 --- /dev/null +++ b/tests/Builder/Expression/SumOperatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::SumUseInProjectStage, $pipeline); + } +} From 471e7f7eb386b52b27f31d74fc37f50c77da7707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 18 Jan 2024 20:51:26 +0100 Subject: [PATCH 33/95] PHPLIB-1348 Add tests on Custom Aggregation Expression Operators (#35) Automatically convert string into Javascript BSON --- generator/config/accumulator/accumulator.yaml | 49 ++++++++++--- generator/config/expression/function.yaml | 46 ++++++++++++ generator/config/query/where.yaml | 15 ++-- generator/config/schema.json | 2 +- generator/src/OperatorClassGenerator.php | 12 ++++ generator/src/OperatorFactoryGenerator.php | 2 + generator/src/OperatorGenerator.php | 6 +- .../Accumulator/AccumulatorAccumulator.php | 17 +++++ src/Builder/Expression/FactoryTrait.php | 4 +- src/Builder/Expression/FunctionOperator.php | 7 +- src/Builder/Query/WhereOperator.php | 6 ++ .../AccumulatorAccumulatorTest.php | 58 ++++++++++++--- tests/Builder/Accumulator/Pipelines.php | 16 ++--- .../Expression/FunctionOperatorTest.php | 71 +++++++++++++++++++ tests/Builder/Expression/Pipelines.php | 62 ++++++++++++++++ tests/Builder/Query/Pipelines.php | 8 ++- tests/Builder/Query/WhereOperatorTest.php | 6 +- 17 files changed, 349 insertions(+), 38 deletions(-) create mode 100644 tests/Builder/Expression/FunctionOperatorTest.php diff --git a/generator/config/accumulator/accumulator.yaml b/generator/config/accumulator/accumulator.yaml index 696a32173..e90fbbf52 100644 --- a/generator/config/accumulator/accumulator.yaml +++ b/generator/config/accumulator/accumulator.yaml @@ -64,14 +64,29 @@ tests: avgCopies: $accumulator: init: - $code: 'function () { return { count: 0, sum: 0 } }' + $code: |- + function() { + return { count: 0, sum: 0 } + } accumulate: - $code: 'function (state, numCopies) { return { count: state.count + 1, sum: state.sum + numCopies } }' + $code: |- + function(state, numCopies) { + return { count: state.count + 1, sum: state.sum + numCopies } + } accumulateArgs: [ "$copies" ], merge: - $code: 'function (state1, state2) { return { count: state1.count + state2.count, sum: state1.sum + state2.sum } }' + $code: |- + function(state1, state2) { + return { + count: state1.count + state2.count, + sum: state1.sum + state2.sum + } + } finalize: - $code: 'function (state) { return (state.sum / state.count) }' + $code: |- + function(state) { + return (state.sum / state.count) + } lang: 'js' - @@ -85,16 +100,34 @@ tests: restaurants: $accumulator: init: - $code: 'function (city, userProfileCity) { return { max: city === userProfileCity ? 3 : 1, restaurants: [] } }' + $code: |- + function(city, userProfileCity) { + return { max: city === userProfileCity ? 3 : 1, restaurants: [] } + } initArgs: - '$city' - 'Bettles' accumulate: - $code: 'function (state, restaurantName) { if (state.restaurants.length < state.max) { state.restaurants.push(restaurantName); } return state; }' + $code: |- + function(state, restaurantName) { + if (state.restaurants.length < state.max) { + state.restaurants.push(restaurantName); + } + return state; + } accumulateArgs: - '$name' merge: - $code: 'function (state1, state2) { return { max: state1.max, restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) } }' + $code: |- + function(state1, state2) { + return { + max: state1.max, + restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) + } + } finalize: - $code: 'function (state) { return state.restaurants }' + $code: |- + function(state) { + return state.restaurants + } lang: 'js' diff --git a/generator/config/expression/function.yaml b/generator/config/expression/function.yaml index 4b39a0e81..fa4dba8b5 100644 --- a/generator/config/expression/function.yaml +++ b/generator/config/expression/function.yaml @@ -21,8 +21,54 @@ arguments: - array description: | Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ]. + default: [] - name: lang type: - string default: js +tests: + - + name: 'Usage Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/#example-1--usage-example' + pipeline: + - + $addFields: + isFound: + $function: + body: + $code: |- + function(name) { + return hex_md5(name) == "15b0a220baa16331e8d80e15367677ad" + } + args: + - '$name' + lang: 'js' + message: + $function: + body: + $code: |- + function(name, scores) { + let total = Array.sum(scores); + return `Hello ${name}. Your total score is ${total}.` + } + args: + - '$name' + - '$scores' + lang: 'js' + - + name: 'Alternative to $where' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/#example-2--alternative-to--where' + pipeline: + - + $match: + $expr: + $function: + body: + $code: |- + function(name) { + return hex_md5(name) == "15b0a220baa16331e8d80e15367677ad"; + } + args: + - '$name' + lang: 'js' diff --git a/generator/config/query/where.yaml b/generator/config/query/where.yaml index 3c63007ca..5f5c974ab 100644 --- a/generator/config/query/where.yaml +++ b/generator/config/query/where.yaml @@ -18,14 +18,19 @@ tests: pipeline: - $match: - $where: 'function() { return hex_md5(this.name) == "9b53e667f30cd329dca1ec9e6a83e994" }' + $where: + $code: |- + function() { + return hex_md5(this.name) == "9b53e667f30cd329dca1ec9e6a83e994" + } - $match: $expr: $function: - body: |- - function(name) { - return hex_md5(name) == "9b53e667f30cd329dca1ec9e6a83e994"; - } + body: + $code: |- + function(name) { + return hex_md5(name) == "9b53e667f30cd329dca1ec9e6a83e994"; + } args: ['$name'] lang: 'js' diff --git a/generator/config/schema.json b/generator/config/schema.json index 7a475a863..11ccc1e32 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -166,7 +166,7 @@ }, "default": { "$comment": "The default value for the argument.", - "type": ["string", "number", "boolean"] + "type": ["array", "boolean", "number", "string"] } }, "required": [ diff --git a/generator/src/OperatorClassGenerator.php b/generator/src/OperatorClassGenerator.php index 8f7be8ec1..f0a9d2dc1 100644 --- a/generator/src/OperatorClassGenerator.php +++ b/generator/src/OperatorClassGenerator.php @@ -4,6 +4,7 @@ namespace MongoDB\CodeGenerator; +use MongoDB\BSON\Javascript; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\QueryObject; @@ -151,6 +152,17 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition PHP); } + + if ($type->javascript) { + $namespace->addUseFunction('is_string'); + $namespace->addUse(Javascript::class); + $constuctor->addBody(<<name})) { + \${$argument->name} = new Javascript(\${$argument->name}); + } + + PHP); + } } // Set property from constructor argument diff --git a/generator/src/OperatorFactoryGenerator.php b/generator/src/OperatorFactoryGenerator.php index 3e5506647..0bc27620f 100644 --- a/generator/src/OperatorFactoryGenerator.php +++ b/generator/src/OperatorFactoryGenerator.php @@ -80,6 +80,8 @@ private function addMethod(GeneratorDefinition $definition, OperatorDefinition $ } else { if ($argument->optional) { $parameter->setDefaultValue(new Literal('Optional::Undefined')); + } elseif ($argument->default !== null) { + $parameter->setDefaultValue($argument->default); } $method->addComment('@param ' . $type->doc . ' $' . $argument->name . rtrim(' ' . $argument->description)); diff --git a/generator/src/OperatorGenerator.php b/generator/src/OperatorGenerator.php index b194c9792..ccb1b8a5d 100644 --- a/generator/src/OperatorGenerator.php +++ b/generator/src/OperatorGenerator.php @@ -71,7 +71,7 @@ final protected function getType(string $type): ExpressionDefinition * Expression types can contain class names, interface, native types or "list". * PHPDoc types are more precise than native types, so we use them systematically even if redundant. * - * @return object{native:string,doc:string,use:list,list:bool,query:bool} + * @return object{native:string,doc:string,use:list,list:bool,query:bool,javascript:bool} */ final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass { @@ -117,6 +117,9 @@ final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass // If the argument is a query, we need to convert it to a QueryObject $isQuery = in_array('query', $arg->type, true); + // If the argument is code, we need to convert it to a Javascript object + $isJavascript = in_array('javascript', $arg->type, true); + // mixed can only be used as a standalone type if (in_array('mixed', $nativeTypes, true)) { $nativeTypes = ['mixed']; @@ -132,6 +135,7 @@ final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass 'use' => array_unique($use), 'list' => $listCheck, 'query' => $isQuery, + 'javascript' => $isJavascript, ]; } diff --git a/src/Builder/Accumulator/AccumulatorAccumulator.php b/src/Builder/Accumulator/AccumulatorAccumulator.php index 0f2088683..b4219ceca 100644 --- a/src/Builder/Accumulator/AccumulatorAccumulator.php +++ b/src/Builder/Accumulator/AccumulatorAccumulator.php @@ -20,6 +20,7 @@ use function array_is_list; use function is_array; +use function is_string; /** * Defines a custom accumulator function. @@ -70,13 +71,25 @@ public function __construct( Optional|PackedArray|ResolvesToArray|BSONArray|array $initArgs = Optional::Undefined, Optional|Javascript|string $finalize = Optional::Undefined, ) { + if (is_string($init)) { + $init = new Javascript($init); + } + $this->init = $init; + if (is_string($accumulate)) { + $accumulate = new Javascript($accumulate); + } + $this->accumulate = $accumulate; if (is_array($accumulateArgs) && ! array_is_list($accumulateArgs)) { throw new InvalidArgumentException('Expected $accumulateArgs argument to be a list, got an associative array.'); } $this->accumulateArgs = $accumulateArgs; + if (is_string($merge)) { + $merge = new Javascript($merge); + } + $this->merge = $merge; $this->lang = $lang; if (is_array($initArgs) && ! array_is_list($initArgs)) { @@ -84,6 +97,10 @@ public function __construct( } $this->initArgs = $initArgs; + if (is_string($finalize)) { + $finalize = new Javascript($finalize); + } + $this->finalize = $finalize; } diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 4112ec732..1389322f0 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -767,8 +767,8 @@ public static function floor(Decimal128|Int64|ResolvesToNumber|float|int $expres */ public static function function( Javascript|string $body, - PackedArray|BSONArray|array $args, - string $lang, + PackedArray|BSONArray|array $args = [], + string $lang = 'js', ): FunctionOperator { return new FunctionOperator($body, $args, $lang); diff --git a/src/Builder/Expression/FunctionOperator.php b/src/Builder/Expression/FunctionOperator.php index e99876192..a48d58e05 100644 --- a/src/Builder/Expression/FunctionOperator.php +++ b/src/Builder/Expression/FunctionOperator.php @@ -17,6 +17,7 @@ use function array_is_list; use function is_array; +use function is_string; /** * Defines a custom function. @@ -46,8 +47,12 @@ class FunctionOperator implements ResolvesToAny, OperatorInterface * @param BSONArray|PackedArray|array $args Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ]. * @param non-empty-string $lang */ - public function __construct(Javascript|string $body, PackedArray|BSONArray|array $args, string $lang = 'js') + public function __construct(Javascript|string $body, PackedArray|BSONArray|array $args = [], string $lang = 'js') { + if (is_string($body)) { + $body = new Javascript($body); + } + $this->body = $body; if (is_array($args) && ! array_is_list($args)) { throw new InvalidArgumentException('Expected $args argument to be a list, got an associative array.'); diff --git a/src/Builder/Query/WhereOperator.php b/src/Builder/Query/WhereOperator.php index 854d4d4dd..8be4a96dc 100644 --- a/src/Builder/Query/WhereOperator.php +++ b/src/Builder/Query/WhereOperator.php @@ -13,6 +13,8 @@ use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\QueryInterface; +use function is_string; + /** * Matches documents that satisfy a JavaScript expression. * @@ -30,6 +32,10 @@ class WhereOperator implements QueryInterface, OperatorInterface */ public function __construct(Javascript|string $function) { + if (is_string($function)) { + $function = new Javascript($function); + } + $this->function = $function; } diff --git a/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php b/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php index 5d8a6b361..8371b7ff2 100644 --- a/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php +++ b/tests/Builder/Accumulator/AccumulatorAccumulatorTest.php @@ -4,7 +4,6 @@ namespace MongoDB\Tests\Builder\Accumulator; -use MongoDB\BSON\Javascript; use MongoDB\Builder\Accumulator; use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; @@ -24,11 +23,30 @@ public function testUseAccumulatorToImplementTheAvgOperator(): void Stage::group( _id: Expression::fieldPath('author'), avgCopies: Accumulator::accumulator( - init: new Javascript('function () { return { count: 0, sum: 0 } }'), - accumulate: new Javascript('function (state, numCopies) { return { count: state.count + 1, sum: state.sum + numCopies } }'), + init: <<<'JS' + function() { + return { count: 0, sum: 0 } + } + JS, + accumulate: <<<'JS' + function(state, numCopies) { + return { count: state.count + 1, sum: state.sum + numCopies } + } + JS, accumulateArgs: [Expression::fieldPath('copies')], - merge: new Javascript('function (state1, state2) { return { count: state1.count + state2.count, sum: state1.sum + state2.sum } }'), - finalize: new Javascript('function (state) { return (state.sum / state.count) }'), + merge: <<<'JS' + function(state1, state2) { + return { + count: state1.count + state2.count, + sum: state1.sum + state2.sum + } + } + JS, + finalize: <<<'JS' + function(state) { + return (state.sum / state.count) + } + JS, lang: 'js', ), ), @@ -43,16 +61,38 @@ public function testUseInitArgsToVaryTheInitialStateByGroup(): void Stage::group( _id: object(city: Expression::fieldPath('city')), restaurants: Accumulator::accumulator( - init: new Javascript('function (city, userProfileCity) { return { max: city === userProfileCity ? 3 : 1, restaurants: [] } }'), - accumulate: new Javascript('function (state, restaurantName) { if (state.restaurants.length < state.max) { state.restaurants.push(restaurantName); } return state; }'), + init: <<<'JS' + function(city, userProfileCity) { + return { max: city === userProfileCity ? 3 : 1, restaurants: [] } + } + JS, + accumulate: <<<'JS' + function(state, restaurantName) { + if (state.restaurants.length < state.max) { + state.restaurants.push(restaurantName); + } + return state; + } + JS, accumulateArgs: [Expression::fieldPath('name')], - merge: new Javascript('function (state1, state2) { return { max: state1.max, restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) } }'), + merge: <<<'JS' + function(state1, state2) { + return { + max: state1.max, + restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) + } + } + JS, lang: 'js', initArgs: [ Expression::fieldPath('city'), 'Bettles', ], - finalize: new Javascript('function (state) { return state.restaurants }'), + finalize: <<<'JS' + function(state) { + return state.restaurants + } + JS, ), ), ); diff --git a/tests/Builder/Accumulator/Pipelines.php b/tests/Builder/Accumulator/Pipelines.php index e499a083a..f6644c9c1 100644 --- a/tests/Builder/Accumulator/Pipelines.php +++ b/tests/Builder/Accumulator/Pipelines.php @@ -23,19 +23,19 @@ enum Pipelines: string "avgCopies": { "$accumulator": { "init": { - "$code": "function () { return { count: 0, sum: 0 } }" + "$code": "function() {\n return { count: 0, sum: 0 }\n}" }, "accumulate": { - "$code": "function (state, numCopies) { return { count: state.count + 1, sum: state.sum + numCopies } }" + "$code": "function(state, numCopies) {\n return { count: state.count + 1, sum: state.sum + numCopies }\n}" }, "accumulateArgs": [ "$copies" ], "merge": { - "$code": "function (state1, state2) { return { count: state1.count + state2.count, sum: state1.sum + state2.sum } }" + "$code": "function(state1, state2) {\n return {\n count: state1.count + state2.count,\n sum: state1.sum + state2.sum\n }\n}" }, "finalize": { - "$code": "function (state) { return (state.sum \/ state.count) }" + "$code": "function(state) {\n return (state.sum \/ state.count)\n}" }, "lang": "js" } @@ -60,23 +60,23 @@ enum Pipelines: string "restaurants": { "$accumulator": { "init": { - "$code": "function (city, userProfileCity) { return { max: city === userProfileCity ? 3 : 1, restaurants: [] } }" + "$code": "function(city, userProfileCity) {\n return { max: city === userProfileCity ? 3 : 1, restaurants: [] }\n}" }, "initArgs": [ "$city", "Bettles" ], "accumulate": { - "$code": "function (state, restaurantName) { if (state.restaurants.length < state.max) { state.restaurants.push(restaurantName); } return state; }" + "$code": "function(state, restaurantName) {\n if (state.restaurants.length < state.max) {\n state.restaurants.push(restaurantName);\n }\n return state;\n}" }, "accumulateArgs": [ "$name" ], "merge": { - "$code": "function (state1, state2) { return { max: state1.max, restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) } }" + "$code": "function(state1, state2) {\n return {\n max: state1.max,\n restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max)\n }\n}" }, "finalize": { - "$code": "function (state) { return state.restaurants }" + "$code": "function(state) {\n return state.restaurants\n}" }, "lang": "js" } diff --git a/tests/Builder/Expression/FunctionOperatorTest.php b/tests/Builder/Expression/FunctionOperatorTest.php new file mode 100644 index 000000000..97ee7c3c7 --- /dev/null +++ b/tests/Builder/Expression/FunctionOperatorTest.php @@ -0,0 +1,71 @@ +assertSamePipeline(Pipelines::FunctionAlternativeToWhere, $pipeline); + } + + public function testUsageExample(): void + { + $pipeline = new Pipeline( + Stage::addFields( + isFound: Expression::function( + body: <<<'JS' + function(name) { + return hex_md5(name) == "15b0a220baa16331e8d80e15367677ad" + } + JS, + args: [ + Expression::stringFieldPath('name'), + ], + ), + message: Expression::function( + body: <<<'JS' + function(name, scores) { + let total = Array.sum(scores); + return `Hello ${name}. Your total score is ${total}.` + } + JS, + args: [ + Expression::stringFieldPath('name'), + Expression::stringFieldPath('scores'), + ], + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FunctionUsageExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 04690e9f1..f5c3ad3cb 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -752,6 +752,68 @@ enum Pipelines: string ] JSON; + /** + * Usage Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/#example-1--usage-example + */ + case FunctionUsageExample = <<<'JSON' + [ + { + "$addFields": { + "isFound": { + "$function": { + "body": { + "$code": "function(name) {\n return hex_md5(name) == \"15b0a220baa16331e8d80e15367677ad\"\n}" + }, + "args": [ + "$name" + ], + "lang": "js" + } + }, + "message": { + "$function": { + "body": { + "$code": "function(name, scores) {\n let total = Array.sum(scores);\n return `Hello ${name}. Your total score is ${total}.`\n}" + }, + "args": [ + "$name", + "$scores" + ], + "lang": "js" + } + } + } + } + ] + JSON; + + /** + * Alternative to $where + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/#example-2--alternative-to--where + */ + case FunctionAlternativeToWhere = <<<'JSON' + [ + { + "$match": { + "$expr": { + "$function": { + "body": { + "$code": "function(name) {\n return hex_md5(name) == \"15b0a220baa16331e8d80e15367677ad\";\n}" + }, + "args": [ + "$name" + ], + "lang": "js" + } + } + } + } + ] + JSON; + /** * Example * diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 727713f71..a8e97a5a1 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -1657,14 +1657,18 @@ enum Pipelines: string [ { "$match": { - "$where": "function() { return hex_md5(this.name) == \"9b53e667f30cd329dca1ec9e6a83e994\" }" + "$where": { + "$code": "function() {\n return hex_md5(this.name) == \"9b53e667f30cd329dca1ec9e6a83e994\"\n}" + } } }, { "$match": { "$expr": { "$function": { - "body": "function(name) {\n return hex_md5(name) == \"9b53e667f30cd329dca1ec9e6a83e994\";\n}", + "body": { + "$code": "function(name) {\n return hex_md5(name) == \"9b53e667f30cd329dca1ec9e6a83e994\";\n}" + }, "args": [ "$name" ], diff --git a/tests/Builder/Query/WhereOperatorTest.php b/tests/Builder/Query/WhereOperatorTest.php index f8a7b9c44..ed87faa4a 100644 --- a/tests/Builder/Query/WhereOperatorTest.php +++ b/tests/Builder/Query/WhereOperatorTest.php @@ -19,7 +19,11 @@ public function testExample(): void { $pipeline = new Pipeline( Stage::match( - Query::where('function() { return hex_md5(this.name) == "9b53e667f30cd329dca1ec9e6a83e994" }'), + Query::where(<<<'JS' + function() { + return hex_md5(this.name) == "9b53e667f30cd329dca1ec9e6a83e994" + } + JS), ), Stage::match( Query::expr( From 92ecc8f65edb6d52c819a1378f7d92d79217267d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 19 Jan 2024 09:09:45 +0100 Subject: [PATCH 34/95] PHPLIB-1352 Add tests on Object Expression Operators (#40) --- generator/config/expression/setField.yaml | 78 +++++++ generator/config/expression/unsetField.yaml | 36 +++ tests/Builder/Expression/Pipelines.php | 214 ++++++++++++++++++ .../Expression/SetFieldOperatorTest.php | 114 ++++++++++ .../Expression/UnsetFieldOperatorTest.php | 62 +++++ 5 files changed, 504 insertions(+) create mode 100644 tests/Builder/Expression/SetFieldOperatorTest.php create mode 100644 tests/Builder/Expression/UnsetFieldOperatorTest.php diff --git a/generator/config/expression/setField.yaml b/generator/config/expression/setField.yaml index 429e1222a..8ff3dbf7c 100644 --- a/generator/config/expression/setField.yaml +++ b/generator/config/expression/setField.yaml @@ -27,3 +27,81 @@ arguments: description: | The value that you want to assign to field. value can be any valid expression. Set to $$REMOVE to remove field from the input document. +tests: + - + name: 'Add Fields that Contain Periods' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#add-fields-that-contain-periods--.-' + pipeline: + - + $replaceWith: + $setField: + field: 'price.usd' + input: '$$ROOT' + value: '$price' + - + # $unset: 'price' + $unset: + - 'price' + - + name: 'Add Fields that Start with a Dollar Sign' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#add-fields-that-start-with-a-dollar-sign----' + pipeline: + - + $replaceWith: + $setField: + field: + $literal: '$price' + input: '$$ROOT' + value: '$price' + - + # $unset: 'price' + $unset: + - 'price' + - + name: 'Update Fields that Contain Periods' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#update-fields-that-contain-periods--.-' + pipeline: + - + $match: + _id: 1 + - + $replaceWith: + $setField: + field: 'price.usd' + input: '$$ROOT' + value: 49.99 + - + name: 'Update Fields that Start with a Dollar Sign' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#update-fields-that-start-with-a-dollar-sign----' + pipeline: + - + $match: + _id: 1 + - + $replaceWith: + $setField: + field: + $literal: '$price' + input: '$$ROOT' + value: 49.99 + - + name: 'Remove Fields that Contain Periods' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#remove-fields-that-contain-periods--.-' + pipeline: + - + $replaceWith: + $setField: + field: 'price.usd' + input: '$$ROOT' + value: '$$REMOVE' + - + name: 'Remove Fields that Start with a Dollar Sign' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#remove-fields-that-start-with-a-dollar-sign----' + pipeline: + - + $replaceWith: + $setField: + field: + $literal: '$price' + input: '$$ROOT' + value: '$$REMOVE' diff --git a/generator/config/expression/unsetField.yaml b/generator/config/expression/unsetField.yaml index 106b47cac..98d121687 100644 --- a/generator/config/expression/unsetField.yaml +++ b/generator/config/expression/unsetField.yaml @@ -20,3 +20,39 @@ arguments: - resolvesToObject description: | Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. +tests: + - + name: 'Remove Fields that Contain Periods' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/#remove-fields-that-contain-periods--.-' + pipeline: + - + $replaceWith: + $unsetField: + field: 'price.usd' + input: '$$ROOT' + - + name: 'Remove Fields that Start with a Dollar Sign' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/#remove-fields-that-start-with-a-dollar-sign----' + pipeline: + - + $replaceWith: + $unsetField: + field: + $literal: '$price' + input: '$$ROOT' + - + name: 'Remove A Subfield' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/#remove-a-subfield' + pipeline: + - + $replaceWith: + $setField: + field: 'price' + input: '$$ROOT' + value: + $unsetField: + field: 'euro' + input: + # $getField: 'price' + $getField: + field: 'price' diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index f5c3ad3cb..5713b9e58 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -1832,6 +1832,154 @@ enum Pipelines: string ] JSON; + /** + * Add Fields that Contain Periods + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#add-fields-that-contain-periods--.- + */ + case SetFieldAddFieldsThatContainPeriods = <<<'JSON' + [ + { + "$replaceWith": { + "$setField": { + "field": "price.usd", + "input": "$$ROOT", + "value": "$price" + } + } + }, + { + "$unset": [ + "price" + ] + } + ] + JSON; + + /** + * Add Fields that Start with a Dollar Sign + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#add-fields-that-start-with-a-dollar-sign---- + */ + case SetFieldAddFieldsThatStartWithADollarSign = <<<'JSON' + [ + { + "$replaceWith": { + "$setField": { + "field": { + "$literal": "$price" + }, + "input": "$$ROOT", + "value": "$price" + } + } + }, + { + "$unset": [ + "price" + ] + } + ] + JSON; + + /** + * Update Fields that Contain Periods + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#update-fields-that-contain-periods--.- + */ + case SetFieldUpdateFieldsThatContainPeriods = <<<'JSON' + [ + { + "$match": { + "_id": { + "$numberInt": "1" + } + } + }, + { + "$replaceWith": { + "$setField": { + "field": "price.usd", + "input": "$$ROOT", + "value": { + "$numberDouble": "49.99000000000000199" + } + } + } + } + ] + JSON; + + /** + * Update Fields that Start with a Dollar Sign + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#update-fields-that-start-with-a-dollar-sign---- + */ + case SetFieldUpdateFieldsThatStartWithADollarSign = <<<'JSON' + [ + { + "$match": { + "_id": { + "$numberInt": "1" + } + } + }, + { + "$replaceWith": { + "$setField": { + "field": { + "$literal": "$price" + }, + "input": "$$ROOT", + "value": { + "$numberDouble": "49.99000000000000199" + } + } + } + } + ] + JSON; + + /** + * Remove Fields that Contain Periods + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#remove-fields-that-contain-periods--.- + */ + case SetFieldRemoveFieldsThatContainPeriods = <<<'JSON' + [ + { + "$replaceWith": { + "$setField": { + "field": "price.usd", + "input": "$$ROOT", + "value": "$$REMOVE" + } + } + } + ] + JSON; + + /** + * Remove Fields that Start with a Dollar Sign + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#remove-fields-that-start-with-a-dollar-sign---- + */ + case SetFieldRemoveFieldsThatStartWithADollarSign = <<<'JSON' + [ + { + "$replaceWith": { + "$setField": { + "field": { + "$literal": "$price" + }, + "input": "$$ROOT", + "value": "$$REMOVE" + } + } + } + ] + JSON; + /** * Example * @@ -2287,6 +2435,72 @@ enum Pipelines: string ] JSON; + /** + * Remove Fields that Contain Periods + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/#remove-fields-that-contain-periods--.- + */ + case UnsetFieldRemoveFieldsThatContainPeriods = <<<'JSON' + [ + { + "$replaceWith": { + "$unsetField": { + "field": "price.usd", + "input": "$$ROOT" + } + } + } + ] + JSON; + + /** + * Remove Fields that Start with a Dollar Sign + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/#remove-fields-that-start-with-a-dollar-sign---- + */ + case UnsetFieldRemoveFieldsThatStartWithADollarSign = <<<'JSON' + [ + { + "$replaceWith": { + "$unsetField": { + "field": { + "$literal": "$price" + }, + "input": "$$ROOT" + } + } + } + ] + JSON; + + /** + * Remove A Subfield + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/#remove-a-subfield + */ + case UnsetFieldRemoveASubfield = <<<'JSON' + [ + { + "$replaceWith": { + "$setField": { + "field": "price", + "input": "$$ROOT", + "value": { + "$unsetField": { + "field": "euro", + "input": { + "$getField": { + "field": "price" + } + } + } + } + } + } + } + ] + JSON; + /** * Matrix Transposition * diff --git a/tests/Builder/Expression/SetFieldOperatorTest.php b/tests/Builder/Expression/SetFieldOperatorTest.php new file mode 100644 index 000000000..eb844334a --- /dev/null +++ b/tests/Builder/Expression/SetFieldOperatorTest.php @@ -0,0 +1,114 @@ +assertSamePipeline(Pipelines::SetFieldAddFieldsThatContainPeriods, $pipeline); + } + + public function testAddFieldsThatStartWithADollarSign(): void + { + $pipeline = new Pipeline( + Stage::replaceWith( + Expression::setField( + field: Expression::literal('$price'), + input: Expression::variable('ROOT'), + value: Expression::fieldPath('price'), + ), + ), + Stage::unset('price'), + ); + + $this->assertSamePipeline(Pipelines::SetFieldAddFieldsThatStartWithADollarSign, $pipeline); + } + + public function testRemoveFieldsThatContainPeriods(): void + { + $pipeline = new Pipeline( + Stage::replaceWith( + Expression::setField( + field: 'price.usd', + input: Expression::variable('ROOT'), + value: Expression::variable('REMOVE'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetFieldRemoveFieldsThatContainPeriods, $pipeline); + } + + public function testRemoveFieldsThatStartWithADollarSign(): void + { + $pipeline = new Pipeline( + Stage::replaceWith( + Expression::setField( + field: Expression::literal('$price'), + input: Expression::variable('ROOT'), + value: Expression::variable('REMOVE'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetFieldRemoveFieldsThatStartWithADollarSign, $pipeline); + } + + public function testUpdateFieldsThatContainPeriods(): void + { + $pipeline = new Pipeline( + Stage::match( + _id: 1, + ), + Stage::replaceWith( + Expression::setField( + field: 'price.usd', + input: Expression::variable('ROOT'), + value: 49.99, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetFieldUpdateFieldsThatContainPeriods, $pipeline); + } + + public function testUpdateFieldsThatStartWithADollarSign(): void + { + $pipeline = new Pipeline( + Stage::match( + _id: 1, + ), + Stage::replaceWith( + Expression::setField( + field: Expression::literal('$price'), + input: Expression::variable('ROOT'), + value: 49.99, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetFieldUpdateFieldsThatStartWithADollarSign, $pipeline); + } +} diff --git a/tests/Builder/Expression/UnsetFieldOperatorTest.php b/tests/Builder/Expression/UnsetFieldOperatorTest.php new file mode 100644 index 000000000..fba80d191 --- /dev/null +++ b/tests/Builder/Expression/UnsetFieldOperatorTest.php @@ -0,0 +1,62 @@ +assertSamePipeline(Pipelines::UnsetFieldRemoveASubfield, $pipeline); + } + + public function testRemoveFieldsThatContainPeriods(): void + { + $pipeline = new Pipeline( + Stage::replaceWith( + Expression::unsetField( + field: 'price.usd', + input: Expression::variable('ROOT'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::UnsetFieldRemoveFieldsThatContainPeriods, $pipeline); + } + + public function testRemoveFieldsThatStartWithADollarSign(): void + { + $pipeline = new Pipeline( + Stage::replaceWith( + Expression::unsetField( + field: Expression::literal('$price'), + input: Expression::variable('ROOT'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::UnsetFieldRemoveFieldsThatStartWithADollarSign, $pipeline); + } +} From 956f690f36a81545a33b7f1d02992917707d4fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 19 Jan 2024 09:11:08 +0100 Subject: [PATCH 35/95] PHPLIB-1338 Add tests on $size Array Query Operators (#36) --- generator/config/query/size.yaml | 9 ++++++++ tests/Builder/Query/Pipelines.php | 19 +++++++++++++++++ tests/Builder/Query/SizeOperatorTest.php | 27 ++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 tests/Builder/Query/SizeOperatorTest.php diff --git a/generator/config/query/size.yaml b/generator/config/query/size.yaml index 0ce05de15..629de4035 100644 --- a/generator/config/query/size.yaml +++ b/generator/config/query/size.yaml @@ -11,3 +11,12 @@ arguments: name: value type: - int +tests: + - + name: 'Query an Array by Array Length' + link: 'https://www.mongodb.com/docs/manual/tutorial/query-arrays/#query-an-array-by-array-length' + pipeline: + - + $match: + tags: + $size: 3 diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index a8e97a5a1..6bb65d977 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -1362,6 +1362,25 @@ enum Pipelines: string ] JSON; + /** + * Query an Array by Array Length + * + * @see https://www.mongodb.com/docs/manual/tutorial/query-arrays/#query-an-array-by-array-length + */ + case SizeQueryAnArrayByArrayLength = <<<'JSON' + [ + { + "$match": { + "tags": { + "$size": { + "$numberInt": "3" + } + } + } + } + ] + JSON; + /** * Search for a Single Word * diff --git a/tests/Builder/Query/SizeOperatorTest.php b/tests/Builder/Query/SizeOperatorTest.php new file mode 100644 index 000000000..ea612dbe7 --- /dev/null +++ b/tests/Builder/Query/SizeOperatorTest.php @@ -0,0 +1,27 @@ +assertSamePipeline(Pipelines::SizeQueryAnArrayByArrayLength, $pipeline); + } +} From 2603b9c3e611afd79009409d466d5506667d7e95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 19 Jan 2024 10:54:55 +0100 Subject: [PATCH 36/95] PHPLIB-1353 Add tests on Set Expression Operators (#41) --- .../config/expression/allElementsTrue.yaml | 13 +- .../config/expression/anyElementTrue.yaml | 12 + .../config/expression/setDifference.yaml | 14 ++ generator/config/expression/setEquals.yaml | 14 ++ .../config/expression/setIntersection.yaml | 30 +++ generator/config/expression/setIsSubset.yaml | 15 ++ generator/config/expression/setUnion.yaml | 14 ++ .../Expression/AllElementsTrueOperator.php | 18 +- src/Builder/Expression/FactoryTrait.php | 7 +- .../AllElementsTrueOperatorTest.php | 31 +++ .../Expression/AnyElementTrueOperatorTest.php | 31 +++ tests/Builder/Expression/Pipelines.php | 224 ++++++++++++++++++ .../Expression/SetDifferenceOperatorTest.php | 33 +++ .../Expression/SetEqualsOperatorTest.php | 33 +++ .../SetIntersectionOperatorTest.php | 55 +++++ .../Expression/SetIsSubsetOperatorTest.php | 33 +++ .../Expression/SetUnionOperatorTest.php | 33 +++ 17 files changed, 594 insertions(+), 16 deletions(-) create mode 100644 tests/Builder/Expression/AllElementsTrueOperatorTest.php create mode 100644 tests/Builder/Expression/AnyElementTrueOperatorTest.php create mode 100644 tests/Builder/Expression/SetDifferenceOperatorTest.php create mode 100644 tests/Builder/Expression/SetEqualsOperatorTest.php create mode 100644 tests/Builder/Expression/SetIntersectionOperatorTest.php create mode 100644 tests/Builder/Expression/SetIsSubsetOperatorTest.php create mode 100644 tests/Builder/Expression/SetUnionOperatorTest.php diff --git a/generator/config/expression/allElementsTrue.yaml b/generator/config/expression/allElementsTrue.yaml index 1a9751a00..7301f8d68 100644 --- a/generator/config/expression/allElementsTrue.yaml +++ b/generator/config/expression/allElementsTrue.yaml @@ -11,4 +11,15 @@ arguments: name: expression type: - resolvesToArray - variadic: array +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/allElementsTrue/#example' + pipeline: + - + $project: + responses: 1 + isAllTrue: + $allElementsTrue: + - '$responses' + _id: 0 diff --git a/generator/config/expression/anyElementTrue.yaml b/generator/config/expression/anyElementTrue.yaml index 14c96506d..50fe665b6 100644 --- a/generator/config/expression/anyElementTrue.yaml +++ b/generator/config/expression/anyElementTrue.yaml @@ -11,3 +11,15 @@ arguments: name: expression type: - resolvesToArray +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/anyElementTrue/#example' + pipeline: + - + $project: + responses: 1 + isAnyTrue: + $anyElementTrue: + - '$responses' + _id: 0 diff --git a/generator/config/expression/setDifference.yaml b/generator/config/expression/setDifference.yaml index fac46be28..69f448cb7 100644 --- a/generator/config/expression/setDifference.yaml +++ b/generator/config/expression/setDifference.yaml @@ -19,3 +19,17 @@ arguments: - resolvesToArray description: | The arguments can be any valid expression as long as they each resolve to an array. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setDifference/#example' + pipeline: + - + $project: + flowerFieldA: 1 + flowerFieldB: 1 + inBOnly: + $setDifference: + - '$flowerFieldB' + - '$flowerFieldA' + _id: 0 diff --git a/generator/config/expression/setEquals.yaml b/generator/config/expression/setEquals.yaml index 4bde55dd0..5194c9ae9 100644 --- a/generator/config/expression/setEquals.yaml +++ b/generator/config/expression/setEquals.yaml @@ -12,3 +12,17 @@ arguments: type: - resolvesToArray variadic: array +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setEquals/#example' + pipeline: + - + $project: + _id: 0 + cakes: 1 + cupcakes: 1 + sameFlavors: + $setEquals: + - '$cakes' + - '$cupcakes' diff --git a/generator/config/expression/setIntersection.yaml b/generator/config/expression/setIntersection.yaml index 5c6798968..8f03651d5 100644 --- a/generator/config/expression/setIntersection.yaml +++ b/generator/config/expression/setIntersection.yaml @@ -12,3 +12,33 @@ arguments: type: - resolvesToArray variadic: array +tests: + - + name: 'Elements Array Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/#elements-array-example' + pipeline: + - + $project: + flowerFieldA: 1 + flowerFieldB: 1 + commonToBoth: + $setIntersection: + - '$flowerFieldA' + - '$flowerFieldB' + _id: 0 + - + name: 'Retrieve Documents for Roles Granted to the Current User' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/#retrieve-documents-for-roles-granted-to-the-current-user' + pipeline: + - + $match: + $expr: + $not: + # the example doesn't use an array inside $not, but the documentation say it is necessary + - + $eq: + - + $setIntersection: + - '$allowedRoles' + - '$$USER_ROLES.role' + - [] diff --git a/generator/config/expression/setIsSubset.yaml b/generator/config/expression/setIsSubset.yaml index ba670b941..fe7c9ed02 100644 --- a/generator/config/expression/setIsSubset.yaml +++ b/generator/config/expression/setIsSubset.yaml @@ -15,3 +15,18 @@ arguments: name: expression2 type: - resolvesToArray +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIsSubset/#example' + pipeline: + - + $project: + flowerFieldA: 1 + flowerFieldB: 1 + AisSubset: + $setIsSubset: + - '$flowerFieldA' + - '$flowerFieldB' + _id: 0 + diff --git a/generator/config/expression/setUnion.yaml b/generator/config/expression/setUnion.yaml index f9b7c9ca9..2cfca3e16 100644 --- a/generator/config/expression/setUnion.yaml +++ b/generator/config/expression/setUnion.yaml @@ -12,3 +12,17 @@ arguments: type: - resolvesToArray variadic: array +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/#example' + pipeline: + - + $project: + flowerFieldA: 1 + flowerFieldB: 1 + allValues: + $setUnion: + - '$flowerFieldA' + - '$flowerFieldB' + _id: 0 diff --git a/src/Builder/Expression/AllElementsTrueOperator.php b/src/Builder/Expression/AllElementsTrueOperator.php index 5724d54d5..dbfebe75c 100644 --- a/src/Builder/Expression/AllElementsTrueOperator.php +++ b/src/Builder/Expression/AllElementsTrueOperator.php @@ -15,6 +15,7 @@ use MongoDB\Model\BSONArray; use function array_is_list; +use function is_array; /** * Returns true if no element of a set evaluates to false, otherwise, returns false. Accepts a single argument expression. @@ -25,21 +26,16 @@ class AllElementsTrueOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var list $expression */ - public readonly array $expression; + /** @var BSONArray|PackedArray|ResolvesToArray|array $expression */ + public readonly PackedArray|ResolvesToArray|BSONArray|array $expression; /** - * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression - * @no-named-arguments + * @param BSONArray|PackedArray|ResolvesToArray|array $expression */ - public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expression) + public function __construct(PackedArray|ResolvesToArray|BSONArray|array $expression) { - if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); - } - - if (! array_is_list($expression)) { - throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); + if (is_array($expression) && ! array_is_list($expression)) { + throw new InvalidArgumentException('Expected $expression argument to be a list, got an associative array.'); } $this->expression = $expression; diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 1389322f0..aafcede5f 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -85,14 +85,13 @@ public static function add( * Returns true if no element of a set evaluates to false, otherwise, returns false. Accepts a single argument expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/allElementsTrue/ - * @no-named-arguments - * @param BSONArray|PackedArray|ResolvesToArray|array ...$expression + * @param BSONArray|PackedArray|ResolvesToArray|array $expression */ public static function allElementsTrue( - PackedArray|ResolvesToArray|BSONArray|array ...$expression, + PackedArray|ResolvesToArray|BSONArray|array $expression, ): AllElementsTrueOperator { - return new AllElementsTrueOperator(...$expression); + return new AllElementsTrueOperator($expression); } /** diff --git a/tests/Builder/Expression/AllElementsTrueOperatorTest.php b/tests/Builder/Expression/AllElementsTrueOperatorTest.php new file mode 100644 index 000000000..b3591dc3f --- /dev/null +++ b/tests/Builder/Expression/AllElementsTrueOperatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::AllElementsTrueExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/AnyElementTrueOperatorTest.php b/tests/Builder/Expression/AnyElementTrueOperatorTest.php new file mode 100644 index 000000000..a99fd1654 --- /dev/null +++ b/tests/Builder/Expression/AnyElementTrueOperatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::AnyElementTrueExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 5713b9e58..abecf4490 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -58,6 +58,31 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/allElementsTrue/#example + */ + case AllElementsTrueExample = <<<'JSON' + [ + { + "$project": { + "responses": { + "$numberInt": "1" + }, + "isAllTrue": { + "$allElementsTrue": [ + "$responses" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + /** * Example * @@ -98,6 +123,31 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/anyElementTrue/#example + */ + case AnyElementTrueExample = <<<'JSON' + [ + { + "$project": { + "responses": { + "$numberInt": "1" + }, + "isAnyTrue": { + "$anyElementTrue": [ + "$responses" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + /** * Example * @@ -1980,6 +2030,180 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setDifference/#example + */ + case SetDifferenceExample = <<<'JSON' + [ + { + "$project": { + "flowerFieldA": { + "$numberInt": "1" + }, + "flowerFieldB": { + "$numberInt": "1" + }, + "inBOnly": { + "$setDifference": [ + "$flowerFieldB", + "$flowerFieldA" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setEquals/#example + */ + case SetEqualsExample = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "cakes": { + "$numberInt": "1" + }, + "cupcakes": { + "$numberInt": "1" + }, + "sameFlavors": { + "$setEquals": [ + "$cakes", + "$cupcakes" + ] + } + } + } + ] + JSON; + + /** + * Elements Array Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/#elements-array-example + */ + case SetIntersectionElementsArrayExample = <<<'JSON' + [ + { + "$project": { + "flowerFieldA": { + "$numberInt": "1" + }, + "flowerFieldB": { + "$numberInt": "1" + }, + "commonToBoth": { + "$setIntersection": [ + "$flowerFieldA", + "$flowerFieldB" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Retrieve Documents for Roles Granted to the Current User + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/#retrieve-documents-for-roles-granted-to-the-current-user + */ + case SetIntersectionRetrieveDocumentsForRolesGrantedToTheCurrentUser = <<<'JSON' + [ + { + "$match": { + "$expr": { + "$not": [ + { + "$eq": [ + { + "$setIntersection": [ + "$allowedRoles", + "$$USER_ROLES.role" + ] + }, + [] + ] + } + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIsSubset/#example + */ + case SetIsSubsetExample = <<<'JSON' + [ + { + "$project": { + "flowerFieldA": { + "$numberInt": "1" + }, + "flowerFieldB": { + "$numberInt": "1" + }, + "AisSubset": { + "$setIsSubset": [ + "$flowerFieldA", + "$flowerFieldB" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/#example + */ + case SetUnionExample = <<<'JSON' + [ + { + "$project": { + "flowerFieldA": { + "$numberInt": "1" + }, + "flowerFieldB": { + "$numberInt": "1" + }, + "allValues": { + "$setUnion": [ + "$flowerFieldA", + "$flowerFieldB" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + /** * Example * diff --git a/tests/Builder/Expression/SetDifferenceOperatorTest.php b/tests/Builder/Expression/SetDifferenceOperatorTest.php new file mode 100644 index 000000000..1a1fafcc4 --- /dev/null +++ b/tests/Builder/Expression/SetDifferenceOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::SetDifferenceExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SetEqualsOperatorTest.php b/tests/Builder/Expression/SetEqualsOperatorTest.php new file mode 100644 index 000000000..e3974dad7 --- /dev/null +++ b/tests/Builder/Expression/SetEqualsOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::SetEqualsExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SetIntersectionOperatorTest.php b/tests/Builder/Expression/SetIntersectionOperatorTest.php new file mode 100644 index 000000000..7bce31d1c --- /dev/null +++ b/tests/Builder/Expression/SetIntersectionOperatorTest.php @@ -0,0 +1,55 @@ +assertSamePipeline(Pipelines::SetIntersectionElementsArrayExample, $pipeline); + } + + public function testRetrieveDocumentsForRolesGrantedToTheCurrentUser(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::expr( + Expression::not( + Expression::eq( + Expression::setIntersection( + Expression::arrayFieldPath('allowedRoles'), + Expression::variable('USER_ROLES.role'), + ), + [], + ), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetIntersectionRetrieveDocumentsForRolesGrantedToTheCurrentUser, $pipeline); + } +} diff --git a/tests/Builder/Expression/SetIsSubsetOperatorTest.php b/tests/Builder/Expression/SetIsSubsetOperatorTest.php new file mode 100644 index 000000000..b54e6bde6 --- /dev/null +++ b/tests/Builder/Expression/SetIsSubsetOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::SetIsSubsetExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SetUnionOperatorTest.php b/tests/Builder/Expression/SetUnionOperatorTest.php new file mode 100644 index 000000000..56bf8694c --- /dev/null +++ b/tests/Builder/Expression/SetUnionOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::SetUnionExample, $pipeline); + } +} From 8b445be9ad76397718632724d2b726b3f9ff0ec8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 19 Jan 2024 10:58:03 +0100 Subject: [PATCH 37/95] PHPLIB-1351 Add tests on Miscellaneous Operators (#39) --- generator/config/expression/getField.yaml | 46 ++- generator/config/expression/rand.yaml | 39 +++ .../config/expression/toHashedIndexKey.yaml | 28 ++ .../{expression => query}/sampleRate.yaml | 12 +- src/Builder/Expression/FactoryTrait.php | 29 +- src/Builder/Expression/GetFieldOperator.php | 8 +- .../Expression/ToHashedIndexKeyOperator.php | 41 +++ src/Builder/Query/FactoryTrait.php | 13 + .../SampleRateOperator.php | 6 +- .../Expression/GetFieldOperatorTest.php | 70 ++++ tests/Builder/Expression/Pipelines.php | 303 ++++++++++++++---- tests/Builder/Expression/RandOperatorTest.php | 61 ++++ .../ToHashedIndexKeyOperatorTest.php | 34 ++ tests/Builder/Query/Pipelines.php | 20 ++ .../Builder/Query/SampleRateOperatorTest.php | 28 ++ 15 files changed, 658 insertions(+), 80 deletions(-) create mode 100644 generator/config/expression/toHashedIndexKey.yaml rename generator/config/{expression => query}/sampleRate.yaml (73%) create mode 100644 src/Builder/Expression/ToHashedIndexKeyOperator.php rename src/Builder/{Expression => Query}/SampleRateOperator.php (89%) create mode 100644 tests/Builder/Expression/GetFieldOperatorTest.php create mode 100644 tests/Builder/Expression/RandOperatorTest.php create mode 100644 tests/Builder/Expression/ToHashedIndexKeyOperatorTest.php create mode 100644 tests/Builder/Query/SampleRateOperatorTest.php diff --git a/generator/config/expression/getField.yaml b/generator/config/expression/getField.yaml index ae81b1b57..2a032478e 100644 --- a/generator/config/expression/getField.yaml +++ b/generator/config/expression/getField.yaml @@ -11,7 +11,7 @@ arguments: - name: field type: - - string + - resolvesToString description: | Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. If field begins with a dollar sign ($), place the field name inside of a $literal expression to return its value. @@ -23,3 +23,47 @@ arguments: description: | Default: $$CURRENT A valid expression that contains the field for which you want to return a value. input must resolve to an object, missing, null, or undefined. If omitted, defaults to the document currently being processed in the pipeline ($$CURRENT). +tests: + - + name: 'Query Fields that Contain Periods' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/#query-fields-that-contain-periods--.-' + pipeline: + - + $match: + $expr: + $gt: + - + # the builder uses the verbose form with parameter names + # $getField: 'price.usd' + $getField: + field: 'price.usd' + - 200 + - + name: 'Query Fields that Start with a Dollar Sign' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/#query-fields-that-start-with-a-dollar-sign----' + pipeline: + - + $match: + $expr: + $gt: + - + $getField: + # the builder uses the verbose form with parameter names + # $literal: '$price' + field: + $literal: '$price' + - 200 + - + name: 'Query a Field in a Sub-document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/#query-a-field-in-a-sub-document' + pipeline: + - + $match: + $expr: + $lte: + - + $getField: + field: + $literal: '$small' + input: '$quantity' + - 20 diff --git a/generator/config/expression/rand.yaml b/generator/config/expression/rand.yaml index f0e731dc7..b1a9d8157 100644 --- a/generator/config/expression/rand.yaml +++ b/generator/config/expression/rand.yaml @@ -6,3 +6,42 @@ type: encode: object description: | Returns a random float between 0 and 1 +tests: + - + name: 'Generate Random Data Points' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/rand/#generate-random-data-points' + pipeline: + - + $set: + amount: + $multiply: + - + $rand: {} + - 100 + - + $set: + amount: + $floor: '$amount' + - + # $merge: 'donors' + $merge: + into: 'donors' + - + name: 'Select Random Items From a Collection' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/rand/#select-random-items-from-a-collection' + pipeline: + - + $match: + district: 3 + - + $match: + $expr: + $lt: + - 0.5 + - + $rand: {} + - + $project: + _id: 0 + name: 1 + registered: 1 diff --git a/generator/config/expression/toHashedIndexKey.yaml b/generator/config/expression/toHashedIndexKey.yaml new file mode 100644 index 000000000..f5811f56d --- /dev/null +++ b/generator/config/expression/toHashedIndexKey.yaml @@ -0,0 +1,28 @@ +# $schema: ../schema.json +name: $toHashedIndexKey +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toHashedIndexKey/' +type: + - resolvesToLong +encode: single +description: | + Computes and returns the hash value of the input expression using the same hash function that MongoDB uses to create a hashed index. A hash function maps a key or string to a fixed-size numeric value. +arguments: + - + name: value + type: + - expression + description: | + key or string to hash +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toHashedIndexKey/#example' + pipeline: + - + $documents: + - + val: 'string to hash' + - + $addFields: + hashedVal: + $toHashedIndexKey: '$val' diff --git a/generator/config/expression/sampleRate.yaml b/generator/config/query/sampleRate.yaml similarity index 73% rename from generator/config/expression/sampleRate.yaml rename to generator/config/query/sampleRate.yaml index 18a0e5453..9995e2d8b 100644 --- a/generator/config/expression/sampleRate.yaml +++ b/generator/config/query/sampleRate.yaml @@ -2,7 +2,7 @@ name: $sampleRate link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sampleRate/' type: - - resolvesToAny + - query encode: single description: | Randomly select documents at a given rate. Although the exact number of documents selected varies on each run, the quantity chosen approximates the sample rate expressed as a percentage of the total number of documents. @@ -14,3 +14,13 @@ arguments: description: | The selection process uses a uniform random distribution. The sample rate is a floating point number between 0 and 1, inclusive, which represents the probability that a given document will be selected as it passes through the pipeline. For example, a sample rate of 0.33 selects roughly one document in three. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sampleRate/#examples' + pipeline: + - + $match: + $sampleRate: 0.33 + - + $count: 'numMatches' diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index aafcede5f..04d046542 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -778,13 +778,13 @@ public static function function( * New in MongoDB 5.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/ - * @param non-empty-string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. + * @param ResolvesToString|non-empty-string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. * If field begins with a dollar sign ($), place the field name inside of a $literal expression to return its value. * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input Default: $$CURRENT * A valid expression that contains the field for which you want to return a value. input must resolve to an object, missing, null, or undefined. If omitted, defaults to the document currently being processed in the pipeline ($$CURRENT). */ public static function getField( - string $field, + ResolvesToString|string $field, Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $input = Optional::Undefined, ): GetFieldOperator { @@ -1692,18 +1692,6 @@ public static function rtrim( return new RtrimOperator($input, $chars); } - /** - * Randomly select documents at a given rate. Although the exact number of documents selected varies on each run, the quantity chosen approximates the sample rate expressed as a percentage of the total number of documents. - * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sampleRate/ - * @param Int64|ResolvesToDouble|float|int $rate The selection process uses a uniform random distribution. The sample rate is a floating point number between 0 and 1, inclusive, which represents the probability that a given document will be selected as it passes through the pipeline. - * For example, a sample rate of 0.33 selects roughly one document in three. - */ - public static function sampleRate(Int64|ResolvesToDouble|float|int $rate): SampleRateOperator - { - return new SampleRateOperator($rate); - } - /** * Returns the seconds for a date as a number between 0 and 60 (leap seconds). * @@ -2144,6 +2132,19 @@ public static function toDouble( return new ToDoubleOperator($expression); } + /** + * Computes and returns the hash value of the input expression using the same hash function that MongoDB uses to create a hashed index. A hash function maps a key or string to a fixed-size numeric value. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toHashedIndexKey/ + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $value key or string to hash + */ + public static function toHashedIndexKey( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $value, + ): ToHashedIndexKeyOperator + { + return new ToHashedIndexKeyOperator($value); + } + /** * Converts value to an integer. * New in MongoDB 4.0. diff --git a/src/Builder/Expression/GetFieldOperator.php b/src/Builder/Expression/GetFieldOperator.php index 52eeae160..c1b14933e 100644 --- a/src/Builder/Expression/GetFieldOperator.php +++ b/src/Builder/Expression/GetFieldOperator.php @@ -26,10 +26,10 @@ class GetFieldOperator implements ResolvesToAny, OperatorInterface public const ENCODE = Encode::Object; /** - * @var non-empty-string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. + * @var ResolvesToString|non-empty-string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. * If field begins with a dollar sign ($), place the field name inside of a $literal expression to return its value. */ - public readonly string $field; + public readonly ResolvesToString|string $field; /** * @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input Default: $$CURRENT @@ -38,13 +38,13 @@ class GetFieldOperator implements ResolvesToAny, OperatorInterface public readonly Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $input; /** - * @param non-empty-string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. + * @param ResolvesToString|non-empty-string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. * If field begins with a dollar sign ($), place the field name inside of a $literal expression to return its value. * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input Default: $$CURRENT * A valid expression that contains the field for which you want to return a value. input must resolve to an object, missing, null, or undefined. If omitted, defaults to the document currently being processed in the pipeline ($$CURRENT). */ public function __construct( - string $field, + ResolvesToString|string $field, Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $input = Optional::Undefined, ) { $this->field = $field; diff --git a/src/Builder/Expression/ToHashedIndexKeyOperator.php b/src/Builder/Expression/ToHashedIndexKeyOperator.php new file mode 100644 index 000000000..1bc3a5ec7 --- /dev/null +++ b/src/Builder/Expression/ToHashedIndexKeyOperator.php @@ -0,0 +1,41 @@ +value = $value; + } + + public function getOperator(): string + { + return '$toHashedIndexKey'; + } +} diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index c12895e31..6f01589c9 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -17,6 +17,7 @@ use MongoDB\BSON\Regex; use MongoDB\BSON\Serializable; use MongoDB\BSON\Type; +use MongoDB\Builder\Expression\ResolvesToDouble; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\GeometryInterface; @@ -473,6 +474,18 @@ public static function regex(Regex $regex): RegexOperator return new RegexOperator($regex); } + /** + * Randomly select documents at a given rate. Although the exact number of documents selected varies on each run, the quantity chosen approximates the sample rate expressed as a percentage of the total number of documents. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sampleRate/ + * @param Int64|ResolvesToDouble|float|int $rate The selection process uses a uniform random distribution. The sample rate is a floating point number between 0 and 1, inclusive, which represents the probability that a given document will be selected as it passes through the pipeline. + * For example, a sample rate of 0.33 selects roughly one document in three. + */ + public static function sampleRate(Int64|ResolvesToDouble|float|int $rate): SampleRateOperator + { + return new SampleRateOperator($rate); + } + /** * Selects documents if the array field is a specified size. * diff --git a/src/Builder/Expression/SampleRateOperator.php b/src/Builder/Query/SampleRateOperator.php similarity index 89% rename from src/Builder/Expression/SampleRateOperator.php rename to src/Builder/Query/SampleRateOperator.php index 37ca3a0a3..a21a1ba5a 100644 --- a/src/Builder/Expression/SampleRateOperator.php +++ b/src/Builder/Query/SampleRateOperator.php @@ -6,18 +6,20 @@ declare(strict_types=1); -namespace MongoDB\Builder\Expression; +namespace MongoDB\Builder\Query; use MongoDB\BSON\Int64; +use MongoDB\Builder\Expression\ResolvesToDouble; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\QueryInterface; /** * Randomly select documents at a given rate. Although the exact number of documents selected varies on each run, the quantity chosen approximates the sample rate expressed as a percentage of the total number of documents. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sampleRate/ */ -class SampleRateOperator implements ResolvesToAny, OperatorInterface +class SampleRateOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; diff --git a/tests/Builder/Expression/GetFieldOperatorTest.php b/tests/Builder/Expression/GetFieldOperatorTest.php new file mode 100644 index 000000000..117149ddf --- /dev/null +++ b/tests/Builder/Expression/GetFieldOperatorTest.php @@ -0,0 +1,70 @@ +assertSamePipeline(Pipelines::GetFieldQueryAFieldInASubdocument, $pipeline); + } + + public function testQueryFieldsThatContainPeriods(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::expr( + Expression::gt( + Expression::getField('price.usd'), + 200, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::GetFieldQueryFieldsThatContainPeriods, $pipeline); + } + + public function testQueryFieldsThatStartWithADollarSign(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::expr( + Expression::gt( + Expression::getField( + Expression::literal('$price'), + ), + 200, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::GetFieldQueryFieldsThatStartWithADollarSign, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index abecf4490..8c82d4a5f 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -864,6 +864,89 @@ enum Pipelines: string ] JSON; + /** + * Query Fields that Contain Periods + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/#query-fields-that-contain-periods--.- + */ + case GetFieldQueryFieldsThatContainPeriods = <<<'JSON' + [ + { + "$match": { + "$expr": { + "$gt": [ + { + "$getField": { + "field": "price.usd" + } + }, + { + "$numberInt": "200" + } + ] + } + } + } + ] + JSON; + + /** + * Query Fields that Start with a Dollar Sign + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/#query-fields-that-start-with-a-dollar-sign---- + */ + case GetFieldQueryFieldsThatStartWithADollarSign = <<<'JSON' + [ + { + "$match": { + "$expr": { + "$gt": [ + { + "$getField": { + "field": { + "$literal": "$price" + } + } + }, + { + "$numberInt": "200" + } + ] + } + } + } + ] + JSON; + + /** + * Query a Field in a Sub-document + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/#query-a-field-in-a-sub-document + */ + case GetFieldQueryAFieldInASubdocument = <<<'JSON' + [ + { + "$match": { + "$expr": { + "$lte": [ + { + "$getField": { + "field": { + "$literal": "$small" + }, + "input": "$quantity" + } + }, + { + "$numberInt": "20" + } + ] + } + } + } + ] + JSON; + /** * Example * @@ -1644,6 +1727,86 @@ enum Pipelines: string ] JSON; + /** + * Generate Random Data Points + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rand/#generate-random-data-points + */ + case RandGenerateRandomDataPoints = <<<'JSON' + [ + { + "$set": { + "amount": { + "$multiply": [ + { + "$rand": {} + }, + { + "$numberInt": "100" + } + ] + } + } + }, + { + "$set": { + "amount": { + "$floor": "$amount" + } + } + }, + { + "$merge": { + "into": "donors" + } + } + ] + JSON; + + /** + * Select Random Items From a Collection + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rand/#select-random-items-from-a-collection + */ + case RandSelectRandomItemsFromACollection = <<<'JSON' + [ + { + "$match": { + "district": { + "$numberInt": "3" + } + } + }, + { + "$match": { + "$expr": { + "$lt": [ + { + "$numberDouble": "0.5" + }, + { + "$rand": {} + } + ] + } + } + }, + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "name": { + "$numberInt": "1" + }, + "registered": { + "$numberInt": "1" + } + } + } + ] + JSON; + /** * Example * @@ -1882,6 +2045,64 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setDifference/#example + */ + case SetDifferenceExample = <<<'JSON' + [ + { + "$project": { + "flowerFieldA": { + "$numberInt": "1" + }, + "flowerFieldB": { + "$numberInt": "1" + }, + "inBOnly": { + "$setDifference": [ + "$flowerFieldB", + "$flowerFieldA" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setEquals/#example + */ + case SetEqualsExample = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "cakes": { + "$numberInt": "1" + }, + "cupcakes": { + "$numberInt": "1" + }, + "sameFlavors": { + "$setEquals": [ + "$cakes", + "$cupcakes" + ] + } + } + } + ] + JSON; + /** * Add Fields that Contain Periods * @@ -2030,64 +2251,6 @@ enum Pipelines: string ] JSON; - /** - * Example - * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setDifference/#example - */ - case SetDifferenceExample = <<<'JSON' - [ - { - "$project": { - "flowerFieldA": { - "$numberInt": "1" - }, - "flowerFieldB": { - "$numberInt": "1" - }, - "inBOnly": { - "$setDifference": [ - "$flowerFieldB", - "$flowerFieldA" - ] - }, - "_id": { - "$numberInt": "0" - } - } - } - ] - JSON; - - /** - * Example - * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setEquals/#example - */ - case SetEqualsExample = <<<'JSON' - [ - { - "$project": { - "_id": { - "$numberInt": "0" - }, - "cakes": { - "$numberInt": "1" - }, - "cupcakes": { - "$numberInt": "1" - }, - "sameFlavors": { - "$setEquals": [ - "$cakes", - "$cupcakes" - ] - } - } - } - ] - JSON; - /** * Elements Array Example * @@ -2659,6 +2822,30 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toHashedIndexKey/#example + */ + case ToHashedIndexKeyExample = <<<'JSON' + [ + { + "$documents": [ + { + "val": "string to hash" + } + ] + }, + { + "$addFields": { + "hashedVal": { + "$toHashedIndexKey": "$val" + } + } + } + ] + JSON; + /** * Remove Fields that Contain Periods * diff --git a/tests/Builder/Expression/RandOperatorTest.php b/tests/Builder/Expression/RandOperatorTest.php new file mode 100644 index 000000000..b4964df69 --- /dev/null +++ b/tests/Builder/Expression/RandOperatorTest.php @@ -0,0 +1,61 @@ +assertSamePipeline(Pipelines::RandGenerateRandomDataPoints, $pipeline); + } + + public function testSelectRandomItemsFromACollection(): void + { + $pipeline = new Pipeline( + Stage::match( + district: 3, + ), + Stage::match( + Query::expr( + Expression::lt( + 0.5, + Expression::rand(), + ), + ), + ), + Stage::project( + _id: 0, + name: 1, + registered: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::RandSelectRandomItemsFromACollection, $pipeline); + } +} diff --git a/tests/Builder/Expression/ToHashedIndexKeyOperatorTest.php b/tests/Builder/Expression/ToHashedIndexKeyOperatorTest.php new file mode 100644 index 000000000..8bb4bb852 --- /dev/null +++ b/tests/Builder/Expression/ToHashedIndexKeyOperatorTest.php @@ -0,0 +1,34 @@ +assertSamePipeline(Pipelines::ToHashedIndexKeyExample, $pipeline); + } +} diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 6bb65d977..43dd4f43a 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -1362,6 +1362,26 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sampleRate/#examples + */ + case SampleRateExample = <<<'JSON' + [ + { + "$match": { + "$sampleRate": { + "$numberDouble": "0.33000000000000001554" + } + } + }, + { + "$count": "numMatches" + } + ] + JSON; + /** * Query an Array by Array Length * diff --git a/tests/Builder/Query/SampleRateOperatorTest.php b/tests/Builder/Query/SampleRateOperatorTest.php new file mode 100644 index 000000000..95abec400 --- /dev/null +++ b/tests/Builder/Query/SampleRateOperatorTest.php @@ -0,0 +1,28 @@ +assertSamePipeline(Pipelines::SampleRateExample, $pipeline); + } +} From 149dca684be706dfbbdb84f77f4d15de4906dcb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 19 Jan 2024 14:47:00 +0100 Subject: [PATCH 38/95] PHPLIB-1350 Add tests on Date Expression Operators and add Yaml tags for BSON types (#34) * Remove unused code and unescape backslashes in JSON * Switched to Yaml tags to represent specific BSON objects --- generator/README.md | 13 + generator/config/expression/dateAdd.yaml | 97 ++ generator/config/expression/dateDiff.yaml | 69 ++ .../config/expression/dateFromParts.yaml | 30 + .../config/expression/dateFromString.yaml | 35 + generator/config/expression/dateSubtract.yaml | 102 ++ generator/config/expression/dateToParts.yaml | 18 + generator/config/expression/dateToString.yaml | 41 + generator/config/expression/dateTrunc.yaml | 30 + generator/config/expression/dayOfMonth.yaml | 11 + generator/config/expression/dayOfWeek.yaml | 11 + generator/config/expression/dayOfYear.yaml | 11 + generator/config/expression/hour.yaml | 11 + generator/config/expression/isoDayOfWeek.yaml | 13 + generator/config/expression/isoWeek.yaml | 13 + generator/config/expression/isoWeekYear.yaml | 11 + generator/config/expression/millisecond.yaml | 11 + generator/config/expression/minute.yaml | 11 + generator/config/expression/month.yaml | 11 + generator/config/expression/second.yaml | 11 + generator/config/expression/toDate.yaml | 12 + generator/config/expression/week.yaml | 11 + generator/config/expression/year.yaml | 11 + generator/config/query/eq.yaml | 8 +- generator/config/query/in.yaml | 10 +- generator/config/query/not.yaml | 5 +- generator/config/query/regex.yaml | 8 +- generator/config/stage/addFields.yaml | 6 +- generator/js2yaml.html | 32 + generator/src/Definition/YamlReader.php | 5 +- generator/src/OperatorTestGenerator.php | 54 +- .../Expression/DateFromPartsOperator.php | 16 +- src/Builder/Expression/FactoryTrait.php | 8 +- tests/Builder/Accumulator/Pipelines.php | 2 +- .../Expression/DateAddOperatorTest.php | 124 ++ .../Expression/DateDiffOperatorTest.php | 98 ++ .../Expression/DateFromPartsOperatorTest.php | 47 + .../Expression/DateFromStringOperatorTest.php | 61 + .../Expression/DateSubtractOperatorTest.php | 130 ++ .../Expression/DateToPartsOperatorTest.php | 37 + .../Expression/DateToStringOperatorTest.php | 60 + .../Expression/DateTruncOperatorTest.php | 58 + .../Expression/DayOfMonthOperatorTest.php | 29 + .../Expression/DayOfWeekOperatorTest.php | 29 + .../Expression/DayOfYearOperatorTest.php | 29 + tests/Builder/Expression/HourOperatorTest.php | 29 + .../Expression/IsoDayOfWeekOperatorTest.php | 31 + .../Expression/IsoWeekOperatorTest.php | 31 + .../Expression/IsoWeekYearOperatorTest.php | 29 + .../Expression/MillisecondOperatorTest.php | 29 + .../Builder/Expression/MinuteOperatorTest.php | 29 + .../Builder/Expression/MonthOperatorTest.php | 29 + tests/Builder/Expression/Pipelines.php | 1041 +++++++++++++++++ .../Builder/Expression/SecondOperatorTest.php | 29 + .../Builder/Expression/ToDateOperatorTest.php | 36 + tests/Builder/Expression/WeekOperatorTest.php | 29 + tests/Builder/Expression/YearOperatorTest.php | 29 + tests/Builder/Query/Pipelines.php | 16 +- 58 files changed, 2786 insertions(+), 51 deletions(-) create mode 100644 tests/Builder/Expression/DateAddOperatorTest.php create mode 100644 tests/Builder/Expression/DateDiffOperatorTest.php create mode 100644 tests/Builder/Expression/DateFromPartsOperatorTest.php create mode 100644 tests/Builder/Expression/DateFromStringOperatorTest.php create mode 100644 tests/Builder/Expression/DateSubtractOperatorTest.php create mode 100644 tests/Builder/Expression/DateToPartsOperatorTest.php create mode 100644 tests/Builder/Expression/DateToStringOperatorTest.php create mode 100644 tests/Builder/Expression/DateTruncOperatorTest.php create mode 100644 tests/Builder/Expression/DayOfMonthOperatorTest.php create mode 100644 tests/Builder/Expression/DayOfWeekOperatorTest.php create mode 100644 tests/Builder/Expression/DayOfYearOperatorTest.php create mode 100644 tests/Builder/Expression/HourOperatorTest.php create mode 100644 tests/Builder/Expression/IsoDayOfWeekOperatorTest.php create mode 100644 tests/Builder/Expression/IsoWeekOperatorTest.php create mode 100644 tests/Builder/Expression/IsoWeekYearOperatorTest.php create mode 100644 tests/Builder/Expression/MillisecondOperatorTest.php create mode 100644 tests/Builder/Expression/MinuteOperatorTest.php create mode 100644 tests/Builder/Expression/MonthOperatorTest.php create mode 100644 tests/Builder/Expression/SecondOperatorTest.php create mode 100644 tests/Builder/Expression/ToDateOperatorTest.php create mode 100644 tests/Builder/Expression/WeekOperatorTest.php create mode 100644 tests/Builder/Expression/YearOperatorTest.php diff --git a/generator/README.md b/generator/README.md index fbb54183f..acbeeec03 100644 --- a/generator/README.md +++ b/generator/README.md @@ -17,5 +17,18 @@ To run the generator, you need to have PHP 8.1+ installed and Composer. The `generator/config/*.yaml` files contains the list of operators and stages that are supported by the library. +### Test pipelines + +Each operator can contain a `tests` section with a list if pipelines. To represent specific BSON objects, +it is necessary to use Yaml tags: + +| BSON Type | Example | +|-------------|----------------------------------------------| +| Regex | `!regex '^abc'`
`!regex ['^abc', 'i']` | +| Int64 | `!long '123456789'` | +| Decimal128 | `!double '0.9'` | +| UTCDateTime | `!date 0` | +| Binary | `!binary 'IA=='` | + To add new test cases to operators, you can get inspiration from the official MongoDB documentation and use the `generator/js2yaml.html` web page to manually convert a pipeline array from JS to Yaml. diff --git a/generator/config/expression/dateAdd.yaml b/generator/config/expression/dateAdd.yaml index c3f167f59..d908f69f9 100644 --- a/generator/config/expression/dateAdd.yaml +++ b/generator/config/expression/dateAdd.yaml @@ -33,3 +33,100 @@ arguments: optional: true description: | The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Add a Future Date' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateAdd/#add-a-future-date' + pipeline: + - + $project: + expectedDeliveryDate: + $dateAdd: + startDate: '$purchaseDate' + unit: 'day' + amount: 3 + - + # $merge: 'shipping' + $merge: + into: 'shipping' + - + name: 'Filter on a Date Range' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateAdd/#filter-on-a-date-range' + pipeline: + - + $match: + $expr: + $gt: + - '$deliveryDate' + - + $dateAdd: + startDate: '$purchaseDate' + unit: 'day' + amount: 5 + - + $project: + _id: 0 + custId: 1 + purchased: + $dateToString: + format: '%Y-%m-%d' + date: '$purchaseDate' + delivery: + $dateToString: + format: '%Y-%m-%d' + date: '$deliveryDate' + - + name: 'Adjust for Daylight Savings Time' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateAdd/#adjust-for-daylight-savings-time' + pipeline: + - + $project: + _id: 0 + location: 1 + start: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: '$login' + days: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: + $dateAdd: + startDate: '$login' + unit: 'day' + amount: 1 + timezone: '$location' + hours: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: + $dateAdd: + startDate: '$login' + unit: 'hour' + amount: 24 + timezone: '$location' + startTZInfo: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: '$login' + timezone: '$location' + daysTZInfo: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: + $dateAdd: + startDate: '$login' + unit: 'day' + amount: 1 + timezone: '$location' + timezone: '$location' + hoursTZInfo: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: + $dateAdd: + startDate: '$login' + unit: 'hour' + amount: 24 + timezone: '$location' + timezone: '$location' diff --git a/generator/config/expression/dateDiff.yaml b/generator/config/expression/dateDiff.yaml index 6ff998154..d2b86a58c 100644 --- a/generator/config/expression/dateDiff.yaml +++ b/generator/config/expression/dateDiff.yaml @@ -43,3 +43,72 @@ arguments: optional: true description: | Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string +tests: + - + name: 'Elapsed Time' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateDiff/#elapsed-time' + pipeline: + - + $group: + _id: ~ + averageTime: + $avg: + $dateDiff: + startDate: '$purchased' + endDate: '$delivered' + unit: 'day' + - + $project: + _id: 0 + numDays: + $trunc: + - '$averageTime' + - 1 + - + name: 'Result Precision' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateDiff/#result-precision' + pipeline: + - + $project: + Start: '$start' + End: '$end' + years: + $dateDiff: + startDate: '$start' + endDate: '$end' + unit: 'year' + months: + $dateDiff: + startDate: '$start' + endDate: '$end' + unit: 'month' + days: + $dateDiff: + startDate: '$start' + endDate: '$end' + unit: 'day' + _id: 0 + - + name: 'Weeks Per Month' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateDiff/#weeks-per-month' + pipeline: + - + $project: + wks_default: + $dateDiff: + startDate: '$start' + endDate: '$end' + unit: 'week' + wks_monday: + $dateDiff: + startDate: '$start' + endDate: '$end' + unit: 'week' + startOfWeek: 'Monday' + wks_friday: + $dateDiff: + startDate: '$start' + endDate: '$end' + unit: 'week' + startOfWeek: 'fri' + _id: 0 diff --git a/generator/config/expression/dateFromParts.yaml b/generator/config/expression/dateFromParts.yaml index 7759f7b96..3ed35004e 100644 --- a/generator/config/expression/dateFromParts.yaml +++ b/generator/config/expression/dateFromParts.yaml @@ -11,12 +11,14 @@ arguments: name: year type: - resolvesToNumber + optional: true description: | Calendar year. Can be any expression that evaluates to a number. - name: isoWeekYear type: - resolvesToNumber + optional: true description: | ISO Week Date Year. Can be any expression that evaluates to a number. - @@ -82,3 +84,31 @@ arguments: optional: true description: | The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateFromParts/#example' + pipeline: + - + $project: + date: + $dateFromParts: + year: 2017 + month: 2 + day: 8 + hour: 12 + date_iso: + $dateFromParts: + isoWeekYear: 2017 + isoWeek: 6 + isoDayOfWeek: 3 + hour: 12 + date_timezone: + $dateFromParts: + year: 2016 + month: 12 + day: 31 + hour: 23 + minute: 46 + second: 12 + timezone: 'America/New_York' diff --git a/generator/config/expression/dateFromString.yaml b/generator/config/expression/dateFromString.yaml index 69c761f9a..c8f118c19 100644 --- a/generator/config/expression/dateFromString.yaml +++ b/generator/config/expression/dateFromString.yaml @@ -44,3 +44,38 @@ arguments: description: | If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. This result value can be of any type. If you do not specify onNull and dateString is null or missing, then $dateFromString outputs null. +tests: + - + name: 'Converting Dates' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateFromString/#converting-dates' + pipeline: + - + $project: + date: + $dateFromString: + dateString: '$date' + timezone: 'America/New_York' + - + name: 'onError' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateFromString/#onerror' + pipeline: + - + $project: + date: + $dateFromString: + dateString: '$date' + timezone: '$timezone' + onError: '$date' + - + name: 'onNull' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateFromString/#onnull' + pipeline: + - + $project: + date: + $dateFromString: + dateString: '$date' + timezone: '$timezone' + # onNull: new Date(0) + onNull: !date 0 + diff --git a/generator/config/expression/dateSubtract.yaml b/generator/config/expression/dateSubtract.yaml index fc01ca0b4..b2b33bede 100644 --- a/generator/config/expression/dateSubtract.yaml +++ b/generator/config/expression/dateSubtract.yaml @@ -33,3 +33,105 @@ arguments: optional: true description: | The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Subtract A Fixed Amount' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateSubtract/#subtract-a-fixed-amount' + pipeline: + - + $match: + $expr: + $eq: + - + # $month: '$logout' + $month: + date: '$logout' + - 1 + - + $project: + logoutTime: + $dateSubtract: + startDate: '$logout' + unit: 'hour' + amount: 3 + - + # $merge: 'connectionTime' + $merge: + into: 'connectionTime' + - + name: 'Filter by Relative Dates' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateSubtract/#filter-by-relative-dates' + pipeline: + - + $match: + $expr: + $gt: + - '$logoutTime' + - + $dateSubtract: + startDate: '$$NOW' + unit: 'week' + amount: 1 + - + $project: + _id: 0 + custId: 1 + loggedOut: + $dateToString: + format: '%Y-%m-%d' + date: '$logoutTime' + - + name: 'Adjust for Daylight Savings Time' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateSubtract/#adjust-for-daylight-savings-time' + pipeline: + - + $project: + _id: 0 + location: 1 + start: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: '$login' + days: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: + $dateSubtract: + startDate: '$login' + unit: 'day' + amount: 1 + timezone: '$location' + hours: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: + $dateSubtract: + startDate: '$login' + unit: 'hour' + amount: 24 + timezone: '$location' + startTZInfo: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: '$login' + timezone: '$location' + daysTZInfo: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: + $dateSubtract: + startDate: '$login' + unit: 'day' + amount: 1 + timezone: '$location' + timezone: '$location' + hoursTZInfo: + $dateToString: + format: '%Y-%m-%d %H:%M' + date: + $dateSubtract: + startDate: '$login' + unit: 'hour' + amount: 24 + timezone: '$location' + timezone: '$location' diff --git a/generator/config/expression/dateToParts.yaml b/generator/config/expression/dateToParts.yaml index 21bace3ad..d250e052f 100644 --- a/generator/config/expression/dateToParts.yaml +++ b/generator/config/expression/dateToParts.yaml @@ -29,3 +29,21 @@ arguments: optional: true description: | If set to true, modifies the output document to use ISO week date fields. Defaults to false. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToParts/#example' + pipeline: + - + $project: + date: + $dateToParts: + date: '$date' + date_iso: + $dateToParts: + date: '$date' + iso8601: true + date_timezone: + $dateToParts: + date: '$date' + timezone: 'America/New_York' diff --git a/generator/config/expression/dateToString.yaml b/generator/config/expression/dateToString.yaml index 248cb9ffb..29e0ea8c8 100644 --- a/generator/config/expression/dateToString.yaml +++ b/generator/config/expression/dateToString.yaml @@ -38,3 +38,44 @@ arguments: description: | The value to return if the date is null or missing. If unspecified, $dateToString returns null if the date is null or missing. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/#example' + pipeline: + - + $project: + yearMonthDayUTC: + $dateToString: + format: '%Y-%m-%d' + date: '$date' + timewithOffsetNY: + $dateToString: + format: '%H:%M:%S:%L%z' + date: '$date' + timezone: 'America/New_York' + timewithOffset430: + $dateToString: + format: '%H:%M:%S:%L%z' + date: '$date' + timezone: '+04:30' + minutesOffsetNY: + $dateToString: + format: '%Z' + date: '$date' + timezone: 'America/New_York' + minutesOffset430: + $dateToString: + format: '%Z' + date: '$date' + timezone: '+04:30' + abbreviated_month: + $dateToString: + format: '%b' + date: '$date' + timezone: '+04:30' + full_month: + $dateToString: + format: '%B' + date: '$date' + timezone: '+04:30' diff --git a/generator/config/expression/dateTrunc.yaml b/generator/config/expression/dateTrunc.yaml index 2c3042b28..0ac6af68e 100644 --- a/generator/config/expression/dateTrunc.yaml +++ b/generator/config/expression/dateTrunc.yaml @@ -45,3 +45,33 @@ arguments: description: | The start of the week. Used when unit is week. Defaults to Sunday. +tests: + - + name: 'Truncate Order Dates in a $project Pipeline Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateTrunc/#truncate-order-dates-in-a--project-pipeline-stage' + pipeline: + - + $project: + _id: 1 + orderDate: 1 + truncatedOrderDate: + $dateTrunc: + date: '$orderDate' + unit: 'week' + binSize: 2 + timezone: 'America/Los_Angeles' + startOfWeek: 'Monday' + - + name: 'Truncate Order Dates and Obtain Quantity Sum in a $group Pipeline Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateTrunc/#truncate-order-dates-and-obtain-quantity-sum-in-a--group-pipeline-stage' + pipeline: + - + $group: + _id: + truncatedOrderDate: + $dateTrunc: + date: '$orderDate' + unit: 'month' + binSize: 6 + sumQuantity: + $sum: '$quantity' diff --git a/generator/config/expression/dayOfMonth.yaml b/generator/config/expression/dayOfMonth.yaml index b0be6eadd..032c61c98 100644 --- a/generator/config/expression/dayOfMonth.yaml +++ b/generator/config/expression/dayOfMonth.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfMonth/#example' + pipeline: + - + $project: + day: + # $dayOfMonth: '$date' + $dayOfMonth: + date: '$date' diff --git a/generator/config/expression/dayOfWeek.yaml b/generator/config/expression/dayOfWeek.yaml index df93ef41a..daa452d75 100644 --- a/generator/config/expression/dayOfWeek.yaml +++ b/generator/config/expression/dayOfWeek.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfWeek/#example' + pipeline: + - + $project: + dayOfWeek: + # $dayOfWeek: '$date' + $dayOfWeek: + date: '$date' diff --git a/generator/config/expression/dayOfYear.yaml b/generator/config/expression/dayOfYear.yaml index 889ff7fee..978291db9 100644 --- a/generator/config/expression/dayOfYear.yaml +++ b/generator/config/expression/dayOfYear.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfYear/#example' + pipeline: + - + $project: + dayOfYear: + # $dayOfYear: '$date' + $dayOfYear: + date: '$date' diff --git a/generator/config/expression/hour.yaml b/generator/config/expression/hour.yaml index f620f8917..abc6db305 100644 --- a/generator/config/expression/hour.yaml +++ b/generator/config/expression/hour.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/hour/#example' + pipeline: + - + $project: + hour: + # $hour: '$date' + $hour: + date: '$date' diff --git a/generator/config/expression/isoDayOfWeek.yaml b/generator/config/expression/isoDayOfWeek.yaml index 7f6245c20..4511a6fa4 100644 --- a/generator/config/expression/isoDayOfWeek.yaml +++ b/generator/config/expression/isoDayOfWeek.yaml @@ -22,3 +22,16 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoDayOfWeek/#example' + pipeline: + - + $project: + _id: 0 + name: '$name' + dayOfWeek: + # $isoDayOfWeek: '$birthday' + $isoDayOfWeek: + date: '$birthday' diff --git a/generator/config/expression/isoWeek.yaml b/generator/config/expression/isoWeek.yaml index b395fe99f..07598de58 100644 --- a/generator/config/expression/isoWeek.yaml +++ b/generator/config/expression/isoWeek.yaml @@ -22,3 +22,16 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoWeek/#example' + pipeline: + - + $project: + _id: 0 + city: '$city' + weekNumber: + # $isoWeek: '$date' + $isoWeek: + date: '$date' diff --git a/generator/config/expression/isoWeekYear.yaml b/generator/config/expression/isoWeekYear.yaml index fb5ee284d..af5457ba7 100644 --- a/generator/config/expression/isoWeekYear.yaml +++ b/generator/config/expression/isoWeekYear.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoWeekYear/#example' + pipeline: + - + $project: + yearNumber: + # $isoWeekYear: '$date' + $isoWeekYear: + date: '$date' diff --git a/generator/config/expression/millisecond.yaml b/generator/config/expression/millisecond.yaml index 3027cfc82..48b2bfe3b 100644 --- a/generator/config/expression/millisecond.yaml +++ b/generator/config/expression/millisecond.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/millisecond/#example' + pipeline: + - + $project: + milliseconds: + # $millisecond: '$date' + $millisecond: + date: '$date' diff --git a/generator/config/expression/minute.yaml b/generator/config/expression/minute.yaml index e6a39feec..4a896618c 100644 --- a/generator/config/expression/minute.yaml +++ b/generator/config/expression/minute.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/minute/#example' + pipeline: + - + $project: + minutes: + # $minute: '$date' + $minute: + date: '$date' diff --git a/generator/config/expression/month.yaml b/generator/config/expression/month.yaml index e77d08520..6187a78f7 100644 --- a/generator/config/expression/month.yaml +++ b/generator/config/expression/month.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/month/#example' + pipeline: + - + $project: + month: + # $month: '$date' + $month: + date: '$date' diff --git a/generator/config/expression/second.yaml b/generator/config/expression/second.yaml index 7e731d24f..48ceba31d 100644 --- a/generator/config/expression/second.yaml +++ b/generator/config/expression/second.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/second/#example' + pipeline: + - + $project: + seconds: + # $second: '$date' + $second: + date: '$date' diff --git a/generator/config/expression/toDate.yaml b/generator/config/expression/toDate.yaml index 173669cd4..d9434a6bd 100644 --- a/generator/config/expression/toDate.yaml +++ b/generator/config/expression/toDate.yaml @@ -12,3 +12,15 @@ arguments: name: expression type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDate/#example' + pipeline: + - + $addFields: + convertedDate: + $toDate: '$order_date' + - + $sort: + convertedDate: 1 diff --git a/generator/config/expression/week.yaml b/generator/config/expression/week.yaml index 4fd7b08f3..839bdae20 100644 --- a/generator/config/expression/week.yaml +++ b/generator/config/expression/week.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/week/#example' + pipeline: + - + $project: + week: + # $week: '$date' + $week: + date: '$date' diff --git a/generator/config/expression/year.yaml b/generator/config/expression/year.yaml index e2de94fa9..ee1a689b6 100644 --- a/generator/config/expression/year.yaml +++ b/generator/config/expression/year.yaml @@ -22,3 +22,14 @@ arguments: optional: true description: | The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/year/#example' + pipeline: + - + $project: + year: + # $year: '$date' + $year: + date: '$date' diff --git a/generator/config/query/eq.yaml b/generator/config/query/eq.yaml index 9e1ca42a5..5018fedc9 100644 --- a/generator/config/query/eq.yaml +++ b/generator/config/query/eq.yaml @@ -53,13 +53,9 @@ tests: - $match: company: - $regularExpression: - options: '' - pattern: '^MongoDB' + !regex '^MongoDB' - $match: company: $eq: - $regularExpression: - options: '' - pattern: '^MongoDB' + !regex '^MongoDB' diff --git a/generator/config/query/in.yaml b/generator/config/query/in.yaml index 3767c39ec..06bda1289 100644 --- a/generator/config/query/in.yaml +++ b/generator/config/query/in.yaml @@ -28,11 +28,5 @@ tests: $match: tags: $in: - - - $regularExpression: - options: '' - pattern: '^be' - - - $regularExpression: - options: '' - pattern: '^st' + - !regex '^be' + - !regex '^st' diff --git a/generator/config/query/not.yaml b/generator/config/query/not.yaml index 2b3702e7c..ee52e3c71 100644 --- a/generator/config/query/not.yaml +++ b/generator/config/query/not.yaml @@ -28,7 +28,4 @@ tests: - $match: price: - $not: - $regularExpression: - pattern: '^p.*' - options: '' + $not: !regex '^p.*' diff --git a/generator/config/query/regex.yaml b/generator/config/query/regex.yaml index d1e69ad23..85f5d2dd3 100644 --- a/generator/config/query/regex.yaml +++ b/generator/config/query/regex.yaml @@ -21,9 +21,7 @@ tests: $match: sku: $regex: - $regularExpression: - pattern: '789$' - options: '' + !regex '789$' - name: 'Perform Case-Insensitive Regular Expression Match' link: 'https://www.mongodb.com/docs/manual/reference/operator/query/regex/#perform-case-insensitive-regular-expression-match' @@ -32,6 +30,4 @@ tests: $match: sku: $regex: - $regularExpression: - pattern: '^ABC' - options: 'i' + !regex ['^ABC', 'i'] diff --git a/generator/config/stage/addFields.yaml b/generator/config/stage/addFields.yaml index 343362a74..9562cd646 100644 --- a/generator/config/stage/addFields.yaml +++ b/generator/config/stage/addFields.yaml @@ -14,7 +14,6 @@ arguments: variadic: object description: | Specify the name of each field to add and set its value to an aggregation expression or an empty object. - tests: - name: 'Using Two $addFields Stages' @@ -37,5 +36,6 @@ tests: name: 'Adding Fields to an Embedded Document' link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#adding-fields-to-an-embedded-document' pipeline: - - $addFields: - specs.fuel_type: 'unleaded' + - + $addFields: + specs.fuel_type: 'unleaded' diff --git a/generator/js2yaml.html b/generator/js2yaml.html index f333ae5ae..763f7c5c6 100644 --- a/generator/js2yaml.html +++ b/generator/js2yaml.html @@ -19,6 +19,15 @@

Convert JS examples into Yaml

+
- - + +
@@ -73,11 +75,11 @@

Convert JS examples into Yaml

} function convert(jsString) { - try { - return toYaml(eval(jsString), 1); - } catch (e) { - return e.toString(); - } + try { + return toYaml(eval(jsString), 1); + } catch (e) { + return e.toString(); + } } function toYaml(object, indent = 0) { @@ -87,6 +89,10 @@

Convert JS examples into Yaml

} if (Array.isArray(object)) { + if (object.length === 0) { + return ' []'; + } + return newline + '-' + object.map((item) => toYaml(item, indent + 1)).join(newline + '-'); } @@ -99,7 +105,7 @@

Convert JS examples into Yaml

} if (object instanceof TaggedValue) { - return " !" + object.tag + toYaml(object.value); + return " !" + object.tag + toYaml(object.value); } switch (typeof object) { @@ -114,6 +120,10 @@

Convert JS examples into Yaml

for (var key in object) { dump.push(key + ':' + toYaml(object[key], indent + 1)); } + if (dump.length === 0) { + return ' {}'; + } + return newline + dump.join(newline); case 'function': return toYaml({ diff --git a/tests/Builder/Expression/ConcatOperatorTest.php b/tests/Builder/Expression/ConcatOperatorTest.php new file mode 100644 index 000000000..d96f17685 --- /dev/null +++ b/tests/Builder/Expression/ConcatOperatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::ConcatExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/IndexOfBytesOperatorTest.php b/tests/Builder/Expression/IndexOfBytesOperatorTest.php new file mode 100644 index 000000000..51c7720e9 --- /dev/null +++ b/tests/Builder/Expression/IndexOfBytesOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::IndexOfBytesExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/IndexOfCPOperatorTest.php b/tests/Builder/Expression/IndexOfCPOperatorTest.php new file mode 100644 index 000000000..4d5e3b6da --- /dev/null +++ b/tests/Builder/Expression/IndexOfCPOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::IndexOfCPExamples, $pipeline); + } +} diff --git a/tests/Builder/Expression/LtrimOperatorTest.php b/tests/Builder/Expression/LtrimOperatorTest.php new file mode 100644 index 000000000..96045dd32 --- /dev/null +++ b/tests/Builder/Expression/LtrimOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::LtrimExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 5bb6988a3..b08609727 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -518,6 +518,27 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/concat/#examples + */ + case ConcatExample = <<<'JSON' + [ + { + "$project": { + "itemDescription": { + "$concat": [ + "$item", + " - ", + "$description" + ] + } + } + } + ] + JSON; + /** * Example * @@ -1937,6 +1958,46 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfBytes/#examples + */ + case IndexOfBytesExample = <<<'JSON' + [ + { + "$project": { + "byteLocation": { + "$indexOfBytes": [ + "$item", + "foo" + ] + } + } + } + ] + JSON; + + /** + * Examples + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfCP/#examples + */ + case IndexOfCPExamples = <<<'JSON' + [ + { + "$project": { + "cpLocation": { + "$indexOfCP": [ + "$item", + "foo" + ] + } + } + } + ] + JSON; + /** * Example * @@ -2180,6 +2241,28 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ltrim/#example + */ + case LtrimExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "description": { + "$ltrim": { + "input": "$description" + } + } + } + } + ] + JSON; + /** * Add to Each Element of an Array * @@ -2986,19 +3069,24 @@ enum Pipelines: string JSON; /** - * Example + * $regexFind and Its Options * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/reverseArray/#example + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFind/#-regexfind-and-its-options */ - case ReverseArrayExample = <<<'JSON' + case RegexFindRegexFindAndItsOptions = <<<'JSON' [ { - "$project": { - "name": { - "$numberInt": "1" - }, - "reverseFavorites": { - "$reverseArray": "$favorites" + "$addFields": { + "returnObject": { + "$regexFind": { + "input": "$description", + "regex": { + "$regularExpression": { + "pattern": "line", + "options": "" + } + } + } } } } @@ -3006,17 +3094,50 @@ enum Pipelines: string JSON; /** - * Example + * i Option * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/second/#example + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFind/#i-option */ - case SecondExample = <<<'JSON' + case RegexFindIOption = <<<'JSON' [ { - "$project": { - "seconds": { - "$second": { - "date": "$date" + "$addFields": { + "returnObject": { + "$regexFind": { + "input": "$description", + "regex": { + "$regularExpression": { + "pattern": "line", + "options": "i" + } + } + } + } + } + }, + { + "$addFields": { + "returnObject": { + "$regexFind": { + "input": "$description", + "regex": "line", + "options": "i" + } + } + } + }, + { + "$addFields": { + "returnObject": { + "$regexFind": { + "input": "$description", + "regex": { + "$regularExpression": { + "pattern": "line", + "options": "" + } + }, + "options": "i" } } } @@ -3025,28 +3146,24 @@ enum Pipelines: string JSON; /** - * Example + * $regexFindAll and Its Options * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setDifference/#example + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFindAll/#-regexfindall-and-its-options */ - case SetDifferenceExample = <<<'JSON' + case RegexFindAllRegexFindAllAndItsOptions = <<<'JSON' [ { - "$project": { - "flowerFieldA": { - "$numberInt": "1" - }, - "flowerFieldB": { - "$numberInt": "1" - }, - "inBOnly": { - "$setDifference": [ - "$flowerFieldB", - "$flowerFieldA" - ] - }, - "_id": { - "$numberInt": "0" + "$addFields": { + "returnObject": { + "$regexFindAll": { + "input": "$description", + "regex": { + "$regularExpression": { + "pattern": "line", + "options": "" + } + } + } } } } @@ -3054,28 +3171,51 @@ enum Pipelines: string JSON; /** - * Example + * i Option * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setEquals/#example + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFindAll/#i-option */ - case SetEqualsExample = <<<'JSON' + case RegexFindAllIOption = <<<'JSON' [ { - "$project": { - "_id": { - "$numberInt": "0" - }, - "cakes": { - "$numberInt": "1" - }, - "cupcakes": { - "$numberInt": "1" - }, - "sameFlavors": { - "$setEquals": [ - "$cakes", - "$cupcakes" - ] + "$addFields": { + "returnObject": { + "$regexFindAll": { + "input": "$description", + "regex": { + "$regularExpression": { + "pattern": "line", + "options": "i" + } + } + } + } + } + }, + { + "$addFields": { + "returnObject": { + "$regexFindAll": { + "input": "$description", + "regex": "line", + "options": "i" + } + } + } + }, + { + "$addFields": { + "returnObject": { + "$regexFindAll": { + "input": "$description", + "regex": { + "$regularExpression": { + "pattern": "line", + "options": "" + } + }, + "options": "i" + } } } } @@ -3083,76 +3223,94 @@ enum Pipelines: string JSON; /** - * Add Fields that Contain Periods + * Use $regexFindAll to Parse Email from String * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#add-fields-that-contain-periods--.- + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFindAll/#use--regexfindall-to-parse-email-from-string */ - case SetFieldAddFieldsThatContainPeriods = <<<'JSON' + case RegexFindAllUseRegexFindAllToParseEmailFromString = <<<'JSON' [ { - "$replaceWith": { - "$setField": { - "field": "price.usd", - "input": "$$ROOT", - "value": "$price" + "$addFields": { + "email": { + "$regexFindAll": { + "input": "$comment", + "regex": { + "$regularExpression": { + "pattern": "[a-z0-9_.+-]+@[a-z0-9_.+-]+\\.[a-z0-9_.+-]+", + "options": "i" + } + } + } } } }, { - "$unset": [ - "price" - ] + "$set": { + "email": "$email.match" + } } ] JSON; /** - * Add Fields that Start with a Dollar Sign + * Use Captured Groupings to Parse User Name * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#add-fields-that-start-with-a-dollar-sign---- + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFindAll/#use-captured-groupings-to-parse-user-name */ - case SetFieldAddFieldsThatStartWithADollarSign = <<<'JSON' + case RegexFindAllUseCapturedGroupingsToParseUserName = <<<'JSON' [ { - "$replaceWith": { - "$setField": { - "field": { - "$literal": "$price" - }, - "input": "$$ROOT", - "value": "$price" + "$addFields": { + "names": { + "$regexFindAll": { + "input": "$comment", + "regex": { + "$regularExpression": { + "pattern": "([a-z0-9_.+-]+)@[a-z0-9_.+-]+\\.[a-z0-9_.+-]+", + "options": "i" + } + } + } } } }, { - "$unset": [ - "price" - ] + "$set": { + "names": { + "$reduce": { + "input": "$names.captures", + "initialValue": [], + "in": { + "$concatArrays": [ + "$$value", + "$$this" + ] + } + } + } + } } ] JSON; /** - * Update Fields that Contain Periods + * $regexMatch and Its Options * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#update-fields-that-contain-periods--.- + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexMatch/#-regexmatch-and-its-options */ - case SetFieldUpdateFieldsThatContainPeriods = <<<'JSON' + case RegexMatchRegexMatchAndItsOptions = <<<'JSON' [ { - "$match": { - "_id": { - "$numberInt": "1" - } - } - }, - { - "$replaceWith": { - "$setField": { - "field": "price.usd", - "input": "$$ROOT", - "value": { - "$numberDouble": "49.99000000000000199" + "$addFields": { + "result": { + "$regexMatch": { + "input": "$description", + "regex": { + "$regularExpression": { + "pattern": "line", + "options": "" + } + } } } } @@ -3161,28 +3319,50 @@ enum Pipelines: string JSON; /** - * Update Fields that Start with a Dollar Sign + * i Option * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#update-fields-that-start-with-a-dollar-sign---- + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexMatch/#i-option */ - case SetFieldUpdateFieldsThatStartWithADollarSign = <<<'JSON' + case RegexMatchIOption = <<<'JSON' [ { - "$match": { - "_id": { - "$numberInt": "1" + "$addFields": { + "result": { + "$regexMatch": { + "input": "$description", + "regex": { + "$regularExpression": { + "pattern": "line", + "options": "i" + } + } + } } } }, { - "$replaceWith": { - "$setField": { - "field": { - "$literal": "$price" - }, - "input": "$$ROOT", - "value": { - "$numberDouble": "49.99000000000000199" + "$addFields": { + "result": { + "$regexMatch": { + "input": "$description", + "regex": "line", + "options": "i" + } + } + } + }, + { + "$addFields": { + "result": { + "$regexMatch": { + "input": "$description", + "regex": { + "$regularExpression": { + "pattern": "line", + "options": "" + } + }, + "options": "i" } } } @@ -3191,18 +3371,30 @@ enum Pipelines: string JSON; /** - * Remove Fields that Contain Periods + * Use $regexMatch to Check Email Address * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#remove-fields-that-contain-periods--.- + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexMatch/#use--regexmatch-to-check-email-address */ - case SetFieldRemoveFieldsThatContainPeriods = <<<'JSON' + case RegexMatchUseRegexMatchToCheckEmailAddress = <<<'JSON' [ { - "$replaceWith": { - "$setField": { - "field": "price.usd", - "input": "$$ROOT", - "value": "$$REMOVE" + "$addFields": { + "category": { + "$cond": { + "if": { + "$regexMatch": { + "input": "$comment", + "regex": { + "$regularExpression": { + "pattern": "[a-z0-9_.+-]+@mongodb.com", + "options": "i" + } + } + } + }, + "then": "Employee", + "else": "External" + } } } } @@ -3210,20 +3402,20 @@ enum Pipelines: string JSON; /** - * Remove Fields that Start with a Dollar Sign + * Example * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#remove-fields-that-start-with-a-dollar-sign---- + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceAll/#example */ - case SetFieldRemoveFieldsThatStartWithADollarSign = <<<'JSON' + case ReplaceAllExample = <<<'JSON' [ { - "$replaceWith": { - "$setField": { - "field": { - "$literal": "$price" - }, - "input": "$$ROOT", - "value": "$$REMOVE" + "$project": { + "item": { + "$replaceAll": { + "input": "$item", + "find": "blue paint", + "replacement": "red paint" + } } } } @@ -3231,28 +3423,62 @@ enum Pipelines: string JSON; /** - * Elements Array Example + * Example * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/#elements-array-example + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceOne/#example */ - case SetIntersectionElementsArrayExample = <<<'JSON' + case ReplaceOneExample = <<<'JSON' [ { "$project": { - "flowerFieldA": { + "item": { + "$replaceOne": { + "input": "$item", + "find": "blue paint", + "replacement": "red paint" + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/reverseArray/#example + */ + case ReverseArrayExample = <<<'JSON' + [ + { + "$project": { + "name": { "$numberInt": "1" }, - "flowerFieldB": { + "reverseFavorites": { + "$reverseArray": "$favorites" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rtrim/#example + */ + case RtrimExample = <<<'JSON' + [ + { + "$project": { + "item": { "$numberInt": "1" }, - "commonToBoth": { - "$setIntersection": [ - "$flowerFieldA", - "$flowerFieldB" - ] - }, - "_id": { - "$numberInt": "0" + "description": { + "$rtrim": { + "input": "$description" + } } } } @@ -3260,28 +3486,18 @@ enum Pipelines: string JSON; /** - * Retrieve Documents for Roles Granted to the Current User + * Example * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/#retrieve-documents-for-roles-granted-to-the-current-user + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/second/#example */ - case SetIntersectionRetrieveDocumentsForRolesGrantedToTheCurrentUser = <<<'JSON' + case SecondExample = <<<'JSON' [ { - "$match": { - "$expr": { - "$not": [ - { - "$eq": [ - { - "$setIntersection": [ - "$allowedRoles", - "$$USER_ROLES.role" - ] - }, - [] - ] - } - ] + "$project": { + "seconds": { + "$second": { + "date": "$date" + } } } } @@ -3291,9 +3507,9 @@ enum Pipelines: string /** * Example * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIsSubset/#example + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setDifference/#example */ - case SetIsSubsetExample = <<<'JSON' + case SetDifferenceExample = <<<'JSON' [ { "$project": { @@ -3303,10 +3519,10 @@ enum Pipelines: string "flowerFieldB": { "$numberInt": "1" }, - "AisSubset": { - "$setIsSubset": [ - "$flowerFieldA", - "$flowerFieldB" + "inBOnly": { + "$setDifference": [ + "$flowerFieldB", + "$flowerFieldA" ] }, "_id": { @@ -3320,26 +3536,26 @@ enum Pipelines: string /** * Example * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/#example + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setEquals/#example */ - case SetUnionExample = <<<'JSON' + case SetEqualsExample = <<<'JSON' [ { "$project": { - "flowerFieldA": { + "_id": { + "$numberInt": "0" + }, + "cakes": { "$numberInt": "1" }, - "flowerFieldB": { + "cupcakes": { "$numberInt": "1" }, - "allValues": { - "$setUnion": [ - "$flowerFieldA", - "$flowerFieldB" + "sameFlavors": { + "$setEquals": [ + "$cakes", + "$cupcakes" ] - }, - "_id": { - "$numberInt": "0" } } } @@ -3347,54 +3563,77 @@ enum Pipelines: string JSON; /** - * Example + * Add Fields that Contain Periods * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/size/#example + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#add-fields-that-contain-periods--.- */ - case SizeExample = <<<'JSON' + case SetFieldAddFieldsThatContainPeriods = <<<'JSON' [ { - "$project": { - "item": { - "$numberInt": "1" - }, - "numberOfColors": { - "$cond": { - "if": { - "$isArray": [ - "$colors" - ] - }, - "then": { - "$size": "$colors" - }, - "else": "NA" - } + "$replaceWith": { + "$setField": { + "field": "price.usd", + "input": "$$ROOT", + "value": "$price" } } + }, + { + "$unset": [ + "price" + ] } ] JSON; /** - * Example + * Add Fields that Start with a Dollar Sign * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/slice/#example + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#add-fields-that-start-with-a-dollar-sign---- */ - case SliceExample = <<<'JSON' + case SetFieldAddFieldsThatStartWithADollarSign = <<<'JSON' [ { - "$project": { - "name": { + "$replaceWith": { + "$setField": { + "field": { + "$literal": "$price" + }, + "input": "$$ROOT", + "value": "$price" + } + } + }, + { + "$unset": [ + "price" + ] + } + ] + JSON; + + /** + * Update Fields that Contain Periods + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#update-fields-that-contain-periods--.- + */ + case SetFieldUpdateFieldsThatContainPeriods = <<<'JSON' + [ + { + "$match": { + "_id": { "$numberInt": "1" - }, - "threeFavorites": { - "$slice": [ - "$favorites", - { - "$numberInt": "3" - } - ] + } + } + }, + { + "$replaceWith": { + "$setField": { + "field": "price.usd", + "input": "$$ROOT", + "value": { + "$numberDouble": "49.99000000000000199" + } } } } @@ -3402,25 +3641,28 @@ enum Pipelines: string JSON; /** - * Sort on a Field + * Update Fields that Start with a Dollar Sign * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-a-field + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#update-fields-that-start-with-a-dollar-sign---- */ - case SortArraySortOnAField = <<<'JSON' + case SetFieldUpdateFieldsThatStartWithADollarSign = <<<'JSON' [ { - "$project": { + "$match": { "_id": { - "$numberInt": "0" - }, - "result": { - "$sortArray": { - "input": "$team", - "sortBy": { - "name": { - "$numberInt": "1" - } - } + "$numberInt": "1" + } + } + }, + { + "$replaceWith": { + "$setField": { + "field": { + "$literal": "$price" + }, + "input": "$$ROOT", + "value": { + "$numberDouble": "49.99000000000000199" } } } @@ -3429,26 +3671,576 @@ enum Pipelines: string JSON; /** - * Sort on a Subfield + * Remove Fields that Contain Periods + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#remove-fields-that-contain-periods--.- + */ + case SetFieldRemoveFieldsThatContainPeriods = <<<'JSON' + [ + { + "$replaceWith": { + "$setField": { + "field": "price.usd", + "input": "$$ROOT", + "value": "$$REMOVE" + } + } + } + ] + JSON; + + /** + * Remove Fields that Start with a Dollar Sign + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/#remove-fields-that-start-with-a-dollar-sign---- + */ + case SetFieldRemoveFieldsThatStartWithADollarSign = <<<'JSON' + [ + { + "$replaceWith": { + "$setField": { + "field": { + "$literal": "$price" + }, + "input": "$$ROOT", + "value": "$$REMOVE" + } + } + } + ] + JSON; + + /** + * Elements Array Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/#elements-array-example + */ + case SetIntersectionElementsArrayExample = <<<'JSON' + [ + { + "$project": { + "flowerFieldA": { + "$numberInt": "1" + }, + "flowerFieldB": { + "$numberInt": "1" + }, + "commonToBoth": { + "$setIntersection": [ + "$flowerFieldA", + "$flowerFieldB" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Retrieve Documents for Roles Granted to the Current User + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIntersection/#retrieve-documents-for-roles-granted-to-the-current-user + */ + case SetIntersectionRetrieveDocumentsForRolesGrantedToTheCurrentUser = <<<'JSON' + [ + { + "$match": { + "$expr": { + "$not": [ + { + "$eq": [ + { + "$setIntersection": [ + "$allowedRoles", + "$$USER_ROLES.role" + ] + }, + [] + ] + } + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setIsSubset/#example + */ + case SetIsSubsetExample = <<<'JSON' + [ + { + "$project": { + "flowerFieldA": { + "$numberInt": "1" + }, + "flowerFieldB": { + "$numberInt": "1" + }, + "AisSubset": { + "$setIsSubset": [ + "$flowerFieldA", + "$flowerFieldB" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/#example + */ + case SetUnionExample = <<<'JSON' + [ + { + "$project": { + "flowerFieldA": { + "$numberInt": "1" + }, + "flowerFieldB": { + "$numberInt": "1" + }, + "allValues": { + "$setUnion": [ + "$flowerFieldA", + "$flowerFieldB" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/size/#example + */ + case SizeExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "numberOfColors": { + "$cond": { + "if": { + "$isArray": [ + "$colors" + ] + }, + "then": { + "$size": "$colors" + }, + "else": "NA" + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/slice/#example + */ + case SliceExample = <<<'JSON' + [ + { + "$project": { + "name": { + "$numberInt": "1" + }, + "threeFavorites": { + "$slice": [ + "$favorites", + { + "$numberInt": "3" + } + ] + } + } + } + ] + JSON; + + /** + * Sort on a Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-a-field + */ + case SortArraySortOnAField = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "result": { + "$sortArray": { + "input": "$team", + "sortBy": { + "name": { + "$numberInt": "1" + } + } + } + } + } + } + ] + JSON; + + /** + * Sort on a Subfield + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-a-subfield + */ + case SortArraySortOnASubfield = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "result": { + "$sortArray": { + "input": "$team", + "sortBy": { + "address.city": { + "$numberInt": "-1" + } + } + } + } + } + } + ] + JSON; + + /** + * Sort on Multiple Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-multiple-fields + */ + case SortArraySortOnMultipleFields = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "result": { + "$sortArray": { + "input": "$team", + "sortBy": { + "age": { + "$numberInt": "-1" + }, + "name": { + "$numberInt": "1" + } + } + } + } + } + } + ] + JSON; + + /** + * Sort an Array of Integers + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-an-array-of-integers + */ + case SortArraySortAnArrayOfIntegers = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "result": { + "$sortArray": { + "input": [ + { + "$numberInt": "1" + }, + { + "$numberInt": "4" + }, + { + "$numberInt": "1" + }, + { + "$numberInt": "6" + }, + { + "$numberInt": "12" + }, + { + "$numberInt": "5" + } + ], + "sortBy": { + "$numberInt": "1" + } + } + } + } + } + ] + JSON; + + /** + * Sort on Mixed Type Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-mixed-type-fields + */ + case SortArraySortOnMixedTypeFields = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "result": { + "$sortArray": { + "input": [ + { + "$numberInt": "20" + }, + { + "$numberInt": "4" + }, + { + "a": "Free" + }, + { + "$numberInt": "6" + }, + { + "$numberInt": "21" + }, + { + "$numberInt": "5" + }, + "Gratis", + { + "a": null + }, + { + "a": { + "sale": true, + "price": { + "$numberInt": "19" + } + } + }, + { + "$numberDouble": "10.230000000000000426" + }, + { + "a": "On sale" + } + ], + "sortBy": { + "$numberInt": "1" + } + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/split/#example + */ + case SplitExample = <<<'JSON' + [ + { + "$project": { + "city_state": { + "$split": [ + "$city", + ", " + ] + }, + "qty": { + "$numberInt": "1" + } + } + }, + { + "$unwind": { + "path": "$city_state" + } + }, + { + "$match": { + "city_state": { + "$regularExpression": { + "pattern": "[A-Z]{2}", + "options": "" + } + } + } + }, + { + "$group": { + "_id": { + "state": "$city_state" + }, + "total_qty": { + "$sum": "$qty" + } + } + }, + { + "$sort": { + "total_qty": { + "$numberInt": "-1" + } + } + } + ] + JSON; + + /** + * Use in $project Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/#use-in--project-stage + */ + case StdDevPopUseInProjectStage = <<<'JSON' + [ + { + "$project": { + "stdDev": { + "$stdDevPop": [ + "$scores.score" + ] + } + } + } + ] + JSON; + + /** + * Single-Byte and Multibyte Character Set + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenBytes/#single-byte-and-multibyte-character-set + */ + case StrLenBytesSingleByteAndMultibyteCharacterSet = <<<'JSON' + [ + { + "$project": { + "name": { + "$numberInt": "1" + }, + "length": { + "$strLenBytes": "$name" + } + } + } + ] + JSON; + + /** + * Single-Byte and Multibyte Character Set + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenBytes/#single-byte-and-multibyte-character-set + */ + case StrLenCPSingleByteAndMultibyteCharacterSet = <<<'JSON' + [ + { + "$project": { + "name": { + "$numberInt": "1" + }, + "length": { + "$strLenCP": "$name" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/strcasecmp/#example + */ + case StrcasecmpExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "comparisonResult": { + "$strcasecmp": [ + "$quarter", + "13q4" + ] + } + } + } + ] + JSON; + + /** + * Example * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-a-subfield + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substr/#example */ - case SortArraySortOnASubfield = <<<'JSON' + case SubstrExample = <<<'JSON' [ { "$project": { - "_id": { - "$numberInt": "0" + "item": { + "$numberInt": "1" }, - "result": { - "$sortArray": { - "input": "$team", - "sortBy": { - "address.city": { - "$numberInt": "-1" - } + "yearSubstring": { + "$substr": [ + "$quarter", + { + "$numberInt": "0" + }, + { + "$numberInt": "2" } - } + ] + }, + "quarterSubtring": { + "$substr": [ + "$quarter", + { + "$numberInt": "2" + }, + { + "$numberInt": "-1" + } + ] } } } @@ -3456,29 +4248,45 @@ enum Pipelines: string JSON; /** - * Sort on Multiple Fields + * Single-Byte Character Set * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-multiple-fields + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrBytes/#single-byte-character-set */ - case SortArraySortOnMultipleFields = <<<'JSON' + case SubstrBytesSingleByteCharacterSet = <<<'JSON' [ { "$project": { - "_id": { - "$numberInt": "0" + "item": { + "$numberInt": "1" }, - "result": { - "$sortArray": { - "input": "$team", - "sortBy": { - "age": { - "$numberInt": "-1" - }, - "name": { - "$numberInt": "1" - } + "yearSubstring": { + "$substrBytes": [ + "$quarter", + { + "$numberInt": "0" + }, + { + "$numberInt": "2" } - } + ] + }, + "quarterSubtring": { + "$substrBytes": [ + "$quarter", + { + "$numberInt": "2" + }, + { + "$subtract": [ + { + "$strLenBytes": "$quarter" + }, + { + "$numberInt": "2" + } + ] + } + ] } } } @@ -3486,43 +4294,27 @@ enum Pipelines: string JSON; /** - * Sort an Array of Integers + * Single-Byte and Multibyte Character Set * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-an-array-of-integers + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrBytes/#single-byte-and-multibyte-character-set */ - case SortArraySortAnArrayOfIntegers = <<<'JSON' + case SubstrBytesSingleByteAndMultibyteCharacterSet = <<<'JSON' [ { "$project": { - "_id": { - "$numberInt": "0" + "name": { + "$numberInt": "1" }, - "result": { - "$sortArray": { - "input": [ - { - "$numberInt": "1" - }, - { - "$numberInt": "4" - }, - { - "$numberInt": "1" - }, - { - "$numberInt": "6" - }, - { - "$numberInt": "12" - }, - { - "$numberInt": "5" - } - ], - "sortBy": { - "$numberInt": "1" + "menuCode": { + "$substrBytes": [ + "$name", + { + "$numberInt": "0" + }, + { + "$numberInt": "3" } - } + ] } } } @@ -3530,61 +4322,45 @@ enum Pipelines: string JSON; /** - * Sort on Mixed Type Fields + * Single-Byte Character Set * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortArray/#sort-on-mixed-type-fields + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrCP/#single-byte-character-set */ - case SortArraySortOnMixedTypeFields = <<<'JSON' + case SubstrCPSingleByteCharacterSet = <<<'JSON' [ { "$project": { - "_id": { - "$numberInt": "0" + "item": { + "$numberInt": "1" }, - "result": { - "$sortArray": { - "input": [ - { - "$numberInt": "20" - }, - { - "$numberInt": "4" - }, - { - "a": "Free" - }, - { - "$numberInt": "6" - }, - { - "$numberInt": "21" - }, - { - "$numberInt": "5" - }, - "Gratis", - { - "a": null - }, - { - "a": { - "sale": true, - "price": { - "$numberInt": "19" - } + "yearSubstring": { + "$substrCP": [ + "$quarter", + { + "$numberInt": "0" + }, + { + "$numberInt": "2" + } + ] + }, + "quarterSubtring": { + "$substrCP": [ + "$quarter", + { + "$numberInt": "2" + }, + { + "$subtract": [ + { + "$strLenCP": "$quarter" + }, + { + "$numberInt": "2" } - }, - { - "$numberDouble": "10.230000000000000426" - }, - { - "a": "On sale" - } - ], - "sortBy": { - "$numberInt": "1" + ] } - } + ] } } } @@ -3592,17 +4368,26 @@ enum Pipelines: string JSON; /** - * Use in $project Stage + * Single-Byte and Multibyte Character Set * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/stdDevPop/#use-in--project-stage + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrCP/#single-byte-and-multibyte-character-set */ - case StdDevPopUseInProjectStage = <<<'JSON' + case SubstrCPSingleByteAndMultibyteCharacterSet = <<<'JSON' [ { "$project": { - "stdDev": { - "$stdDevPop": [ - "$scores.score" + "name": { + "$numberInt": "1" + }, + "menuCode": { + "$substrCP": [ + "$name", + { + "$numberInt": "0" + }, + { + "$numberInt": "3" + } ] } } @@ -3849,6 +4634,92 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLower/#example + */ + case ToLowerExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$toLower": "$item" + }, + "description": { + "$toLower": "$description" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toString/#example + */ + case ToStringExample = <<<'JSON' + [ + { + "$addFields": { + "convertedZipCode": { + "$toString": "$zipcode" + } + } + }, + { + "$sort": { + "convertedZipCode": { + "$numberInt": "1" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toUpper/#example + */ + case ToUpperExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$toUpper": "$item" + }, + "description": { + "$toUpper": "$description" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/trim/#example + */ + case TrimExample = <<<'JSON' + [ + { + "$project": { + "item": { + "$numberInt": "1" + }, + "description": { + "$trim": { + "input": "$description" + } + } + } + } + ] + JSON; + /** * Remove Fields that Contain Periods * diff --git a/tests/Builder/Expression/RegexFindAllOperatorTest.php b/tests/Builder/Expression/RegexFindAllOperatorTest.php new file mode 100644 index 000000000..df8286c11 --- /dev/null +++ b/tests/Builder/Expression/RegexFindAllOperatorTest.php @@ -0,0 +1,100 @@ +assertSamePipeline(Pipelines::RegexFindAllIOption, $pipeline); + } + + public function testRegexFindAllAndItsOptions(): void + { + $pipeline = new Pipeline( + Stage::addFields( + returnObject: Expression::regexFindAll( + input: Expression::stringFieldPath('description'), + regex: new Regex('line'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::RegexFindAllRegexFindAllAndItsOptions, $pipeline); + } + + public function testUseCapturedGroupingsToParseUserName(): void + { + $pipeline = new Pipeline( + Stage::addFields( + names: Expression::regexFindAll( + input: Expression::stringFieldPath('comment'), + regex: new Regex('([a-z0-9_.+-]+)@[a-z0-9_.+-]+\\.[a-z0-9_.+-]+', 'i'), + ), + ), + Stage::set( + names: Expression::reduce( + input: Expression::arrayFieldPath('names.captures'), + initialValue: [], + in: Expression::concatArrays( + Expression::variable('value'), + Expression::variable('this'), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::RegexFindAllUseCapturedGroupingsToParseUserName, $pipeline); + } + + public function testUseRegexFindAllToParseEmailFromString(): void + { + $pipeline = new Pipeline( + Stage::addFields( + email: Expression::regexFindAll( + input: Expression::stringFieldPath('comment'), + regex: new Regex('[a-z0-9_.+-]+@[a-z0-9_.+-]+\\.[a-z0-9_.+-]+', 'i'), + ), + ), + Stage::set( + email: Expression::stringFieldPath('email.match'), + ), + ); + + $this->assertSamePipeline(Pipelines::RegexFindAllUseRegexFindAllToParseEmailFromString, $pipeline); + } +} diff --git a/tests/Builder/Expression/RegexFindOperatorTest.php b/tests/Builder/Expression/RegexFindOperatorTest.php new file mode 100644 index 000000000..d2ed7b453 --- /dev/null +++ b/tests/Builder/Expression/RegexFindOperatorTest.php @@ -0,0 +1,59 @@ +assertSamePipeline(Pipelines::RegexFindIOption, $pipeline); + } + + public function testRegexFindAndItsOptions(): void + { + $pipeline = new Pipeline( + Stage::addFields( + returnObject: Expression::regexFind( + input: Expression::stringFieldPath('description'), + regex: new Regex('line'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::RegexFindRegexFindAndItsOptions, $pipeline); + } +} diff --git a/tests/Builder/Expression/RegexMatchOperatorTest.php b/tests/Builder/Expression/RegexMatchOperatorTest.php new file mode 100644 index 000000000..55f39faf4 --- /dev/null +++ b/tests/Builder/Expression/RegexMatchOperatorTest.php @@ -0,0 +1,77 @@ +assertSamePipeline(Pipelines::RegexMatchIOption, $pipeline); + } + + public function testRegexMatchAndItsOptions(): void + { + $pipeline = new Pipeline( + Stage::addFields( + result: Expression::regexMatch( + input: Expression::stringFieldPath('description'), + regex: new Regex('line', ''), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::RegexMatchRegexMatchAndItsOptions, $pipeline); + } + + public function testUseRegexMatchToCheckEmailAddress(): void + { + $pipeline = new Pipeline( + Stage::addFields( + category: Expression::cond( + if: Expression::regexMatch( + input: Expression::stringFieldPath('comment'), + regex: new Regex('[a-z0-9_.+-]+@mongodb.com', 'i'), + ), + then: 'Employee', + else: 'External', + ), + ), + ); + + $this->assertSamePipeline(Pipelines::RegexMatchUseRegexMatchToCheckEmailAddress, $pipeline); + } +} diff --git a/tests/Builder/Expression/ReplaceAllOperatorTest.php b/tests/Builder/Expression/ReplaceAllOperatorTest.php new file mode 100644 index 000000000..1d1fa6ada --- /dev/null +++ b/tests/Builder/Expression/ReplaceAllOperatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::ReplaceAllExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ReplaceOneOperatorTest.php b/tests/Builder/Expression/ReplaceOneOperatorTest.php new file mode 100644 index 000000000..90bf8082b --- /dev/null +++ b/tests/Builder/Expression/ReplaceOneOperatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::ReplaceOneExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/RtrimOperatorTest.php b/tests/Builder/Expression/RtrimOperatorTest.php new file mode 100644 index 000000000..077fe8dd4 --- /dev/null +++ b/tests/Builder/Expression/RtrimOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::RtrimExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SplitOperatorTest.php b/tests/Builder/Expression/SplitOperatorTest.php new file mode 100644 index 000000000..9519b2899 --- /dev/null +++ b/tests/Builder/Expression/SplitOperatorTest.php @@ -0,0 +1,54 @@ +assertSamePipeline(Pipelines::SplitExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/StrLenBytesOperatorTest.php b/tests/Builder/Expression/StrLenBytesOperatorTest.php new file mode 100644 index 000000000..1c465b672 --- /dev/null +++ b/tests/Builder/Expression/StrLenBytesOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::StrLenBytesSingleByteAndMultibyteCharacterSet, $pipeline); + } +} diff --git a/tests/Builder/Expression/StrLenCPOperatorTest.php b/tests/Builder/Expression/StrLenCPOperatorTest.php new file mode 100644 index 000000000..78e995479 --- /dev/null +++ b/tests/Builder/Expression/StrLenCPOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::StrLenCPSingleByteAndMultibyteCharacterSet, $pipeline); + } +} diff --git a/tests/Builder/Expression/StrcasecmpOperatorTest.php b/tests/Builder/Expression/StrcasecmpOperatorTest.php new file mode 100644 index 000000000..543ac54d0 --- /dev/null +++ b/tests/Builder/Expression/StrcasecmpOperatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::StrcasecmpExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SubstrBytesOperatorTest.php b/tests/Builder/Expression/SubstrBytesOperatorTest.php new file mode 100644 index 000000000..34d4d6f61 --- /dev/null +++ b/tests/Builder/Expression/SubstrBytesOperatorTest.php @@ -0,0 +1,58 @@ +assertSamePipeline(Pipelines::SubstrBytesSingleByteAndMultibyteCharacterSet, $pipeline); + } + + public function testSingleByteCharacterSet(): void + { + $pipeline = new Pipeline( + Stage::project( + item: 1, + yearSubstring: Expression::substrBytes( + Expression::stringFieldPath('quarter'), + 0, + 2, + ), + quarterSubtring: Expression::substrBytes( + Expression::stringFieldPath('quarter'), + 2, + Expression::subtract( + Expression::strLenBytes( + Expression::stringFieldPath('quarter'), + ), + 2, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SubstrBytesSingleByteCharacterSet, $pipeline); + } +} diff --git a/tests/Builder/Expression/SubstrCPOperatorTest.php b/tests/Builder/Expression/SubstrCPOperatorTest.php new file mode 100644 index 000000000..9a24b1fa9 --- /dev/null +++ b/tests/Builder/Expression/SubstrCPOperatorTest.php @@ -0,0 +1,58 @@ +assertSamePipeline(Pipelines::SubstrCPSingleByteAndMultibyteCharacterSet, $pipeline); + } + + public function testSingleByteCharacterSet(): void + { + $pipeline = new Pipeline( + Stage::project( + item: 1, + yearSubstring: Expression::substrCP( + Expression::stringFieldPath('quarter'), + 0, + 2, + ), + quarterSubtring: Expression::substrCP( + Expression::stringFieldPath('quarter'), + 2, + Expression::subtract( + Expression::strLenCP( + Expression::stringFieldPath('quarter'), + ), + 2, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SubstrCPSingleByteCharacterSet, $pipeline); + } +} diff --git a/tests/Builder/Expression/SubstrOperatorTest.php b/tests/Builder/Expression/SubstrOperatorTest.php new file mode 100644 index 000000000..f2a6f432d --- /dev/null +++ b/tests/Builder/Expression/SubstrOperatorTest.php @@ -0,0 +1,37 @@ +assertSamePipeline(Pipelines::SubstrExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ToLowerOperatorTest.php b/tests/Builder/Expression/ToLowerOperatorTest.php new file mode 100644 index 000000000..6f8cc154d --- /dev/null +++ b/tests/Builder/Expression/ToLowerOperatorTest.php @@ -0,0 +1,32 @@ +assertSamePipeline(Pipelines::ToLowerExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ToStringOperatorTest.php b/tests/Builder/Expression/ToStringOperatorTest.php new file mode 100644 index 000000000..dae89b5c7 --- /dev/null +++ b/tests/Builder/Expression/ToStringOperatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::ToStringExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ToUpperOperatorTest.php b/tests/Builder/Expression/ToUpperOperatorTest.php new file mode 100644 index 000000000..529892ea1 --- /dev/null +++ b/tests/Builder/Expression/ToUpperOperatorTest.php @@ -0,0 +1,32 @@ +assertSamePipeline(Pipelines::ToUpperExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/TrimOperatorTest.php b/tests/Builder/Expression/TrimOperatorTest.php new file mode 100644 index 000000000..81c2e2353 --- /dev/null +++ b/tests/Builder/Expression/TrimOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::TrimExample, $pipeline); + } +} From 3ce798c64e0c6273e63712cc8017c177e2bb41d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:46:37 +0100 Subject: [PATCH 42/95] Bump actions/cache from 3 to 4 (#45) Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coding-standards.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 281d63d92..650ae577b 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -36,7 +36,7 @@ jobs: key: "extcache-v1" - name: Cache extensions - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.extcache.outputs.dir }} key: ${{ steps.extcache.outputs.key }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 6c9bdd7a6..a2b4fd883 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -36,7 +36,7 @@ jobs: key: "extcache-v1" - name: Cache extensions - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.extcache.outputs.dir }} key: ${{ steps.extcache.outputs.key }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8ad4a39cb..e3caaabc2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -56,7 +56,7 @@ jobs: key: "extcache-v1" - name: Cache extensions - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.extcache.outputs.dir }} key: ${{ steps.extcache.outputs.key }} From d0f0565c8990cc1594d6e6fd8b7debf1f071f106 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 23 Jan 2024 15:14:10 +0100 Subject: [PATCH 43/95] Fix bson_regex Yaml tag --- generator/config/expression/regexFind.yaml | 6 +++--- generator/config/expression/regexFindAll.yaml | 10 +++++----- generator/config/expression/regexMatch.yaml | 8 ++++---- generator/config/expression/split.yaml | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/generator/config/expression/regexFind.yaml b/generator/config/expression/regexFind.yaml index 41426f6a3..d953a4ae6 100644 --- a/generator/config/expression/regexFind.yaml +++ b/generator/config/expression/regexFind.yaml @@ -36,7 +36,7 @@ tests: returnObject: $regexFind: input: '$description' - regex: !regex 'line' + regex: !bson_regex 'line' - name: 'i Option' link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFind/#i-option' @@ -47,7 +47,7 @@ tests: # Specify i as part of the Regex type $regexFind: input: '$description' - regex: !regex ['line', 'i'] + regex: !bson_regex ['line', 'i'] - $addFields: returnObject: @@ -62,5 +62,5 @@ tests: # Mix Regex type with options field $regexFind: input: '$description' - regex: !regex 'line' + regex: !bson_regex 'line' options: 'i' diff --git a/generator/config/expression/regexFindAll.yaml b/generator/config/expression/regexFindAll.yaml index 78f61260e..6aea184d7 100644 --- a/generator/config/expression/regexFindAll.yaml +++ b/generator/config/expression/regexFindAll.yaml @@ -36,7 +36,7 @@ tests: returnObject: $regexFindAll: input: '$description' - regex: !regex 'line' + regex: !bson_regex 'line' - name: 'i Option' link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFindAll/#i-option' @@ -47,7 +47,7 @@ tests: # Specify i as part of the regex type $regexFindAll: input: '$description' - regex: !regex ['line', 'i'] + regex: !bson_regex ['line', 'i'] - $addFields: returnObject: @@ -62,7 +62,7 @@ tests: # Mix Regex type with options field $regexFindAll: input: '$description' - regex: !regex 'line' + regex: !bson_regex 'line' options: 'i' - name: 'Use $regexFindAll to Parse Email from String' @@ -73,7 +73,7 @@ tests: email: $regexFindAll: input: '$comment' - regex: !regex ['[a-z0-9_.+-]+@[a-z0-9_.+-]+\.[a-z0-9_.+-]+', 'i'] + regex: !bson_regex ['[a-z0-9_.+-]+@[a-z0-9_.+-]+\.[a-z0-9_.+-]+', 'i'] - $set: email: '$email.match' @@ -86,7 +86,7 @@ tests: names: $regexFindAll: input: '$comment' - regex: !regex ['([a-z0-9_.+-]+)@[a-z0-9_.+-]+\.[a-z0-9_.+-]+', 'i'] + regex: !bson_regex ['([a-z0-9_.+-]+)@[a-z0-9_.+-]+\.[a-z0-9_.+-]+', 'i'] - $set: names: diff --git a/generator/config/expression/regexMatch.yaml b/generator/config/expression/regexMatch.yaml index 29846d7f9..4ea9f0aab 100644 --- a/generator/config/expression/regexMatch.yaml +++ b/generator/config/expression/regexMatch.yaml @@ -36,7 +36,7 @@ tests: result: $regexMatch: input: '$description' - regex: !regex 'line' + regex: !bson_regex 'line' - name: 'i Option' link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexMatch/#i-option' @@ -47,7 +47,7 @@ tests: # Specify i as part of the Regex type $regexMatch: input: '$description' - regex: !regex ['line', 'i'] + regex: !bson_regex ['line', 'i'] - $addFields: result: @@ -62,7 +62,7 @@ tests: # Mix Regex type with options field $regexMatch: input: '$description' - regex: !regex 'line' + regex: !bson_regex 'line' options: 'i' - name: 'Use $regexMatch to Check Email Address' @@ -75,6 +75,6 @@ tests: if: $regexMatch: input: '$comment' - regex: !regex ['[a-z0-9_.+-]+@mongodb.com', 'i'] + regex: !bson_regex ['[a-z0-9_.+-]+@mongodb.com', 'i'] then: 'Employee' else: 'External' diff --git a/generator/config/expression/split.yaml b/generator/config/expression/split.yaml index 0956a7791..1c6169910 100644 --- a/generator/config/expression/split.yaml +++ b/generator/config/expression/split.yaml @@ -36,7 +36,7 @@ tests: path: '$city_state' - $match: - city_state: !regex '[A-Z]{2}' + city_state: !bson_regex '[A-Z]{2}' - $group: _id: From 731507334995f03803fe3b54714e3b30b5b5109a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 23 Jan 2024 18:42:32 +0100 Subject: [PATCH 44/95] PHPLIB-1356 Add tests on Timestamp Expression Operators (#47) --- generator/config/expression/tsIncrement.yaml | 25 +++++ generator/config/expression/tsSecond.yaml | 19 ++++ tests/Builder/Expression/Pipelines.php | 94 +++++++++++++++++++ .../Expression/TsIncrementOperatorTest.php | 53 +++++++++++ .../Expression/TsSecondOperatorTest.php | 44 +++++++++ 5 files changed, 235 insertions(+) create mode 100644 tests/Builder/Expression/TsIncrementOperatorTest.php create mode 100644 tests/Builder/Expression/TsSecondOperatorTest.php diff --git a/generator/config/expression/tsIncrement.yaml b/generator/config/expression/tsIncrement.yaml index 08d1c5b2c..9fded2143 100644 --- a/generator/config/expression/tsIncrement.yaml +++ b/generator/config/expression/tsIncrement.yaml @@ -12,3 +12,28 @@ arguments: name: expression type: - resolvesToTimestamp +tests: + - + name: 'Obtain the Incrementing Ordinal from a Timestamp Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsIncrement/#obtain-the-incrementing-ordinal-from-a-timestamp-field' + pipeline: + - + $project: + _id: 0 + saleTimestamp: 1 + saleIncrement: + $tsIncrement: '$saleTimestamp' + - + name: 'Use $tsSecond in a Change Stream Cursor to Monitor Collection Changes' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsSecond/#use--tssecond-in-a-change-stream-cursor-to-monitor-collection-changes' + pipeline: + - + $match: + $expr: + $eq: + - + $mod: + - + $tsIncrement: '$clusterTime' + - 2 + - 0 diff --git a/generator/config/expression/tsSecond.yaml b/generator/config/expression/tsSecond.yaml index 066b8bb27..20a84904b 100644 --- a/generator/config/expression/tsSecond.yaml +++ b/generator/config/expression/tsSecond.yaml @@ -12,3 +12,22 @@ arguments: name: expression type: - resolvesToTimestamp +tests: + - + name: 'Obtain the Number of Seconds from a Timestamp Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsSecond/#obtain-the-number-of-seconds-from-a-timestamp-field' + pipeline: + - + $project: + _id: 0 + saleTimestamp: 1 + saleSeconds: + $tsSecond: '$saleTimestamp' + - + name: 'Use $tsSecond in a Change Stream Cursor to Monitor Collection Changes' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsSecond/#use--tssecond-in-a-change-stream-cursor-to-monitor-collection-changes' + pipeline: + - + $addFields: + clusterTimeSeconds: + $tsSecond: '$clusterTime' diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index b08609727..f7eaf0fa0 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -4720,6 +4720,100 @@ enum Pipelines: string ] JSON; + /** + * Obtain the Incrementing Ordinal from a Timestamp Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsIncrement/#obtain-the-incrementing-ordinal-from-a-timestamp-field + */ + case TsIncrementObtainTheIncrementingOrdinalFromATimestampField = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "saleTimestamp": { + "$numberInt": "1" + }, + "saleIncrement": { + "$tsIncrement": "$saleTimestamp" + } + } + } + ] + JSON; + + /** + * Use $tsSecond in a Change Stream Cursor to Monitor Collection Changes + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsSecond/#use--tssecond-in-a-change-stream-cursor-to-monitor-collection-changes + */ + case TsIncrementUseTsSecondInAChangeStreamCursorToMonitorCollectionChanges = <<<'JSON' + [ + { + "$match": { + "$expr": { + "$eq": [ + { + "$mod": [ + { + "$tsIncrement": "$clusterTime" + }, + { + "$numberInt": "2" + } + ] + }, + { + "$numberInt": "0" + } + ] + } + } + } + ] + JSON; + + /** + * Obtain the Number of Seconds from a Timestamp Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsSecond/#obtain-the-number-of-seconds-from-a-timestamp-field + */ + case TsSecondObtainTheNumberOfSecondsFromATimestampField = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "saleTimestamp": { + "$numberInt": "1" + }, + "saleSeconds": { + "$tsSecond": "$saleTimestamp" + } + } + } + ] + JSON; + + /** + * Use $tsSecond in a Change Stream Cursor to Monitor Collection Changes + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/tsSecond/#use--tssecond-in-a-change-stream-cursor-to-monitor-collection-changes + */ + case TsSecondUseTsSecondInAChangeStreamCursorToMonitorCollectionChanges = <<<'JSON' + [ + { + "$addFields": { + "clusterTimeSeconds": { + "$tsSecond": "$clusterTime" + } + } + } + ] + JSON; + /** * Remove Fields that Contain Periods * diff --git a/tests/Builder/Expression/TsIncrementOperatorTest.php b/tests/Builder/Expression/TsIncrementOperatorTest.php new file mode 100644 index 000000000..092f57660 --- /dev/null +++ b/tests/Builder/Expression/TsIncrementOperatorTest.php @@ -0,0 +1,53 @@ +assertSamePipeline(Pipelines::TsIncrementObtainTheIncrementingOrdinalFromATimestampField, $pipeline); + } + + public function testUseTsSecondInAChangeStreamCursorToMonitorCollectionChanges(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::expr( + Expression::eq( + Expression::mod( + Expression::tsIncrement( + Expression::timestampFieldPath('clusterTime'), + ), + 2, + ), + 0, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::TsIncrementUseTsSecondInAChangeStreamCursorToMonitorCollectionChanges, $pipeline); + } +} diff --git a/tests/Builder/Expression/TsSecondOperatorTest.php b/tests/Builder/Expression/TsSecondOperatorTest.php new file mode 100644 index 000000000..16f5b59e6 --- /dev/null +++ b/tests/Builder/Expression/TsSecondOperatorTest.php @@ -0,0 +1,44 @@ +assertSamePipeline(Pipelines::TsSecondObtainTheNumberOfSecondsFromATimestampField, $pipeline); + } + + public function testUseTsSecondInAChangeStreamCursorToMonitorCollectionChanges(): void + { + $pipeline = new Pipeline( + Stage::addFields( + clusterTimeSeconds: Expression::tsSecond( + Expression::timestampFieldPath('clusterTime'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::TsSecondUseTsSecondInAChangeStreamCursorToMonitorCollectionChanges, $pipeline); + } +} From 2ccad18385e2803124f399097f92c85616905d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 23 Jan 2024 22:14:00 +0100 Subject: [PATCH 45/95] PHPLIB-1357 Add tests on Trigonometry Expression Operators (#48) --- generator/config/expression/acos.yaml | 13 + generator/config/expression/acosh.yaml | 10 + generator/config/expression/asin.yaml | 13 + generator/config/expression/asinh.yaml | 10 + generator/config/expression/atan.yaml | 13 + generator/config/expression/atan2.yaml | 12 + generator/config/expression/atanh.yaml | 10 + generator/config/expression/cos.yaml | 13 + generator/config/expression/cosh.yaml | 10 + .../config/expression/degreesToRadians.yaml | 13 + .../config/expression/radiansToDegrees.yaml | 13 + generator/config/expression/sin.yaml | 13 + generator/config/expression/sinh.yaml | 10 + generator/config/expression/tan.yaml | 13 + generator/config/expression/tanh.yaml | 10 + tests/Builder/Expression/AcosOperatorTest.php | 34 ++ .../Builder/Expression/AcoshOperatorTest.php | 33 ++ tests/Builder/Expression/AsinOperatorTest.php | 34 ++ .../Builder/Expression/AsinhOperatorTest.php | 33 ++ .../Builder/Expression/Atan2OperatorTest.php | 32 ++ tests/Builder/Expression/AtanOperatorTest.php | 34 ++ .../Builder/Expression/AtanhOperatorTest.php | 33 ++ tests/Builder/Expression/CosOperatorTest.php | 34 ++ tests/Builder/Expression/CoshOperatorTest.php | 31 ++ .../DegreesToRadiansOperatorTest.php | 35 ++ tests/Builder/Expression/Pipelines.php | 326 ++++++++++++++++++ .../RadiansToDegreesOperatorTest.php | 35 ++ tests/Builder/Expression/SinOperatorTest.php | 34 ++ tests/Builder/Expression/SinhOperatorTest.php | 31 ++ tests/Builder/Expression/TanOperatorTest.php | 34 ++ tests/Builder/Expression/TanhOperatorTest.php | 32 ++ 31 files changed, 1001 insertions(+) create mode 100644 tests/Builder/Expression/AcosOperatorTest.php create mode 100644 tests/Builder/Expression/AcoshOperatorTest.php create mode 100644 tests/Builder/Expression/AsinOperatorTest.php create mode 100644 tests/Builder/Expression/AsinhOperatorTest.php create mode 100644 tests/Builder/Expression/Atan2OperatorTest.php create mode 100644 tests/Builder/Expression/AtanOperatorTest.php create mode 100644 tests/Builder/Expression/AtanhOperatorTest.php create mode 100644 tests/Builder/Expression/CosOperatorTest.php create mode 100644 tests/Builder/Expression/CoshOperatorTest.php create mode 100644 tests/Builder/Expression/DegreesToRadiansOperatorTest.php create mode 100644 tests/Builder/Expression/RadiansToDegreesOperatorTest.php create mode 100644 tests/Builder/Expression/SinOperatorTest.php create mode 100644 tests/Builder/Expression/SinhOperatorTest.php create mode 100644 tests/Builder/Expression/TanOperatorTest.php create mode 100644 tests/Builder/Expression/TanhOperatorTest.php diff --git a/generator/config/expression/acos.yaml b/generator/config/expression/acos.yaml index 07d0ea2d3..7deca736d 100644 --- a/generator/config/expression/acos.yaml +++ b/generator/config/expression/acos.yaml @@ -16,3 +16,16 @@ arguments: $acos takes any valid expression that resolves to a number between -1 and 1, e.g. -1 <= value <= 1. $acos returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. By default $acos returns values as a double. $acos can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/acos/#example' + pipeline: + - + $addFields: + angle_a: + $radiansToDegrees: + $acos: + $divide: + - '$side_b' + - '$hypotenuse' diff --git a/generator/config/expression/acosh.yaml b/generator/config/expression/acosh.yaml index 8e516df0b..ce575e317 100644 --- a/generator/config/expression/acosh.yaml +++ b/generator/config/expression/acosh.yaml @@ -16,3 +16,13 @@ arguments: $acosh takes any valid expression that resolves to a number between 1 and +Infinity, e.g. 1 <= value <= +Infinity. $acosh returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. By default $acosh returns values as a double. $acosh can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/acosh/#example' + pipeline: + - + $addFields: + y-coordinate: + $radiansToDegrees: + $acosh: '$x-coordinate' diff --git a/generator/config/expression/asin.yaml b/generator/config/expression/asin.yaml index 169ed50f6..43e2832a2 100644 --- a/generator/config/expression/asin.yaml +++ b/generator/config/expression/asin.yaml @@ -16,3 +16,16 @@ arguments: $asin takes any valid expression that resolves to a number between -1 and 1, e.g. -1 <= value <= 1. $asin returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. By default $asin returns values as a double. $asin can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/asin/#example' + pipeline: + - + $addFields: + angle_a: + $radiansToDegrees: + $asin: + $divide: + - '$side_a' + - '$hypotenuse' diff --git a/generator/config/expression/asinh.yaml b/generator/config/expression/asinh.yaml index f85049f9b..6d45c14fa 100644 --- a/generator/config/expression/asinh.yaml +++ b/generator/config/expression/asinh.yaml @@ -16,3 +16,13 @@ arguments: $asinh takes any valid expression that resolves to a number. $asinh returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. By default $asinh returns values as a double. $asinh can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/asinh/#example' + pipeline: + - + $addFields: + y-coordinate: + $radiansToDegrees: + $asinh: '$x-coordinate' diff --git a/generator/config/expression/atan.yaml b/generator/config/expression/atan.yaml index 9f48641db..a8bb1674f 100644 --- a/generator/config/expression/atan.yaml +++ b/generator/config/expression/atan.yaml @@ -16,3 +16,16 @@ arguments: $atan takes any valid expression that resolves to a number. $atan returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. By default $atan returns values as a double. $atan can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/atan/#example' + pipeline: + - + $addFields: + angle_a: + $radiansToDegrees: + $atan: + $divide: + - '$side_b' + - '$side_a' diff --git a/generator/config/expression/atan2.yaml b/generator/config/expression/atan2.yaml index f4312f8de..1abc55e6a 100644 --- a/generator/config/expression/atan2.yaml +++ b/generator/config/expression/atan2.yaml @@ -20,3 +20,15 @@ arguments: name: x type: - resolvesToNumber +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/atan2/#example' + pipeline: + - + $addFields: + angle_a: + $radiansToDegrees: + $atan2: + - '$side_b' + - '$side_a' diff --git a/generator/config/expression/atanh.yaml b/generator/config/expression/atanh.yaml index 41a4901d5..501fba2bf 100644 --- a/generator/config/expression/atanh.yaml +++ b/generator/config/expression/atanh.yaml @@ -16,3 +16,13 @@ arguments: $atanh takes any valid expression that resolves to a number between -1 and 1, e.g. -1 <= value <= 1. $atanh returns values in radians. Use $radiansToDegrees operator to convert the output value from radians to degrees. By default $atanh returns values as a double. $atanh can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/atanh/#example' + pipeline: + - + $addFields: + y-coordinate: + $radiansToDegrees: + $atanh: '$x-coordinate' diff --git a/generator/config/expression/cos.yaml b/generator/config/expression/cos.yaml index e9aa9bdd6..0b47670cf 100644 --- a/generator/config/expression/cos.yaml +++ b/generator/config/expression/cos.yaml @@ -15,3 +15,16 @@ arguments: description: | $cos takes any valid expression that resolves to a number. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the result to radians. By default $cos returns values as a double. $cos can also return values as a 128-bit decimal as long as the resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/cos/#example' + pipeline: + - + $addFields: + side_a: + $multiply: + - + $cos: + $degreesToRadians: '$angle_a' + - '$hypotenuse' diff --git a/generator/config/expression/cosh.yaml b/generator/config/expression/cosh.yaml index a7963ab29..419fa8caa 100644 --- a/generator/config/expression/cosh.yaml +++ b/generator/config/expression/cosh.yaml @@ -15,3 +15,13 @@ arguments: description: | $cosh takes any valid expression that resolves to a number, measured in radians. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the value to radians. By default $cosh returns values as a double. $cosh can also return values as a 128-bit decimal if the resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/cosh/#example' + pipeline: + - + $addFields: + cosh_output: + $cosh: + $degreesToRadians: '$angle' diff --git a/generator/config/expression/degreesToRadians.yaml b/generator/config/expression/degreesToRadians.yaml index 50e8a5586..59c18d2e3 100644 --- a/generator/config/expression/degreesToRadians.yaml +++ b/generator/config/expression/degreesToRadians.yaml @@ -15,3 +15,16 @@ arguments: description: | $degreesToRadians takes any valid expression that resolves to a number. By default $degreesToRadians returns values as a double. $degreesToRadians can also return values as a 128-bit decimal as long as the resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/degreesToRadians/#example' + pipeline: + - + $addFields: + angle_a_rad: + $degreesToRadians: '$angle_a' + angle_b_rad: + $degreesToRadians: '$angle_b' + angle_c_rad: + $degreesToRadians: '$angle_c' diff --git a/generator/config/expression/radiansToDegrees.yaml b/generator/config/expression/radiansToDegrees.yaml index e8f8d3794..be4dd1e1d 100644 --- a/generator/config/expression/radiansToDegrees.yaml +++ b/generator/config/expression/radiansToDegrees.yaml @@ -12,3 +12,16 @@ arguments: name: expression type: - resolvesToNumber +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/radiansToDegrees/#example' + pipeline: + - + $addFields: + angle_a_deg: + $radiansToDegrees: '$angle_a' + angle_b_deg: + $radiansToDegrees: '$angle_b' + angle_c_deg: + $radiansToDegrees: '$angle_c' diff --git a/generator/config/expression/sin.yaml b/generator/config/expression/sin.yaml index 2eb5b3c72..fe02b4f28 100644 --- a/generator/config/expression/sin.yaml +++ b/generator/config/expression/sin.yaml @@ -15,3 +15,16 @@ arguments: description: | $sin takes any valid expression that resolves to a number. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the result to radians. By default $sin returns values as a double. $sin can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sin/#example' + pipeline: + - + $addFields: + side_b: + $multiply: + - + $sin: + $degreesToRadians: '$angle_a' + - '$hypotenuse' diff --git a/generator/config/expression/sinh.yaml b/generator/config/expression/sinh.yaml index a3e779d58..a5b446add 100644 --- a/generator/config/expression/sinh.yaml +++ b/generator/config/expression/sinh.yaml @@ -15,3 +15,13 @@ arguments: description: | $sinh takes any valid expression that resolves to a number, measured in radians. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the value to radians. By default $sinh returns values as a double. $sinh can also return values as a 128-bit decimal if the expression resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sinh/#example' + pipeline: + - + $addFields: + sinh_output: + $sinh: + $degreesToRadians: '$angle' diff --git a/generator/config/expression/tan.yaml b/generator/config/expression/tan.yaml index f82484155..17b11ee63 100644 --- a/generator/config/expression/tan.yaml +++ b/generator/config/expression/tan.yaml @@ -15,3 +15,16 @@ arguments: description: | $tan takes any valid expression that resolves to a number. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the result to radians. By default $tan returns values as a double. $tan can also return values as a 128-bit decimal as long as the expression resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/tan/#example' + pipeline: + - + $addFields: + side_b: + $multiply: + - + $tan: + $degreesToRadians: '$angle_a' + - '$side_a' diff --git a/generator/config/expression/tanh.yaml b/generator/config/expression/tanh.yaml index 1ce471cf4..364589452 100644 --- a/generator/config/expression/tanh.yaml +++ b/generator/config/expression/tanh.yaml @@ -15,3 +15,13 @@ arguments: description: | $tanh takes any valid expression that resolves to a number, measured in radians. If the expression returns a value in degrees, use the $degreesToRadians operator to convert the value to radians. By default $tanh returns values as a double. $tanh can also return values as a 128-bit decimal if the expression resolves to a 128-bit decimal value. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/tanh/#example' + pipeline: + - + $addFields: + tanh_output: + $tanh: + $degreesToRadians: '$angle' diff --git a/tests/Builder/Expression/AcosOperatorTest.php b/tests/Builder/Expression/AcosOperatorTest.php new file mode 100644 index 000000000..21b4f4ff0 --- /dev/null +++ b/tests/Builder/Expression/AcosOperatorTest.php @@ -0,0 +1,34 @@ +assertSamePipeline(Pipelines::AcosExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/AcoshOperatorTest.php b/tests/Builder/Expression/AcoshOperatorTest.php new file mode 100644 index 000000000..4ba41cf59 --- /dev/null +++ b/tests/Builder/Expression/AcoshOperatorTest.php @@ -0,0 +1,33 @@ + Expression::radiansToDegrees( + Expression::acosh( + Expression::numberFieldPath('x-coordinate'), + ), + ), + ], + ), + ); + + $this->assertSamePipeline(Pipelines::AcoshExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/AsinOperatorTest.php b/tests/Builder/Expression/AsinOperatorTest.php new file mode 100644 index 000000000..c74dc75c7 --- /dev/null +++ b/tests/Builder/Expression/AsinOperatorTest.php @@ -0,0 +1,34 @@ +assertSamePipeline(Pipelines::AsinExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/AsinhOperatorTest.php b/tests/Builder/Expression/AsinhOperatorTest.php new file mode 100644 index 000000000..138030e54 --- /dev/null +++ b/tests/Builder/Expression/AsinhOperatorTest.php @@ -0,0 +1,33 @@ + Expression::radiansToDegrees( + Expression::asinh( + Expression::numberFieldPath('x-coordinate'), + ), + ), + ], + ), + ); + + $this->assertSamePipeline(Pipelines::AsinhExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Atan2OperatorTest.php b/tests/Builder/Expression/Atan2OperatorTest.php new file mode 100644 index 000000000..442e0150f --- /dev/null +++ b/tests/Builder/Expression/Atan2OperatorTest.php @@ -0,0 +1,32 @@ +assertSamePipeline(Pipelines::Atan2Example, $pipeline); + } +} diff --git a/tests/Builder/Expression/AtanOperatorTest.php b/tests/Builder/Expression/AtanOperatorTest.php new file mode 100644 index 000000000..3040823b1 --- /dev/null +++ b/tests/Builder/Expression/AtanOperatorTest.php @@ -0,0 +1,34 @@ +assertSamePipeline(Pipelines::AtanExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/AtanhOperatorTest.php b/tests/Builder/Expression/AtanhOperatorTest.php new file mode 100644 index 000000000..3194a8e94 --- /dev/null +++ b/tests/Builder/Expression/AtanhOperatorTest.php @@ -0,0 +1,33 @@ + Expression::radiansToDegrees( + Expression::atanh( + Expression::numberFieldPath('x-coordinate'), + ), + ), + ], + ), + ); + + $this->assertSamePipeline(Pipelines::AtanhExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/CosOperatorTest.php b/tests/Builder/Expression/CosOperatorTest.php new file mode 100644 index 000000000..0124b88c1 --- /dev/null +++ b/tests/Builder/Expression/CosOperatorTest.php @@ -0,0 +1,34 @@ +assertSamePipeline(Pipelines::CosExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/CoshOperatorTest.php b/tests/Builder/Expression/CoshOperatorTest.php new file mode 100644 index 000000000..37ec83724 --- /dev/null +++ b/tests/Builder/Expression/CoshOperatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::CoshExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/DegreesToRadiansOperatorTest.php b/tests/Builder/Expression/DegreesToRadiansOperatorTest.php new file mode 100644 index 000000000..82e9797a2 --- /dev/null +++ b/tests/Builder/Expression/DegreesToRadiansOperatorTest.php @@ -0,0 +1,35 @@ +assertSamePipeline(Pipelines::DegreesToRadiansExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index f7eaf0fa0..e41135c9d 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -10,6 +10,49 @@ enum Pipelines: string { + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/acos/#example + */ + case AcosExample = <<<'JSON' + [ + { + "$addFields": { + "angle_a": { + "$radiansToDegrees": { + "$acos": { + "$divide": [ + "$side_b", + "$hypotenuse" + ] + } + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/acosh/#example + */ + case AcoshExample = <<<'JSON' + [ + { + "$addFields": { + "y-coordinate": { + "$radiansToDegrees": { + "$acosh": "$x-coordinate" + } + } + } + } + ] + JSON; + /** * Add Numbers * @@ -248,6 +291,114 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/asin/#example + */ + case AsinExample = <<<'JSON' + [ + { + "$addFields": { + "angle_a": { + "$radiansToDegrees": { + "$asin": { + "$divide": [ + "$side_a", + "$hypotenuse" + ] + } + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/asinh/#example + */ + case AsinhExample = <<<'JSON' + [ + { + "$addFields": { + "y-coordinate": { + "$radiansToDegrees": { + "$asinh": "$x-coordinate" + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/atan/#example + */ + case AtanExample = <<<'JSON' + [ + { + "$addFields": { + "angle_a": { + "$radiansToDegrees": { + "$atan": { + "$divide": [ + "$side_b", + "$side_a" + ] + } + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/atan2/#example + */ + case Atan2Example = <<<'JSON' + [ + { + "$addFields": { + "angle_a": { + "$radiansToDegrees": { + "$atan2": [ + "$side_b", + "$side_a" + ] + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/atanh/#example + */ + case AtanhExample = <<<'JSON' + [ + { + "$addFields": { + "y-coordinate": { + "$radiansToDegrees": { + "$atanh": "$x-coordinate" + } + } + } + } + ] + JSON; + /** * Use in $project Stage * @@ -594,6 +745,49 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/cos/#example + */ + case CosExample = <<<'JSON' + [ + { + "$addFields": { + "side_a": { + "$multiply": [ + { + "$cos": { + "$degreesToRadians": "$angle_a" + } + }, + "$hypotenuse" + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/cosh/#example + */ + case CoshExample = <<<'JSON' + [ + { + "$addFields": { + "cosh_output": { + "$cosh": { + "$degreesToRadians": "$angle" + } + } + } + } + ] + JSON; + /** * Add a Future Date * @@ -1413,6 +1607,29 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/degreesToRadians/#example + */ + case DegreesToRadiansExample = <<<'JSON' + [ + { + "$addFields": { + "angle_a_rad": { + "$degreesToRadians": "$angle_a" + }, + "angle_b_rad": { + "$degreesToRadians": "$angle_b" + }, + "angle_c_rad": { + "$degreesToRadians": "$angle_c" + } + } + } + ] + JSON; + /** * Example * @@ -2770,6 +2987,29 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/radiansToDegrees/#example + */ + case RadiansToDegreesExample = <<<'JSON' + [ + { + "$addFields": { + "angle_a_deg": { + "$radiansToDegrees": "$angle_a" + }, + "angle_b_deg": { + "$radiansToDegrees": "$angle_b" + }, + "angle_c_deg": { + "$radiansToDegrees": "$angle_c" + } + } + } + ] + JSON; + /** * Generate Random Data Points * @@ -3826,6 +4066,49 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sin/#example + */ + case SinExample = <<<'JSON' + [ + { + "$addFields": { + "side_b": { + "$multiply": [ + { + "$sin": { + "$degreesToRadians": "$angle_a" + } + }, + "$hypotenuse" + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sinh/#example + */ + case SinhExample = <<<'JSON' + [ + { + "$addFields": { + "sinh_output": { + "$sinh": { + "$degreesToRadians": "$angle" + } + } + } + } + ] + JSON; + /** * Example * @@ -4586,6 +4869,49 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/tan/#example + */ + case TanExample = <<<'JSON' + [ + { + "$addFields": { + "side_b": { + "$multiply": [ + { + "$tan": { + "$degreesToRadians": "$angle_a" + } + }, + "$side_a" + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/tanh/#example + */ + case TanhExample = <<<'JSON' + [ + { + "$addFields": { + "tanh_output": { + "$tanh": { + "$degreesToRadians": "$angle" + } + } + } + } + ] + JSON; + /** * Example * diff --git a/tests/Builder/Expression/RadiansToDegreesOperatorTest.php b/tests/Builder/Expression/RadiansToDegreesOperatorTest.php new file mode 100644 index 000000000..b52375936 --- /dev/null +++ b/tests/Builder/Expression/RadiansToDegreesOperatorTest.php @@ -0,0 +1,35 @@ +assertSamePipeline(Pipelines::RadiansToDegreesExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SinOperatorTest.php b/tests/Builder/Expression/SinOperatorTest.php new file mode 100644 index 000000000..5a585f3c9 --- /dev/null +++ b/tests/Builder/Expression/SinOperatorTest.php @@ -0,0 +1,34 @@ +assertSamePipeline(Pipelines::SinExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SinhOperatorTest.php b/tests/Builder/Expression/SinhOperatorTest.php new file mode 100644 index 000000000..5ba2241f3 --- /dev/null +++ b/tests/Builder/Expression/SinhOperatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::SinhExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/TanOperatorTest.php b/tests/Builder/Expression/TanOperatorTest.php new file mode 100644 index 000000000..34b210d0a --- /dev/null +++ b/tests/Builder/Expression/TanOperatorTest.php @@ -0,0 +1,34 @@ +assertSamePipeline(Pipelines::TanExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/TanhOperatorTest.php b/tests/Builder/Expression/TanhOperatorTest.php new file mode 100644 index 000000000..4ae799e55 --- /dev/null +++ b/tests/Builder/Expression/TanhOperatorTest.php @@ -0,0 +1,32 @@ +assertSamePipeline(Pipelines::TanhExample, $pipeline); + } +} From ff4fc7962887d3709ebe2fcf27d1d9e518ada7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 24 Jan 2024 10:19:28 +0100 Subject: [PATCH 46/95] PHPLIB-1358 Add tests on Type Expression Operators (#49) --- generator/config/expression/convert.yaml | 47 +++ generator/config/expression/isNumber.yaml | 61 ++- generator/config/expression/toBool.yaml | 27 ++ generator/config/expression/toDecimal.yaml | 9 + generator/config/expression/toDouble.yaml | 13 + generator/config/expression/toInt.yaml | 9 + generator/config/expression/toLong.yaml | 12 + generator/config/expression/toObjectId.yaml | 12 + generator/config/expression/type.yaml | 9 + generator/js2yaml.html | 12 + src/Builder/Expression/FactoryTrait.php | 7 +- src/Builder/Expression/IsNumberOperator.php | 20 +- .../Expression/ConvertOperatorTest.php | 75 ++++ .../Expression/IsNumberOperatorTest.php | 96 +++++ tests/Builder/Expression/Pipelines.php | 363 ++++++++++++++++++ .../Builder/Expression/ToBoolOperatorTest.php | 52 +++ .../Expression/ToDecimalOperatorTest.php | 29 ++ .../Expression/ToDoubleOperatorTest.php | 33 ++ .../Builder/Expression/ToIntOperatorTest.php | 29 ++ .../Builder/Expression/ToLongOperatorTest.php | 36 ++ .../Expression/ToObjectIdOperatorTest.php | 36 ++ tests/Builder/Expression/TypeOperatorTest.php | 29 ++ 22 files changed, 995 insertions(+), 21 deletions(-) create mode 100644 tests/Builder/Expression/ConvertOperatorTest.php create mode 100644 tests/Builder/Expression/IsNumberOperatorTest.php create mode 100644 tests/Builder/Expression/ToBoolOperatorTest.php create mode 100644 tests/Builder/Expression/ToDecimalOperatorTest.php create mode 100644 tests/Builder/Expression/ToDoubleOperatorTest.php create mode 100644 tests/Builder/Expression/ToIntOperatorTest.php create mode 100644 tests/Builder/Expression/ToLongOperatorTest.php create mode 100644 tests/Builder/Expression/ToObjectIdOperatorTest.php create mode 100644 tests/Builder/Expression/TypeOperatorTest.php diff --git a/generator/config/expression/convert.yaml b/generator/config/expression/convert.yaml index 36475be8c..a76311ed5 100644 --- a/generator/config/expression/convert.yaml +++ b/generator/config/expression/convert.yaml @@ -33,3 +33,50 @@ arguments: description: | The value to return if the input is null or missing. The arguments can be any valid expression. If unspecified, $convert returns null if the input is null or missing. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/convert/#example' + pipeline: + - + $addFields: + convertedPrice: + $convert: + input: '$price' + to: 'decimal' + onError: 'Error' + onNull: !bson_decimal128 '0' + convertedQty: + $convert: + input: '$qty' + to: 'int' + onError: + $concat: + - 'Could not convert ' + - + $toString: '$qty' + - ' to type integer.' + onNull: 0 + - + $project: + totalPrice: + $switch: + branches: + - + case: + $eq: + - + $type: '$convertedPrice' + - 'string' + then: 'NaN' + - + case: + $eq: + - + $type: '$convertedQty' + - 'string' + then: 'NaN' + default: + $multiply: + - '$convertedPrice' + - '$convertedQty' diff --git a/generator/config/expression/isNumber.yaml b/generator/config/expression/isNumber.yaml index 4bce308a9..3bce99e99 100644 --- a/generator/config/expression/isNumber.yaml +++ b/generator/config/expression/isNumber.yaml @@ -13,4 +13,63 @@ arguments: name: expression type: - expression - variadic: array +tests: + - + name: 'Use $isNumber to Check if a Field is Numeric' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/#use--isnumber-to-check-if-a-field-is-numeric' + pipeline: + - + $addFields: + isNumber: + $isNumber: '$reading' + hasType: + $type: '$reading' + - + name: 'Conditionally Modify Fields using $isNumber' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/#conditionally-modify-fields-using--isnumber' + pipeline: + - + $addFields: + points: + $cond: + if: + $isNumber: '$grade' + then: '$grade' + else: + $switch: + branches: + - + case: + $eq: + - '$grade' + - 'A' + then: 4 + - + case: + $eq: + - '$grade' + - 'B' + then: 3 + - + case: + $eq: + - '$grade' + - 'C' + then: 2 + - + case: + $eq: + - '$grade' + - 'D' + then: 1 + - + case: + $eq: + - '$grade' + - 'F' + then: 0 + - + $group: + _id: '$student_id' + GPA: + $avg: '$points' diff --git a/generator/config/expression/toBool.yaml b/generator/config/expression/toBool.yaml index eaf8ca210..7f771ec8d 100644 --- a/generator/config/expression/toBool.yaml +++ b/generator/config/expression/toBool.yaml @@ -12,3 +12,30 @@ arguments: name: expression type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toBool/#example' + pipeline: + - + $addFields: + convertedShippedFlag: + $switch: + branches: + - + case: + $eq: + - '$shipped' + - 'false' + then: false + - + case: + $eq: + - '$shipped' + - '' + then: false + default: + $toBool: '$shipped' + - + $match: + convertedShippedFlag: false diff --git a/generator/config/expression/toDecimal.yaml b/generator/config/expression/toDecimal.yaml index 59db31a53..2f3588323 100644 --- a/generator/config/expression/toDecimal.yaml +++ b/generator/config/expression/toDecimal.yaml @@ -12,3 +12,12 @@ arguments: name: expression type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDecimal/#example' + pipeline: + - + $addFields: + convertedPrice: + $toDecimal: '$price' diff --git a/generator/config/expression/toDouble.yaml b/generator/config/expression/toDouble.yaml index 6fc87f6cd..f34c36e9a 100644 --- a/generator/config/expression/toDouble.yaml +++ b/generator/config/expression/toDouble.yaml @@ -12,3 +12,16 @@ arguments: name: expression type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDouble/#example' + pipeline: + - + $addFields: + degrees: + $toDouble: + $substrBytes: + - '$temp' + - 0 + - 4 diff --git a/generator/config/expression/toInt.yaml b/generator/config/expression/toInt.yaml index cf343643a..2b0239955 100644 --- a/generator/config/expression/toInt.yaml +++ b/generator/config/expression/toInt.yaml @@ -12,3 +12,12 @@ arguments: name: expression type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toInt/#example' + pipeline: + - + $addFields: + convertedQty: + $toInt: '$qty' diff --git a/generator/config/expression/toLong.yaml b/generator/config/expression/toLong.yaml index 9687e4791..3168ad9ff 100644 --- a/generator/config/expression/toLong.yaml +++ b/generator/config/expression/toLong.yaml @@ -12,3 +12,15 @@ arguments: name: expression type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLong/#example' + pipeline: + - + $addFields: + convertedQty: + $toLong: '$qty' + - + $sort: + convertedQty: -1 diff --git a/generator/config/expression/toObjectId.yaml b/generator/config/expression/toObjectId.yaml index e78ee28e1..803f7cafa 100644 --- a/generator/config/expression/toObjectId.yaml +++ b/generator/config/expression/toObjectId.yaml @@ -12,3 +12,15 @@ arguments: name: expression type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toObjectId/#example' + pipeline: + - + $addFields: + convertedId: + $toObjectId: '$_id' + - + $sort: + convertedId: -1 diff --git a/generator/config/expression/type.yaml b/generator/config/expression/type.yaml index f9aa73f39..c1f63db79 100644 --- a/generator/config/expression/type.yaml +++ b/generator/config/expression/type.yaml @@ -11,3 +11,12 @@ arguments: name: expression type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/type/#example' + pipeline: + - + $project: + a: + $type: '$a' diff --git a/generator/js2yaml.html b/generator/js2yaml.html index 8e11abbea..89e0dd594 100644 --- a/generator/js2yaml.html +++ b/generator/js2yaml.html @@ -74,6 +74,18 @@

Convert JS examples into Yaml

return new TaggedValue('bson_binary', value); } + function Decimal128(value) { + return new TaggedValue('bson_decimal128', value) + } + + function Int32(value) { + return parseInt(value); + } + + function Int64(value) { + return new TaggedValue('bson_int64', value) + } + function convert(jsString) { try { return toYaml(eval(jsString), 1); diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 2c16a6a9c..cadcf016a 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -970,14 +970,13 @@ public static function isArray( * New in MongoDB 4.4. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/ - * @no-named-arguments - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ public static function isNumber( - Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, ): IsNumberOperator { - return new IsNumberOperator(...$expression); + return new IsNumberOperator($expression); } /** diff --git a/src/Builder/Expression/IsNumberOperator.php b/src/Builder/Expression/IsNumberOperator.php index 2eb1e1e2e..8ccc977a4 100644 --- a/src/Builder/Expression/IsNumberOperator.php +++ b/src/Builder/Expression/IsNumberOperator.php @@ -12,11 +12,8 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\OperatorInterface; -use MongoDB\Exception\InvalidArgumentException; use stdClass; -use function array_is_list; - /** * Returns boolean true if the specified expression resolves to an integer, decimal, double, or long. * Returns boolean false if the expression resolves to any other BSON type, null, or a missing field. @@ -28,23 +25,14 @@ class IsNumberOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ - public readonly array $expression; + /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression - * @no-named-arguments + * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ - public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) + public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { - if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); - } - - if (! array_is_list($expression)) { - throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported'); - } - $this->expression = $expression; } diff --git a/tests/Builder/Expression/ConvertOperatorTest.php b/tests/Builder/Expression/ConvertOperatorTest.php new file mode 100644 index 000000000..f284889d1 --- /dev/null +++ b/tests/Builder/Expression/ConvertOperatorTest.php @@ -0,0 +1,75 @@ +assertSamePipeline(Pipelines::ConvertExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/IsNumberOperatorTest.php b/tests/Builder/Expression/IsNumberOperatorTest.php new file mode 100644 index 000000000..2fb81a174 --- /dev/null +++ b/tests/Builder/Expression/IsNumberOperatorTest.php @@ -0,0 +1,96 @@ +assertSamePipeline(Pipelines::IsNumberConditionallyModifyFieldsUsingIsNumber, $pipeline); + } + + public function testUseIsNumberToCheckIfAFieldIsNumeric(): void + { + $pipeline = new Pipeline( + Stage::addFields( + isNumber: Expression::isNumber( + Expression::fieldPath('reading'), + ), + hasType: Expression::type( + Expression::fieldPath('reading'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::IsNumberUseIsNumberToCheckIfAFieldIsNumeric, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index e41135c9d..37fe784f0 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -745,6 +745,86 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/convert/#example + */ + case ConvertExample = <<<'JSON' + [ + { + "$addFields": { + "convertedPrice": { + "$convert": { + "input": "$price", + "to": "decimal", + "onError": "Error", + "onNull": { + "$numberDecimal": "0" + } + } + }, + "convertedQty": { + "$convert": { + "input": "$qty", + "to": "int", + "onError": { + "$concat": [ + "Could not convert ", + { + "$toString": "$qty" + }, + " to type integer." + ] + }, + "onNull": { + "$numberInt": "0" + } + } + } + } + }, + { + "$project": { + "totalPrice": { + "$switch": { + "branches": [ + { + "case": { + "$eq": [ + { + "$type": "$convertedPrice" + }, + "string" + ] + }, + "then": "NaN" + }, + { + "case": { + "$eq": [ + { + "$type": "$convertedQty" + }, + "string" + ] + }, + "then": "NaN" + } + ], + "default": { + "$multiply": [ + "$convertedPrice", + "$convertedQty" + ] + } + } + } + } + } + ] + JSON; + /** * Example * @@ -2254,6 +2334,117 @@ enum Pipelines: string ] JSON; + /** + * Use $isNumber to Check if a Field is Numeric + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/#use--isnumber-to-check-if-a-field-is-numeric + */ + case IsNumberUseIsNumberToCheckIfAFieldIsNumeric = <<<'JSON' + [ + { + "$addFields": { + "isNumber": { + "$isNumber": "$reading" + }, + "hasType": { + "$type": "$reading" + } + } + } + ] + JSON; + + /** + * Conditionally Modify Fields using $isNumber + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/#conditionally-modify-fields-using--isnumber + */ + case IsNumberConditionallyModifyFieldsUsingIsNumber = <<<'JSON' + [ + { + "$addFields": { + "points": { + "$cond": { + "if": { + "$isNumber": "$grade" + }, + "then": "$grade", + "else": { + "$switch": { + "branches": [ + { + "case": { + "$eq": [ + "$grade", + "A" + ] + }, + "then": { + "$numberInt": "4" + } + }, + { + "case": { + "$eq": [ + "$grade", + "B" + ] + }, + "then": { + "$numberInt": "3" + } + }, + { + "case": { + "$eq": [ + "$grade", + "C" + ] + }, + "then": { + "$numberInt": "2" + } + }, + { + "case": { + "$eq": [ + "$grade", + "D" + ] + }, + "then": { + "$numberInt": "1" + } + }, + { + "case": { + "$eq": [ + "$grade", + "F" + ] + }, + "then": { + "$numberInt": "0" + } + } + ] + } + } + } + } + } + }, + { + "$group": { + "_id": "$student_id", + "GPA": { + "$avg": "$points" + } + } + } + ] + JSON; + /** * Example * @@ -4912,6 +5103,52 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toBool/#example + */ + case ToBoolExample = <<<'JSON' + [ + { + "$addFields": { + "convertedShippedFlag": { + "$switch": { + "branches": [ + { + "case": { + "$eq": [ + "$shipped", + "false" + ] + }, + "then": false + }, + { + "case": { + "$eq": [ + "$shipped", + "" + ] + }, + "then": false + } + ], + "default": { + "$toBool": "$shipped" + } + } + } + } + }, + { + "$match": { + "convertedShippedFlag": false + } + } + ] + JSON; + /** * Example * @@ -4936,6 +5173,50 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDecimal/#example + */ + case ToDecimalExample = <<<'JSON' + [ + { + "$addFields": { + "convertedPrice": { + "$toDecimal": "$price" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDouble/#example + */ + case ToDoubleExample = <<<'JSON' + [ + { + "$addFields": { + "degrees": { + "$toDouble": { + "$substrBytes": [ + "$temp", + { + "$numberInt": "0" + }, + { + "$numberInt": "4" + } + ] + } + } + } + } + ] + JSON; + /** * Example * @@ -4960,6 +5241,47 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toInt/#example + */ + case ToIntExample = <<<'JSON' + [ + { + "$addFields": { + "convertedQty": { + "$toInt": "$qty" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLong/#example + */ + case ToLongExample = <<<'JSON' + [ + { + "$addFields": { + "convertedQty": { + "$toLong": "$qty" + } + } + }, + { + "$sort": { + "convertedQty": { + "$numberInt": "-1" + } + } + } + ] + JSON; + /** * Example * @@ -4980,6 +5302,30 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toObjectId/#example + */ + case ToObjectIdExample = <<<'JSON' + [ + { + "$addFields": { + "convertedId": { + "$toObjectId": "$_id" + } + } + }, + { + "$sort": { + "convertedId": { + "$numberInt": "-1" + } + } + } + ] + JSON; + /** * Example * @@ -5140,6 +5486,23 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/type/#example + */ + case TypeExample = <<<'JSON' + [ + { + "$project": { + "a": { + "$type": "$a" + } + } + } + ] + JSON; + /** * Remove Fields that Contain Periods * diff --git a/tests/Builder/Expression/ToBoolOperatorTest.php b/tests/Builder/Expression/ToBoolOperatorTest.php new file mode 100644 index 000000000..6d6b85517 --- /dev/null +++ b/tests/Builder/Expression/ToBoolOperatorTest.php @@ -0,0 +1,52 @@ +assertSamePipeline(Pipelines::ToBoolExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ToDecimalOperatorTest.php b/tests/Builder/Expression/ToDecimalOperatorTest.php new file mode 100644 index 000000000..d12a72755 --- /dev/null +++ b/tests/Builder/Expression/ToDecimalOperatorTest.php @@ -0,0 +1,29 @@ +assertSamePipeline(Pipelines::ToDecimalExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ToDoubleOperatorTest.php b/tests/Builder/Expression/ToDoubleOperatorTest.php new file mode 100644 index 000000000..d0819e79d --- /dev/null +++ b/tests/Builder/Expression/ToDoubleOperatorTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::ToDoubleExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ToIntOperatorTest.php b/tests/Builder/Expression/ToIntOperatorTest.php new file mode 100644 index 000000000..cc88ca63d --- /dev/null +++ b/tests/Builder/Expression/ToIntOperatorTest.php @@ -0,0 +1,29 @@ +assertSamePipeline(Pipelines::ToIntExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ToLongOperatorTest.php b/tests/Builder/Expression/ToLongOperatorTest.php new file mode 100644 index 000000000..c21fef612 --- /dev/null +++ b/tests/Builder/Expression/ToLongOperatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::ToLongExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ToObjectIdOperatorTest.php b/tests/Builder/Expression/ToObjectIdOperatorTest.php new file mode 100644 index 000000000..856c76575 --- /dev/null +++ b/tests/Builder/Expression/ToObjectIdOperatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::ToObjectIdExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/TypeOperatorTest.php b/tests/Builder/Expression/TypeOperatorTest.php new file mode 100644 index 000000000..797536c27 --- /dev/null +++ b/tests/Builder/Expression/TypeOperatorTest.php @@ -0,0 +1,29 @@ +assertSamePipeline(Pipelines::TypeExample, $pipeline); + } +} From b4e2b1372f955b1443a1d34fa3c1760339558719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 25 Jan 2024 10:57:14 +0100 Subject: [PATCH 47/95] PHPLIB-1361 Add test on $let expression operator (#55) --- generator/config/expression/let.yaml | 23 ++++++++++ tests/Builder/Expression/LetOperatorTest.php | 45 ++++++++++++++++++++ tests/Builder/Expression/Pipelines.php | 43 +++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 tests/Builder/Expression/LetOperatorTest.php diff --git a/generator/config/expression/let.yaml b/generator/config/expression/let.yaml index 7b2228a62..7d3017282 100644 --- a/generator/config/expression/let.yaml +++ b/generator/config/expression/let.yaml @@ -21,3 +21,26 @@ arguments: - expression description: | The expression to evaluate. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/let/#example' + pipeline: + - + $project: + finalTotal: + $let: + vars: + total: + $add: + - '$price' + - '$tax' + discounted: + $cond: + if: '$applyDiscount' + then: 0.9 + else: 1 + in: + $multiply: + - '$$total' + - '$$discounted' diff --git a/tests/Builder/Expression/LetOperatorTest.php b/tests/Builder/Expression/LetOperatorTest.php new file mode 100644 index 000000000..602a69adf --- /dev/null +++ b/tests/Builder/Expression/LetOperatorTest.php @@ -0,0 +1,45 @@ +assertSamePipeline(Pipelines::LetExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 37fe784f0..d5b78ce6d 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -2587,6 +2587,49 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/let/#example + */ + case LetExample = <<<'JSON' + [ + { + "$project": { + "finalTotal": { + "$let": { + "vars": { + "total": { + "$add": [ + "$price", + "$tax" + ] + }, + "discounted": { + "$cond": { + "if": "$applyDiscount", + "then": { + "$numberDouble": "0.9000000000000000222" + }, + "else": { + "$numberInt": "1" + } + } + } + }, + "in": { + "$multiply": [ + "$$total", + "$$discounted" + ] + } + } + } + } + } + ] + JSON; + /** * Example * From 5951e36bb46ad58e8ff837116b52549e0871d3fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 26 Jan 2024 07:41:45 +0100 Subject: [PATCH 48/95] Allow empty strings, let the server return an error (#50) --- generator/src/OperatorGenerator.php | 5 - .../Accumulator/AccumulatorAccumulator.php | 20 +- .../Accumulator/AddToSetAccumulator.php | 4 +- src/Builder/Accumulator/BottomAccumulator.php | 4 +- .../Accumulator/BottomNAccumulator.php | 4 +- .../Accumulator/DerivativeAccumulator.php | 4 +- src/Builder/Accumulator/FactoryTrait.php | 42 +-- src/Builder/Accumulator/FirstAccumulator.php | 4 +- src/Builder/Accumulator/FirstNAccumulator.php | 4 +- src/Builder/Accumulator/LastAccumulator.php | 4 +- src/Builder/Accumulator/MaxAccumulator.php | 4 +- src/Builder/Accumulator/MedianAccumulator.php | 4 +- src/Builder/Accumulator/MinAccumulator.php | 4 +- .../Accumulator/PercentileAccumulator.php | 4 +- src/Builder/Accumulator/PushAccumulator.php | 4 +- src/Builder/Accumulator/ShiftAccumulator.php | 8 +- src/Builder/Accumulator/TopAccumulator.php | 4 +- src/Builder/Accumulator/TopNAccumulator.php | 4 +- src/Builder/Expression/AndOperator.php | 4 +- src/Builder/Expression/BinarySizeOperator.php | 4 +- src/Builder/Expression/CmpOperator.php | 8 +- src/Builder/Expression/ConcatOperator.php | 4 +- src/Builder/Expression/CondOperator.php | 8 +- src/Builder/Expression/ConvertOperator.php | 16 +- src/Builder/Expression/DateAddOperator.php | 8 +- src/Builder/Expression/DateDiffOperator.php | 12 +- .../Expression/DateFromPartsOperator.php | 4 +- .../Expression/DateFromStringOperator.php | 20 +- .../Expression/DateSubtractOperator.php | 8 +- .../Expression/DateToPartsOperator.php | 4 +- .../Expression/DateToStringOperator.php | 12 +- src/Builder/Expression/DateTruncOperator.php | 12 +- src/Builder/Expression/DayOfMonthOperator.php | 4 +- src/Builder/Expression/DayOfWeekOperator.php | 4 +- src/Builder/Expression/DayOfYearOperator.php | 4 +- src/Builder/Expression/EqOperator.php | 8 +- src/Builder/Expression/FactoryTrait.php | 262 +++++++++--------- src/Builder/Expression/FilterOperator.php | 4 +- src/Builder/Expression/FunctionOperator.php | 8 +- src/Builder/Expression/GetFieldOperator.php | 8 +- src/Builder/Expression/GtOperator.php | 8 +- src/Builder/Expression/GteOperator.php | 8 +- src/Builder/Expression/HourOperator.php | 4 +- src/Builder/Expression/IfNullOperator.php | 4 +- src/Builder/Expression/InOperator.php | 4 +- .../Expression/IndexOfArrayOperator.php | 4 +- .../Expression/IndexOfBytesOperator.php | 8 +- src/Builder/Expression/IndexOfCPOperator.php | 8 +- src/Builder/Expression/IntegralOperator.php | 4 +- src/Builder/Expression/IsArrayOperator.php | 4 +- src/Builder/Expression/IsNumberOperator.php | 4 +- .../Expression/IsoDayOfWeekOperator.php | 4 +- src/Builder/Expression/IsoWeekOperator.php | 4 +- .../Expression/IsoWeekYearOperator.php | 4 +- src/Builder/Expression/LetOperator.php | 4 +- src/Builder/Expression/LiteralOperator.php | 4 +- src/Builder/Expression/LocfOperator.php | 4 +- src/Builder/Expression/LtOperator.php | 8 +- src/Builder/Expression/LteOperator.php | 8 +- src/Builder/Expression/LtrimOperator.php | 8 +- src/Builder/Expression/MapOperator.php | 8 +- src/Builder/Expression/MaxOperator.php | 4 +- src/Builder/Expression/MedianOperator.php | 4 +- src/Builder/Expression/MetaOperator.php | 4 +- .../Expression/MillisecondOperator.php | 4 +- src/Builder/Expression/MinOperator.php | 4 +- src/Builder/Expression/MinuteOperator.php | 4 +- src/Builder/Expression/MonthOperator.php | 4 +- src/Builder/Expression/NeOperator.php | 8 +- src/Builder/Expression/NotOperator.php | 4 +- src/Builder/Expression/OrOperator.php | 4 +- src/Builder/Expression/PercentileOperator.php | 4 +- src/Builder/Expression/ReduceOperator.php | 8 +- .../Expression/RegexFindAllOperator.php | 12 +- src/Builder/Expression/RegexFindOperator.php | 12 +- src/Builder/Expression/RegexMatchOperator.php | 12 +- src/Builder/Expression/ReplaceAllOperator.php | 12 +- src/Builder/Expression/ReplaceOneOperator.php | 12 +- src/Builder/Expression/RtrimOperator.php | 8 +- src/Builder/Expression/SecondOperator.php | 4 +- src/Builder/Expression/SetFieldOperator.php | 8 +- src/Builder/Expression/SplitOperator.php | 8 +- .../Expression/StrLenBytesOperator.php | 4 +- src/Builder/Expression/StrLenCPOperator.php | 4 +- src/Builder/Expression/StrcasecmpOperator.php | 8 +- .../Expression/SubstrBytesOperator.php | 4 +- src/Builder/Expression/SubstrCPOperator.php | 4 +- src/Builder/Expression/SubstrOperator.php | 4 +- src/Builder/Expression/SwitchOperator.php | 4 +- src/Builder/Expression/ToBoolOperator.php | 4 +- src/Builder/Expression/ToDateOperator.php | 4 +- src/Builder/Expression/ToDecimalOperator.php | 4 +- src/Builder/Expression/ToDoubleOperator.php | 4 +- .../Expression/ToHashedIndexKeyOperator.php | 4 +- src/Builder/Expression/ToIntOperator.php | 4 +- src/Builder/Expression/ToLongOperator.php | 4 +- src/Builder/Expression/ToLowerOperator.php | 4 +- src/Builder/Expression/ToObjectIdOperator.php | 4 +- src/Builder/Expression/ToStringOperator.php | 4 +- src/Builder/Expression/ToUpperOperator.php | 4 +- src/Builder/Expression/TrimOperator.php | 8 +- src/Builder/Expression/TypeOperator.php | 4 +- src/Builder/Expression/UnsetFieldOperator.php | 4 +- src/Builder/Expression/WeekOperator.php | 4 +- src/Builder/Expression/YearOperator.php | 4 +- src/Builder/Projection/FactoryTrait.php | 2 +- src/Builder/Projection/FilterOperator.php | 4 +- src/Builder/Query/AllOperator.php | 4 +- src/Builder/Query/BitsAllClearOperator.php | 4 +- src/Builder/Query/BitsAllSetOperator.php | 4 +- src/Builder/Query/BitsAnyClearOperator.php | 4 +- src/Builder/Query/BitsAnySetOperator.php | 4 +- src/Builder/Query/CommentOperator.php | 4 +- src/Builder/Query/ElemMatchOperator.php | 4 +- src/Builder/Query/EqOperator.php | 4 +- src/Builder/Query/ExprOperator.php | 4 +- src/Builder/Query/FactoryTrait.php | 40 +-- src/Builder/Query/GeometryOperator.php | 4 +- src/Builder/Query/GtOperator.php | 4 +- src/Builder/Query/GteOperator.php | 4 +- src/Builder/Query/LtOperator.php | 4 +- src/Builder/Query/LteOperator.php | 4 +- src/Builder/Query/NeOperator.php | 4 +- src/Builder/Query/NotOperator.php | 4 +- src/Builder/Query/TextOperator.php | 8 +- src/Builder/Query/TypeOperator.php | 4 +- src/Builder/Query/WhereOperator.php | 4 +- src/Builder/Stage/AddFieldsStage.php | 4 +- src/Builder/Stage/BucketAutoStage.php | 4 +- src/Builder/Stage/BucketStage.php | 8 +- src/Builder/Stage/ChangeStreamStage.php | 8 +- src/Builder/Stage/CountStage.php | 4 +- src/Builder/Stage/DensifyStage.php | 4 +- src/Builder/Stage/FactoryTrait.php | 82 +++--- src/Builder/Stage/FillStage.php | 4 +- src/Builder/Stage/GeoNearStage.php | 12 +- src/Builder/Stage/GraphLookupStage.php | 24 +- src/Builder/Stage/GroupStage.php | 4 +- src/Builder/Stage/ListSampledQueriesStage.php | 4 +- src/Builder/Stage/ListSearchIndexesStage.php | 8 +- src/Builder/Stage/LookupStage.php | 16 +- src/Builder/Stage/MergeStage.php | 16 +- src/Builder/Stage/OutStage.php | 8 +- src/Builder/Stage/ProjectStage.php | 4 +- src/Builder/Stage/RedactStage.php | 4 +- src/Builder/Stage/SetStage.php | 4 +- src/Builder/Stage/SetWindowFieldsStage.php | 4 +- src/Builder/Stage/SortByCountStage.php | 4 +- src/Builder/Stage/UnionWithStage.php | 4 +- src/Builder/Stage/UnsetStage.php | 4 +- src/Builder/Stage/UnwindStage.php | 8 +- 151 files changed, 642 insertions(+), 647 deletions(-) diff --git a/generator/src/OperatorGenerator.php b/generator/src/OperatorGenerator.php index ccb1b8a5d..0b9c60748 100644 --- a/generator/src/OperatorGenerator.php +++ b/generator/src/OperatorGenerator.php @@ -95,11 +95,6 @@ final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass $use = []; foreach ($nativeTypes as $key => $typeName) { - // strings cannot be empty - if ($typeName === 'string') { - $docTypes[$key] = 'non-empty-string'; - } - if (interface_exists($typeName) || class_exists($typeName)) { $use[] = $nativeTypes[$key] = '\\' . $typeName; $docTypes[$key] = $this->splitNamespaceAndClassName($typeName)[1]; diff --git a/src/Builder/Accumulator/AccumulatorAccumulator.php b/src/Builder/Accumulator/AccumulatorAccumulator.php index b4219ceca..a12b57c7c 100644 --- a/src/Builder/Accumulator/AccumulatorAccumulator.php +++ b/src/Builder/Accumulator/AccumulatorAccumulator.php @@ -32,35 +32,35 @@ class AccumulatorAccumulator implements AccumulatorInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var Javascript|non-empty-string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. */ + /** @var Javascript|string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. */ public readonly Javascript|string $init; - /** @var Javascript|non-empty-string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. */ + /** @var Javascript|string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. */ public readonly Javascript|string $accumulate; /** @var BSONArray|PackedArray|ResolvesToArray|array $accumulateArgs Arguments passed to the accumulate function. You can use accumulateArgs to specify what field value(s) to pass to the accumulate function. */ public readonly PackedArray|ResolvesToArray|BSONArray|array $accumulateArgs; - /** @var Javascript|non-empty-string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. */ + /** @var Javascript|string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. */ public readonly Javascript|string $merge; - /** @var non-empty-string $lang The language used in the $accumulator code. */ + /** @var string $lang The language used in the $accumulator code. */ public readonly string $lang; /** @var Optional|BSONArray|PackedArray|ResolvesToArray|array $initArgs Arguments passed to the init function. */ public readonly Optional|PackedArray|ResolvesToArray|BSONArray|array $initArgs; - /** @var Optional|Javascript|non-empty-string $finalize Function used to update the result of the accumulation. */ + /** @var Optional|Javascript|string $finalize Function used to update the result of the accumulation. */ public readonly Optional|Javascript|string $finalize; /** - * @param Javascript|non-empty-string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. - * @param Javascript|non-empty-string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. + * @param Javascript|string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. + * @param Javascript|string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. * @param BSONArray|PackedArray|ResolvesToArray|array $accumulateArgs Arguments passed to the accumulate function. You can use accumulateArgs to specify what field value(s) to pass to the accumulate function. - * @param Javascript|non-empty-string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. - * @param non-empty-string $lang The language used in the $accumulator code. + * @param Javascript|string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. + * @param string $lang The language used in the $accumulator code. * @param Optional|BSONArray|PackedArray|ResolvesToArray|array $initArgs Arguments passed to the init function. - * @param Optional|Javascript|non-empty-string $finalize Function used to update the result of the accumulation. + * @param Optional|Javascript|string $finalize Function used to update the result of the accumulation. */ public function __construct( Javascript|string $init, diff --git a/src/Builder/Accumulator/AddToSetAccumulator.php b/src/Builder/Accumulator/AddToSetAccumulator.php index 02a202990..08bda8cea 100644 --- a/src/Builder/Accumulator/AddToSetAccumulator.php +++ b/src/Builder/Accumulator/AddToSetAccumulator.php @@ -26,11 +26,11 @@ class AddToSetAccumulator implements AccumulatorInterface, WindowInterface, Oper { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Accumulator/BottomAccumulator.php b/src/Builder/Accumulator/BottomAccumulator.php index b2337debd..c8408bafc 100644 --- a/src/Builder/Accumulator/BottomAccumulator.php +++ b/src/Builder/Accumulator/BottomAccumulator.php @@ -31,12 +31,12 @@ class BottomAccumulator implements AccumulatorInterface, WindowInterface, Operat /** @var Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. */ public readonly Document|Serializable|stdClass|array $sortBy; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $output; /** * @param Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public function __construct( Document|Serializable|stdClass|array $sortBy, diff --git a/src/Builder/Accumulator/BottomNAccumulator.php b/src/Builder/Accumulator/BottomNAccumulator.php index 004f6a019..47e51852a 100644 --- a/src/Builder/Accumulator/BottomNAccumulator.php +++ b/src/Builder/Accumulator/BottomNAccumulator.php @@ -36,13 +36,13 @@ class BottomNAccumulator implements AccumulatorInterface, WindowInterface, Opera /** @var Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. */ public readonly Document|Serializable|stdClass|array $sortBy; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $output; /** * @param ResolvesToInt|int $n Limits the number of results per group and has to be a positive integral expression that is either a constant or depends on the _id value for $group. * @param Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public function __construct( ResolvesToInt|int $n, diff --git a/src/Builder/Accumulator/DerivativeAccumulator.php b/src/Builder/Accumulator/DerivativeAccumulator.php index 4a04119b3..cd109536a 100644 --- a/src/Builder/Accumulator/DerivativeAccumulator.php +++ b/src/Builder/Accumulator/DerivativeAccumulator.php @@ -32,14 +32,14 @@ class DerivativeAccumulator implements WindowInterface, OperatorInterface public readonly Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input; /** - * @var Optional|non-empty-string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @var Optional|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ public readonly Optional|string $unit; /** * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input - * @param Optional|non-empty-string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @param Optional|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ public function __construct( diff --git a/src/Builder/Accumulator/FactoryTrait.php b/src/Builder/Accumulator/FactoryTrait.php index af211520f..604e3c388 100644 --- a/src/Builder/Accumulator/FactoryTrait.php +++ b/src/Builder/Accumulator/FactoryTrait.php @@ -36,13 +36,13 @@ trait FactoryTrait * New in MongoDB 4.4. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/accumulator/ - * @param Javascript|non-empty-string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. - * @param Javascript|non-empty-string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. + * @param Javascript|string $init Function used to initialize the state. The init function receives its arguments from the initArgs array expression. You can specify the function definition as either BSON type Code or String. + * @param Javascript|string $accumulate Function used to accumulate documents. The accumulate function receives its arguments from the current state and accumulateArgs array expression. The result of the accumulate function becomes the new state. You can specify the function definition as either BSON type Code or String. * @param BSONArray|PackedArray|ResolvesToArray|array $accumulateArgs Arguments passed to the accumulate function. You can use accumulateArgs to specify what field value(s) to pass to the accumulate function. - * @param Javascript|non-empty-string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. - * @param non-empty-string $lang The language used in the $accumulator code. + * @param Javascript|string $merge Function used to merge two internal states. merge must be either a String or Code BSON type. merge returns the combined result of the two merged states. For information on when the merge function is called, see Merge Two States with $merge. + * @param string $lang The language used in the $accumulator code. * @param Optional|BSONArray|PackedArray|ResolvesToArray|array $initArgs Arguments passed to the init function. - * @param Optional|Javascript|non-empty-string $finalize Function used to update the result of the accumulation. + * @param Optional|Javascript|string $finalize Function used to update the result of the accumulation. */ public static function accumulator( Javascript|string $init, @@ -62,7 +62,7 @@ public static function accumulator( * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/addToSet/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function addToSet( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -89,7 +89,7 @@ public static function avg(Decimal128|Int64|ResolvesToNumber|float|int $expressi * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottom/ * @param Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public static function bottom( Document|Serializable|stdClass|array $sortBy, @@ -107,7 +107,7 @@ public static function bottom( * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bottomN/ * @param ResolvesToInt|int $n Limits the number of results per group and has to be a positive integral expression that is either a constant or depends on the _id value for $group. * @param Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public static function bottomN( ResolvesToInt|int $n, @@ -179,7 +179,7 @@ public static function denseRank(): DenseRankAccumulator * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/derivative/ * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input - * @param Optional|non-empty-string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @param Optional|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ public static function derivative( @@ -227,7 +227,7 @@ public static function expMovingAvg( * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/first/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function first( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -242,7 +242,7 @@ public static function first( * If the group contains fewer than n elements, $firstN returns all elements in the group. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/firstN/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input An expression that resolves to the array from which to return n elements. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $input An expression that resolves to the array from which to return n elements. * @param ResolvesToInt|int $n A positive integral expression that is either a constant or depends on the _id value for $group. */ public static function firstN( @@ -258,7 +258,7 @@ public static function firstN( * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/last/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function last( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -289,7 +289,7 @@ public static function lastN( * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function max( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -323,7 +323,7 @@ public static function maxN( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/ * @param Decimal128|Int64|ResolvesToNumber|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. - * @param non-empty-string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. + * @param string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ public static function median( Decimal128|Int64|ResolvesToNumber|float|int $input, @@ -351,7 +351,7 @@ public static function mergeObjects( * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function min( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -390,7 +390,7 @@ public static function minN( * @param Decimal128|Int64|ResolvesToNumber|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. * @param BSONArray|PackedArray|ResolvesToArray|array $p $percentile calculates a percentile value for each element in p. The elements represent percentages and must evaluate to numeric values in the range 0.0 to 1.0, inclusive. * $percentile returns results in the same order as the elements in p. - * @param non-empty-string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. + * @param string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. */ public static function percentile( Decimal128|Int64|ResolvesToNumber|float|int $input, @@ -406,7 +406,7 @@ public static function percentile( * Changed in MongoDB 5.0: Available in the $setWindowFields stage. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/push/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function push( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -420,13 +420,13 @@ public static function push( * New in MongoDB 5.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/shift/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Specifies an expression to evaluate and return in the output. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Specifies an expression to evaluate and return in the output. * @param int $by Specifies an integer with a numeric document position relative to the current document in the output. * For example: * 1 specifies the document position after the current document. * -1 specifies the document position before the current document. * -2 specifies the document position that is two positions before the current document. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $default Specifies an optional default expression to evaluate if the document position is outside of the implicit $setWindowFields stage window. The implicit window contains all the documents in the partition. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $default Specifies an optional default expression to evaluate if the document position is outside of the implicit $setWindowFields stage window. The implicit window contains all the documents in the partition. * The default expression must evaluate to a constant value. * If you do not specify a default expression, $shift returns null for documents whose positions are outside of the implicit $setWindowFields stage window. */ @@ -485,7 +485,7 @@ public static function sum(Decimal128|Int64|ResolvesToNumber|float|int $expressi * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/top/ * @param Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public static function top( Document|Serializable|stdClass|array $sortBy, @@ -504,7 +504,7 @@ public static function top( * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/topN/ * @param ResolvesToInt|int $n limits the number of results per group and has to be a positive integral expression that is either a constant or depends on the _id value for $group. * @param Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public static function topN( ResolvesToInt|int $n, diff --git a/src/Builder/Accumulator/FirstAccumulator.php b/src/Builder/Accumulator/FirstAccumulator.php index 883b0e04c..559305d52 100644 --- a/src/Builder/Accumulator/FirstAccumulator.php +++ b/src/Builder/Accumulator/FirstAccumulator.php @@ -26,11 +26,11 @@ class FirstAccumulator implements AccumulatorInterface, WindowInterface, Operato { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Accumulator/FirstNAccumulator.php b/src/Builder/Accumulator/FirstNAccumulator.php index ef5b4de77..0100f1aed 100644 --- a/src/Builder/Accumulator/FirstNAccumulator.php +++ b/src/Builder/Accumulator/FirstNAccumulator.php @@ -28,14 +28,14 @@ class FirstNAccumulator implements AccumulatorInterface, WindowInterface, Operat { public const ENCODE = Encode::Object; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input An expression that resolves to the array from which to return n elements. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $input An expression that resolves to the array from which to return n elements. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $input; /** @var ResolvesToInt|int $n A positive integral expression that is either a constant or depends on the _id value for $group. */ public readonly ResolvesToInt|int $n; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input An expression that resolves to the array from which to return n elements. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $input An expression that resolves to the array from which to return n elements. * @param ResolvesToInt|int $n A positive integral expression that is either a constant or depends on the _id value for $group. */ public function __construct( diff --git a/src/Builder/Accumulator/LastAccumulator.php b/src/Builder/Accumulator/LastAccumulator.php index 0d142f548..ab01fecdb 100644 --- a/src/Builder/Accumulator/LastAccumulator.php +++ b/src/Builder/Accumulator/LastAccumulator.php @@ -26,11 +26,11 @@ class LastAccumulator implements AccumulatorInterface, WindowInterface, Operator { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Accumulator/MaxAccumulator.php b/src/Builder/Accumulator/MaxAccumulator.php index 517fdd23a..0258eb82b 100644 --- a/src/Builder/Accumulator/MaxAccumulator.php +++ b/src/Builder/Accumulator/MaxAccumulator.php @@ -26,11 +26,11 @@ class MaxAccumulator implements AccumulatorInterface, WindowInterface, OperatorI { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Accumulator/MedianAccumulator.php b/src/Builder/Accumulator/MedianAccumulator.php index 9236c2f7f..a81070700 100644 --- a/src/Builder/Accumulator/MedianAccumulator.php +++ b/src/Builder/Accumulator/MedianAccumulator.php @@ -33,12 +33,12 @@ class MedianAccumulator implements AccumulatorInterface, WindowInterface, Operat /** @var Decimal128|Int64|ResolvesToNumber|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. */ public readonly Decimal128|Int64|ResolvesToNumber|float|int $input; - /** @var non-empty-string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ + /** @var string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ public readonly string $method; /** * @param Decimal128|Int64|ResolvesToNumber|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. - * @param non-empty-string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. + * @param string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ public function __construct(Decimal128|Int64|ResolvesToNumber|float|int $input, string $method) { diff --git a/src/Builder/Accumulator/MinAccumulator.php b/src/Builder/Accumulator/MinAccumulator.php index 23681a7f5..9194c8866 100644 --- a/src/Builder/Accumulator/MinAccumulator.php +++ b/src/Builder/Accumulator/MinAccumulator.php @@ -26,11 +26,11 @@ class MinAccumulator implements AccumulatorInterface, WindowInterface, OperatorI { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Accumulator/PercentileAccumulator.php b/src/Builder/Accumulator/PercentileAccumulator.php index f17f40e33..85128f024 100644 --- a/src/Builder/Accumulator/PercentileAccumulator.php +++ b/src/Builder/Accumulator/PercentileAccumulator.php @@ -49,14 +49,14 @@ class PercentileAccumulator implements AccumulatorInterface, WindowInterface, Op */ public readonly PackedArray|ResolvesToArray|BSONArray|array $p; - /** @var non-empty-string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. */ + /** @var string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. */ public readonly string $method; /** * @param Decimal128|Int64|ResolvesToNumber|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. * @param BSONArray|PackedArray|ResolvesToArray|array $p $percentile calculates a percentile value for each element in p. The elements represent percentages and must evaluate to numeric values in the range 0.0 to 1.0, inclusive. * $percentile returns results in the same order as the elements in p. - * @param non-empty-string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. + * @param string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. */ public function __construct( Decimal128|Int64|ResolvesToNumber|float|int $input, diff --git a/src/Builder/Accumulator/PushAccumulator.php b/src/Builder/Accumulator/PushAccumulator.php index 4368debad..3eb095670 100644 --- a/src/Builder/Accumulator/PushAccumulator.php +++ b/src/Builder/Accumulator/PushAccumulator.php @@ -26,11 +26,11 @@ class PushAccumulator implements AccumulatorInterface, WindowInterface, Operator { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Accumulator/ShiftAccumulator.php b/src/Builder/Accumulator/ShiftAccumulator.php index cf1739cc2..c8533c140 100644 --- a/src/Builder/Accumulator/ShiftAccumulator.php +++ b/src/Builder/Accumulator/ShiftAccumulator.php @@ -25,7 +25,7 @@ class ShiftAccumulator implements WindowInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Specifies an expression to evaluate and return in the output. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Specifies an expression to evaluate and return in the output. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $output; /** @@ -38,20 +38,20 @@ class ShiftAccumulator implements WindowInterface, OperatorInterface public readonly int $by; /** - * @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $default Specifies an optional default expression to evaluate if the document position is outside of the implicit $setWindowFields stage window. The implicit window contains all the documents in the partition. + * @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $default Specifies an optional default expression to evaluate if the document position is outside of the implicit $setWindowFields stage window. The implicit window contains all the documents in the partition. * The default expression must evaluate to a constant value. * If you do not specify a default expression, $shift returns null for documents whose positions are outside of the implicit $setWindowFields stage window. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $default; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Specifies an expression to evaluate and return in the output. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Specifies an expression to evaluate and return in the output. * @param int $by Specifies an integer with a numeric document position relative to the current document in the output. * For example: * 1 specifies the document position after the current document. * -1 specifies the document position before the current document. * -2 specifies the document position that is two positions before the current document. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $default Specifies an optional default expression to evaluate if the document position is outside of the implicit $setWindowFields stage window. The implicit window contains all the documents in the partition. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $default Specifies an optional default expression to evaluate if the document position is outside of the implicit $setWindowFields stage window. The implicit window contains all the documents in the partition. * The default expression must evaluate to a constant value. * If you do not specify a default expression, $shift returns null for documents whose positions are outside of the implicit $setWindowFields stage window. */ diff --git a/src/Builder/Accumulator/TopAccumulator.php b/src/Builder/Accumulator/TopAccumulator.php index 11f4caca0..135b8eb0a 100644 --- a/src/Builder/Accumulator/TopAccumulator.php +++ b/src/Builder/Accumulator/TopAccumulator.php @@ -32,12 +32,12 @@ class TopAccumulator implements AccumulatorInterface, OperatorInterface /** @var Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. */ public readonly Document|Serializable|stdClass|array $sortBy; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $output; /** * @param Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public function __construct( Document|Serializable|stdClass|array $sortBy, diff --git a/src/Builder/Accumulator/TopNAccumulator.php b/src/Builder/Accumulator/TopNAccumulator.php index dabec3721..ab8009121 100644 --- a/src/Builder/Accumulator/TopNAccumulator.php +++ b/src/Builder/Accumulator/TopNAccumulator.php @@ -36,13 +36,13 @@ class TopNAccumulator implements AccumulatorInterface, OperatorInterface /** @var Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. */ public readonly Document|Serializable|stdClass|array $sortBy; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $output; /** * @param ResolvesToInt|int $n limits the number of results per group and has to be a positive integral expression that is either a constant or depends on the _id value for $group. * @param Document|Serializable|array|stdClass $sortBy Specifies the order of results, with syntax similar to $sort. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $output Represents the output for each element in the group and can be any expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $output Represents the output for each element in the group and can be any expression. */ public function __construct( ResolvesToInt|int $n, diff --git a/src/Builder/Expression/AndOperator.php b/src/Builder/Expression/AndOperator.php index e576ff71c..989a27f8f 100644 --- a/src/Builder/Expression/AndOperator.php +++ b/src/Builder/Expression/AndOperator.php @@ -28,11 +28,11 @@ class AndOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ + /** @var list $expression */ public readonly array $expression; /** - * @param Decimal128|ExpressionInterface|Int64|ResolvesToBool|ResolvesToNull|ResolvesToNumber|ResolvesToString|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param Decimal128|ExpressionInterface|Int64|ResolvesToBool|ResolvesToNull|ResolvesToNumber|ResolvesToString|Type|array|bool|float|int|null|stdClass|string ...$expression * @no-named-arguments */ public function __construct( diff --git a/src/Builder/Expression/BinarySizeOperator.php b/src/Builder/Expression/BinarySizeOperator.php index d4e93f345..28293cc88 100644 --- a/src/Builder/Expression/BinarySizeOperator.php +++ b/src/Builder/Expression/BinarySizeOperator.php @@ -21,11 +21,11 @@ class BinarySizeOperator implements ResolvesToInt, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Binary|ResolvesToBinData|ResolvesToNull|ResolvesToString|non-empty-string|null $expression */ + /** @var Binary|ResolvesToBinData|ResolvesToNull|ResolvesToString|null|string $expression */ public readonly Binary|ResolvesToBinData|ResolvesToNull|ResolvesToString|null|string $expression; /** - * @param Binary|ResolvesToBinData|ResolvesToNull|ResolvesToString|non-empty-string|null $expression + * @param Binary|ResolvesToBinData|ResolvesToNull|ResolvesToString|null|string $expression */ public function __construct(Binary|ResolvesToBinData|ResolvesToNull|ResolvesToString|null|string $expression) { diff --git a/src/Builder/Expression/CmpOperator.php b/src/Builder/Expression/CmpOperator.php index c80ba393c..78354cb20 100644 --- a/src/Builder/Expression/CmpOperator.php +++ b/src/Builder/Expression/CmpOperator.php @@ -23,15 +23,15 @@ class CmpOperator implements ResolvesToInt, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public function __construct( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, diff --git a/src/Builder/Expression/ConcatOperator.php b/src/Builder/Expression/ConcatOperator.php index 4bfd99ca7..20bd3e95d 100644 --- a/src/Builder/Expression/ConcatOperator.php +++ b/src/Builder/Expression/ConcatOperator.php @@ -23,11 +23,11 @@ class ConcatOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ + /** @var list $expression */ public readonly array $expression; /** - * @param ResolvesToString|non-empty-string ...$expression + * @param ResolvesToString|string ...$expression * @no-named-arguments */ public function __construct(ResolvesToString|string ...$expression) diff --git a/src/Builder/Expression/CondOperator.php b/src/Builder/Expression/CondOperator.php index dfd53ba6f..1d3a44c58 100644 --- a/src/Builder/Expression/CondOperator.php +++ b/src/Builder/Expression/CondOperator.php @@ -26,16 +26,16 @@ class CondOperator implements ResolvesToAny, OperatorInterface /** @var ResolvesToBool|bool $if */ public readonly ResolvesToBool|bool $if; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $then */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $then */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $then; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $else */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $else */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $else; /** * @param ResolvesToBool|bool $if - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $then - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $else + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $then + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $else */ public function __construct( ResolvesToBool|bool $if, diff --git a/src/Builder/Expression/ConvertOperator.php b/src/Builder/Expression/ConvertOperator.php index ecfdafed6..f8cb5210e 100644 --- a/src/Builder/Expression/ConvertOperator.php +++ b/src/Builder/Expression/ConvertOperator.php @@ -25,30 +25,30 @@ class ConvertOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $input */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $input; - /** @var ResolvesToInt|ResolvesToString|int|non-empty-string $to */ + /** @var ResolvesToInt|ResolvesToString|int|string $to */ public readonly ResolvesToInt|ResolvesToString|int|string $to; /** - * @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onError The value to return on encountering an error during conversion, including unsupported type conversions. The arguments can be any valid expression. + * @var Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onError The value to return on encountering an error during conversion, including unsupported type conversions. The arguments can be any valid expression. * If unspecified, the operation throws an error upon encountering an error and stops. */ public readonly Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $onError; /** - * @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull The value to return if the input is null or missing. The arguments can be any valid expression. + * @var Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onNull The value to return if the input is null or missing. The arguments can be any valid expression. * If unspecified, $convert returns null if the input is null or missing. */ public readonly Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $onNull; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input - * @param ResolvesToInt|ResolvesToString|int|non-empty-string $to - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onError The value to return on encountering an error during conversion, including unsupported type conversions. The arguments can be any valid expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $input + * @param ResolvesToInt|ResolvesToString|int|string $to + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onError The value to return on encountering an error during conversion, including unsupported type conversions. The arguments can be any valid expression. * If unspecified, the operation throws an error upon encountering an error and stops. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull The value to return if the input is null or missing. The arguments can be any valid expression. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onNull The value to return if the input is null or missing. The arguments can be any valid expression. * If unspecified, $convert returns null if the input is null or missing. */ public function __construct( diff --git a/src/Builder/Expression/DateAddOperator.php b/src/Builder/Expression/DateAddOperator.php index 183d7b405..9d35d3beb 100644 --- a/src/Builder/Expression/DateAddOperator.php +++ b/src/Builder/Expression/DateAddOperator.php @@ -28,20 +28,20 @@ class DateAddOperator implements ResolvesToDate, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate; - /** @var ResolvesToString|non-empty-string $unit The unit used to measure the amount of time added to the startDate. */ + /** @var ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. */ public readonly ResolvesToString|string $unit; /** @var Int64|ResolvesToInt|ResolvesToLong|int $amount */ public readonly Int64|ResolvesToInt|ResolvesToLong|int $amount; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|non-empty-string $unit The unit used to measure the amount of time added to the startDate. + * @param ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. * @param Int64|ResolvesToInt|ResolvesToLong|int $amount - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, diff --git a/src/Builder/Expression/DateDiffOperator.php b/src/Builder/Expression/DateDiffOperator.php index 62cedac3f..e28d43508 100644 --- a/src/Builder/Expression/DateDiffOperator.php +++ b/src/Builder/Expression/DateDiffOperator.php @@ -30,21 +30,21 @@ class DateDiffOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $endDate The end of the time period. The endDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $endDate; - /** @var ResolvesToString|non-empty-string $unit The time measurement unit between the startDate and endDate */ + /** @var ResolvesToString|string $unit The time measurement unit between the startDate and endDate */ public readonly ResolvesToString|string $unit; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; - /** @var Optional|ResolvesToString|non-empty-string $startOfWeek Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string */ + /** @var Optional|ResolvesToString|string $startOfWeek Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string */ public readonly Optional|ResolvesToString|string $startOfWeek; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The start of the time period. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $endDate The end of the time period. The endDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|non-empty-string $unit The time measurement unit between the startDate and endDate - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. - * @param Optional|ResolvesToString|non-empty-string $startOfWeek Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string + * @param ResolvesToString|string $unit The time measurement unit between the startDate and endDate + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $startOfWeek Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, diff --git a/src/Builder/Expression/DateFromPartsOperator.php b/src/Builder/Expression/DateFromPartsOperator.php index 022e0299c..f8b7ad639 100644 --- a/src/Builder/Expression/DateFromPartsOperator.php +++ b/src/Builder/Expression/DateFromPartsOperator.php @@ -53,7 +53,7 @@ class DateFromPartsOperator implements ResolvesToDate, OperatorInterface /** @var Optional|Decimal128|Int64|ResolvesToNumber|float|int $millisecond Millisecond. Defaults to 0. */ public readonly Optional|Decimal128|Int64|ResolvesToNumber|float|int $millisecond; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** @@ -67,7 +67,7 @@ class DateFromPartsOperator implements ResolvesToDate, OperatorInterface * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $minute Minute. Defaults to 0. * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $second Second. Defaults to 0. * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $millisecond Millisecond. Defaults to 0. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( Optional|Decimal128|Int64|ResolvesToNumber|float|int $year = Optional::Undefined, diff --git a/src/Builder/Expression/DateFromStringOperator.php b/src/Builder/Expression/DateFromStringOperator.php index cbda447b8..5a3f808f0 100644 --- a/src/Builder/Expression/DateFromStringOperator.php +++ b/src/Builder/Expression/DateFromStringOperator.php @@ -24,38 +24,38 @@ class DateFromStringOperator implements ResolvesToDate, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToString|non-empty-string $dateString The date/time string to convert to a date object. */ + /** @var ResolvesToString|string $dateString The date/time string to convert to a date object. */ public readonly ResolvesToString|string $dateString; /** - * @var Optional|ResolvesToString|non-empty-string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + * @var Optional|ResolvesToString|string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. * If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. */ public readonly Optional|ResolvesToString|string $format; - /** @var Optional|ResolvesToString|non-empty-string $timezone The time zone to use to format the date. */ + /** @var Optional|ResolvesToString|string $timezone The time zone to use to format the date. */ public readonly Optional|ResolvesToString|string $timezone; /** - * @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onError If $dateFromString encounters an error while parsing the given dateString, it outputs the result value of the provided onError expression. This result value can be of any type. + * @var Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onError If $dateFromString encounters an error while parsing the given dateString, it outputs the result value of the provided onError expression. This result value can be of any type. * If you do not specify onError, $dateFromString throws an error if it cannot parse dateString. */ public readonly Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $onError; /** - * @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. This result value can be of any type. + * @var Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onNull If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. This result value can be of any type. * If you do not specify onNull and dateString is null or missing, then $dateFromString outputs null. */ public readonly Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $onNull; /** - * @param ResolvesToString|non-empty-string $dateString The date/time string to convert to a date object. - * @param Optional|ResolvesToString|non-empty-string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + * @param ResolvesToString|string $dateString The date/time string to convert to a date object. + * @param Optional|ResolvesToString|string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. * If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. - * @param Optional|ResolvesToString|non-empty-string $timezone The time zone to use to format the date. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onError If $dateFromString encounters an error while parsing the given dateString, it outputs the result value of the provided onError expression. This result value can be of any type. + * @param Optional|ResolvesToString|string $timezone The time zone to use to format the date. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onError If $dateFromString encounters an error while parsing the given dateString, it outputs the result value of the provided onError expression. This result value can be of any type. * If you do not specify onError, $dateFromString throws an error if it cannot parse dateString. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. This result value can be of any type. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onNull If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. This result value can be of any type. * If you do not specify onNull and dateString is null or missing, then $dateFromString outputs null. */ public function __construct( diff --git a/src/Builder/Expression/DateSubtractOperator.php b/src/Builder/Expression/DateSubtractOperator.php index 8229a35fa..e1a583096 100644 --- a/src/Builder/Expression/DateSubtractOperator.php +++ b/src/Builder/Expression/DateSubtractOperator.php @@ -28,20 +28,20 @@ class DateSubtractOperator implements ResolvesToDate, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate; - /** @var ResolvesToString|non-empty-string $unit The unit used to measure the amount of time added to the startDate. */ + /** @var ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. */ public readonly ResolvesToString|string $unit; /** @var Int64|ResolvesToInt|ResolvesToLong|int $amount */ public readonly Int64|ResolvesToInt|ResolvesToLong|int $amount; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|non-empty-string $unit The unit used to measure the amount of time added to the startDate. + * @param ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. * @param Int64|ResolvesToInt|ResolvesToLong|int $amount - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, diff --git a/src/Builder/Expression/DateToPartsOperator.php b/src/Builder/Expression/DateToPartsOperator.php index 37d7a4cfc..c0f637259 100644 --- a/src/Builder/Expression/DateToPartsOperator.php +++ b/src/Builder/Expression/DateToPartsOperator.php @@ -27,7 +27,7 @@ class DateToPartsOperator implements ResolvesToObject, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The input date for which to return parts. date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** @var Optional|bool $iso8601 If set to true, modifies the output document to use ISO week date fields. Defaults to false. */ @@ -35,7 +35,7 @@ class DateToPartsOperator implements ResolvesToObject, OperatorInterface /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The input date for which to return parts. date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. * @param Optional|bool $iso8601 If set to true, modifies the output document to use ISO week date fields. Defaults to false. */ public function __construct( diff --git a/src/Builder/Expression/DateToStringOperator.php b/src/Builder/Expression/DateToStringOperator.php index 0180068e4..e33c519fd 100644 --- a/src/Builder/Expression/DateToStringOperator.php +++ b/src/Builder/Expression/DateToStringOperator.php @@ -31,26 +31,26 @@ class DateToStringOperator implements ResolvesToString, OperatorInterface public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; /** - * @var Optional|ResolvesToString|non-empty-string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + * @var Optional|ResolvesToString|string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. * If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. */ public readonly Optional|ResolvesToString|string $format; - /** @var Optional|ResolvesToString|non-empty-string $timezone The time zone to use to format the date. */ + /** @var Optional|ResolvesToString|string $timezone The time zone to use to format the date. */ public readonly Optional|ResolvesToString|string $timezone; /** - * @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull The value to return if the date is null or missing. + * @var Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onNull The value to return if the date is null or missing. * If unspecified, $dateToString returns null if the date is null or missing. */ public readonly Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $onNull; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to convert to string. Must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + * @param Optional|ResolvesToString|string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. * If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. - * @param Optional|ResolvesToString|non-empty-string $timezone The time zone to use to format the date. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull The value to return if the date is null or missing. + * @param Optional|ResolvesToString|string $timezone The time zone to use to format the date. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onNull The value to return if the date is null or missing. * If unspecified, $dateToString returns null if the date is null or missing. */ public function __construct( diff --git a/src/Builder/Expression/DateTruncOperator.php b/src/Builder/Expression/DateTruncOperator.php index 9464e7752..cf56cdfa2 100644 --- a/src/Builder/Expression/DateTruncOperator.php +++ b/src/Builder/Expression/DateTruncOperator.php @@ -30,7 +30,7 @@ class DateTruncOperator implements ResolvesToDate, OperatorInterface public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; /** - * @var ResolvesToString|non-empty-string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. + * @var ResolvesToString|string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. * Together, binSize and unit specify the time period used in the $dateTrunc calculation. */ public readonly ResolvesToString|string $unit; @@ -41,23 +41,23 @@ class DateTruncOperator implements ResolvesToDate, OperatorInterface */ public readonly Optional|Decimal128|Int64|ResolvesToNumber|float|int $binSize; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** - * @var Optional|non-empty-string $startOfWeek The start of the week. Used when + * @var Optional|string $startOfWeek The start of the week. Used when * unit is week. Defaults to Sunday. */ public readonly Optional|string $startOfWeek; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to truncate, specified in UTC. The date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|non-empty-string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. + * @param ResolvesToString|string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. * Together, binSize and unit specify the time period used in the $dateTrunc calculation. * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $binSize The numeric time value, specified as an expression that must resolve to a positive non-zero number. Defaults to 1. * Together, binSize and unit specify the time period used in the $dateTrunc calculation. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. - * @param Optional|non-empty-string $startOfWeek The start of the week. Used when + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|string $startOfWeek The start of the week. Used when * unit is week. Defaults to Sunday. */ public function __construct( diff --git a/src/Builder/Expression/DayOfMonthOperator.php b/src/Builder/Expression/DayOfMonthOperator.php index a13e3b983..a1a06db6b 100644 --- a/src/Builder/Expression/DayOfMonthOperator.php +++ b/src/Builder/Expression/DayOfMonthOperator.php @@ -27,12 +27,12 @@ class DayOfMonthOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/DayOfWeekOperator.php b/src/Builder/Expression/DayOfWeekOperator.php index c593600d2..8b439c02c 100644 --- a/src/Builder/Expression/DayOfWeekOperator.php +++ b/src/Builder/Expression/DayOfWeekOperator.php @@ -27,12 +27,12 @@ class DayOfWeekOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/DayOfYearOperator.php b/src/Builder/Expression/DayOfYearOperator.php index 3b0762612..d97be270d 100644 --- a/src/Builder/Expression/DayOfYearOperator.php +++ b/src/Builder/Expression/DayOfYearOperator.php @@ -27,12 +27,12 @@ class DayOfYearOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/EqOperator.php b/src/Builder/Expression/EqOperator.php index c60717006..5da15a3c3 100644 --- a/src/Builder/Expression/EqOperator.php +++ b/src/Builder/Expression/EqOperator.php @@ -23,15 +23,15 @@ class EqOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public function __construct( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index cadcf016a..4611c0835 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -99,7 +99,7 @@ public static function allElementsTrue( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/and/ * @no-named-arguments - * @param Decimal128|ExpressionInterface|Int64|ResolvesToBool|ResolvesToNull|ResolvesToNumber|ResolvesToString|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param Decimal128|ExpressionInterface|Int64|ResolvesToBool|ResolvesToNull|ResolvesToNumber|ResolvesToString|Type|array|bool|float|int|null|stdClass|string ...$expression */ public static function and( Decimal128|Int64|Type|ResolvesToBool|ResolvesToNull|ResolvesToNumber|ResolvesToString|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, @@ -233,7 +233,7 @@ public static function avg(Decimal128|Int64|ResolvesToNumber|float|int ...$expre * Returns the size of a given string or binary data value's content in bytes. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/binarySize/ - * @param Binary|ResolvesToBinData|ResolvesToNull|ResolvesToString|non-empty-string|null $expression + * @param Binary|ResolvesToBinData|ResolvesToNull|ResolvesToString|null|string $expression */ public static function binarySize( Binary|ResolvesToBinData|ResolvesToNull|ResolvesToString|null|string $expression, @@ -321,8 +321,8 @@ public static function ceil(Decimal128|Int64|ResolvesToNumber|float|int $express * Returns 0 if the two values are equivalent, 1 if the first value is greater than the second, and -1 if the first value is less than the second. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/cmp/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public static function cmp( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, @@ -337,7 +337,7 @@ public static function cmp( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/concat/ * @no-named-arguments - * @param ResolvesToString|non-empty-string ...$expression + * @param ResolvesToString|string ...$expression */ public static function concat(ResolvesToString|string ...$expression): ConcatOperator { @@ -361,8 +361,8 @@ public static function concatArrays(PackedArray|ResolvesToArray|BSONArray|array * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/cond/ * @param ResolvesToBool|bool $if - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $then - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $else + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $then + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $else */ public static function cond( ResolvesToBool|bool $if, @@ -378,11 +378,11 @@ public static function cond( * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/convert/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input - * @param ResolvesToInt|ResolvesToString|int|non-empty-string $to - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onError The value to return on encountering an error during conversion, including unsupported type conversions. The arguments can be any valid expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $input + * @param ResolvesToInt|ResolvesToString|int|string $to + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onError The value to return on encountering an error during conversion, including unsupported type conversions. The arguments can be any valid expression. * If unspecified, the operation throws an error upon encountering an error and stops. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull The value to return if the input is null or missing. The arguments can be any valid expression. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onNull The value to return if the input is null or missing. The arguments can be any valid expression. * If unspecified, $convert returns null if the input is null or missing. */ public static function convert( @@ -424,9 +424,9 @@ public static function cosh(Decimal128|Int64|ResolvesToNumber|float|int $express * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateAdd/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|non-empty-string $unit The unit used to measure the amount of time added to the startDate. + * @param ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. * @param Int64|ResolvesToInt|ResolvesToLong|int $amount - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function dateAdd( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, @@ -444,9 +444,9 @@ public static function dateAdd( * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateDiff/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The start of the time period. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $endDate The end of the time period. The endDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|non-empty-string $unit The time measurement unit between the startDate and endDate - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. - * @param Optional|ResolvesToString|non-empty-string $startOfWeek Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string + * @param ResolvesToString|string $unit The time measurement unit between the startDate and endDate + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $startOfWeek Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string */ public static function dateDiff( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, @@ -473,7 +473,7 @@ public static function dateDiff( * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $minute Minute. Defaults to 0. * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $second Second. Defaults to 0. * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $millisecond Millisecond. Defaults to 0. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function dateFromParts( Optional|Decimal128|Int64|ResolvesToNumber|float|int $year = Optional::Undefined, @@ -496,13 +496,13 @@ public static function dateFromParts( * Converts a date/time string to a date object. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateFromString/ - * @param ResolvesToString|non-empty-string $dateString The date/time string to convert to a date object. - * @param Optional|ResolvesToString|non-empty-string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + * @param ResolvesToString|string $dateString The date/time string to convert to a date object. + * @param Optional|ResolvesToString|string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. * If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. - * @param Optional|ResolvesToString|non-empty-string $timezone The time zone to use to format the date. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onError If $dateFromString encounters an error while parsing the given dateString, it outputs the result value of the provided onError expression. This result value can be of any type. + * @param Optional|ResolvesToString|string $timezone The time zone to use to format the date. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onError If $dateFromString encounters an error while parsing the given dateString, it outputs the result value of the provided onError expression. This result value can be of any type. * If you do not specify onError, $dateFromString throws an error if it cannot parse dateString. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. This result value can be of any type. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onNull If the dateString provided to $dateFromString is null or missing, it outputs the result value of the provided onNull expression. This result value can be of any type. * If you do not specify onNull and dateString is null or missing, then $dateFromString outputs null. */ public static function dateFromString( @@ -521,9 +521,9 @@ public static function dateFromString( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateSubtract/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|non-empty-string $unit The unit used to measure the amount of time added to the startDate. + * @param ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. * @param Int64|ResolvesToInt|ResolvesToLong|int $amount - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function dateSubtract( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, @@ -540,7 +540,7 @@ public static function dateSubtract( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToParts/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The input date for which to return parts. date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. * @param Optional|bool $iso8601 If set to true, modifies the output document to use ISO week date fields. Defaults to false. */ public static function dateToParts( @@ -557,10 +557,10 @@ public static function dateToParts( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateToString/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to convert to string. Must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. + * @param Optional|ResolvesToString|string $format The date format specification of the dateString. The format can be any expression that evaluates to a string literal, containing 0 or more format specifiers. * If unspecified, $dateFromString uses "%Y-%m-%dT%H:%M:%S.%LZ" as the default format but accepts a variety of formats and attempts to parse the dateString if possible. - * @param Optional|ResolvesToString|non-empty-string $timezone The time zone to use to format the date. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $onNull The value to return if the date is null or missing. + * @param Optional|ResolvesToString|string $timezone The time zone to use to format the date. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $onNull The value to return if the date is null or missing. * If unspecified, $dateToString returns null if the date is null or missing. */ public static function dateToString( @@ -578,12 +578,12 @@ public static function dateToString( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateTrunc/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to truncate, specified in UTC. The date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|non-empty-string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. + * @param ResolvesToString|string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. * Together, binSize and unit specify the time period used in the $dateTrunc calculation. * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $binSize The numeric time value, specified as an expression that must resolve to a positive non-zero number. Defaults to 1. * Together, binSize and unit specify the time period used in the $dateTrunc calculation. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. - * @param Optional|non-empty-string $startOfWeek The start of the week. Used when + * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|string $startOfWeek The start of the week. Used when * unit is week. Defaults to Sunday. */ public static function dateTrunc( @@ -602,7 +602,7 @@ public static function dateTrunc( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfMonth/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function dayOfMonth( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -617,7 +617,7 @@ public static function dayOfMonth( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfWeek/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function dayOfWeek( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -632,7 +632,7 @@ public static function dayOfWeek( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dayOfYear/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function dayOfYear( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -675,8 +675,8 @@ public static function divide( * Returns true if the values are equivalent. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/eq/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public static function eq( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, @@ -703,7 +703,7 @@ public static function exp(Decimal128|Int64|ResolvesToNumber|float|int $exponent * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/ * @param BSONArray|PackedArray|ResolvesToArray|array $input * @param ResolvesToBool|bool $cond An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. - * @param Optional|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + * @param Optional|string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. * @param Optional|ResolvesToInt|int $limit A number expression that restricts the number of matching array elements that $filter returns. You cannot specify a limit less than 1. The matching array elements are returned in the order they appear in the input array. * If the specified limit is greater than the number of matching array elements, $filter returns all matching array elements. If the limit is null, $filter returns all matching array elements. */ @@ -759,10 +759,10 @@ public static function floor(Decimal128|Int64|ResolvesToNumber|float|int $expres * New in MongoDB 4.4. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/ - * @param Javascript|non-empty-string $body The function definition. You can specify the function definition as either BSON\JavaScript or string. + * @param Javascript|string $body The function definition. You can specify the function definition as either BSON\JavaScript or string. * function(arg1, arg2, ...) { ... } * @param BSONArray|PackedArray|array $args Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ]. - * @param non-empty-string $lang + * @param string $lang */ public static function function( Javascript|string $body, @@ -778,9 +778,9 @@ public static function function( * New in MongoDB 5.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/getField/ - * @param ResolvesToString|non-empty-string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. + * @param ResolvesToString|string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. * If field begins with a dollar sign ($), place the field name inside of a $literal expression to return its value. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input Default: $$CURRENT + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $input Default: $$CURRENT * A valid expression that contains the field for which you want to return a value. input must resolve to an object, missing, null, or undefined. If omitted, defaults to the document currently being processed in the pipeline ($$CURRENT). */ public static function getField( @@ -795,8 +795,8 @@ public static function getField( * Returns true if the first value is greater than the second. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/gt/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public static function gt( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, @@ -810,8 +810,8 @@ public static function gt( * Returns true if the first value is greater than or equal to the second. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/gte/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public static function gte( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, @@ -826,7 +826,7 @@ public static function gte( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/hour/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function hour( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -841,7 +841,7 @@ public static function hour( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ifNull/ * @no-named-arguments - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$expression */ public static function ifNull( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, @@ -854,7 +854,7 @@ public static function ifNull( * Returns a boolean indicating whether a specified value is in an array. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/in/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression Any valid expression expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression Any valid expression expression. * @param BSONArray|PackedArray|ResolvesToArray|array $array Any valid expression that resolves to an array. */ public static function in( @@ -872,7 +872,7 @@ public static function in( * @param BSONArray|PackedArray|ResolvesToArray|array $array Can be any valid expression as long as it resolves to an array. * If the array expression resolves to a value of null or refers to a field that is missing, $indexOfArray returns null. * If the array expression does not resolve to an array or null nor refers to a missing field, $indexOfArray returns an error. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $search + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $search * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. * If unspecified, the starting index position for the search is the beginning of the string. * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. @@ -892,10 +892,10 @@ public static function indexOfArray( * Searches a string for an occurrence of a substring and returns the UTF-8 byte index of the first occurrence. If the substring is not found, returns -1. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfBytes/ - * @param ResolvesToString|non-empty-string $string Can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $string Can be any valid expression as long as it resolves to a string. * If the string expression resolves to a value of null or refers to a field that is missing, $indexOfBytes returns null. * If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfBytes returns an error. - * @param ResolvesToString|non-empty-string $substring Can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $substring Can be any valid expression as long as it resolves to a string. * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. * If unspecified, the starting index position for the search is the beginning of the string. * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. @@ -915,10 +915,10 @@ public static function indexOfBytes( * Searches a string for an occurrence of a substring and returns the UTF-8 code point index of the first occurrence. If the substring is not found, returns -1 * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexOfCP/ - * @param ResolvesToString|non-empty-string $string Can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $string Can be any valid expression as long as it resolves to a string. * If the string expression resolves to a value of null or refers to a field that is missing, $indexOfCP returns null. * If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfCP returns an error. - * @param ResolvesToString|non-empty-string $substring Can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $substring Can be any valid expression as long as it resolves to a string. * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. * If unspecified, the starting index position for the search is the beginning of the string. * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. @@ -940,7 +940,7 @@ public static function indexOfCP( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/ * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input - * @param Optional|ResolvesToString|non-empty-string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @param Optional|ResolvesToString|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ public static function integral( @@ -955,7 +955,7 @@ public static function integral( * Determines if the operand is an array. Returns a boolean. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isArray/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function isArray( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -970,7 +970,7 @@ public static function isArray( * New in MongoDB 4.4. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function isNumber( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -984,7 +984,7 @@ public static function isNumber( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoDayOfWeek/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function isoDayOfWeek( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -999,7 +999,7 @@ public static function isoDayOfWeek( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoWeek/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function isoWeek( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -1014,7 +1014,7 @@ public static function isoWeek( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isoWeekYear/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function isoWeekYear( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -1057,7 +1057,7 @@ public static function lastN( * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/let/ * @param Document|Serializable|array|stdClass $vars Assignment block for the variables accessible in the in expression. To assign a variable, specify a string for the variable name and assign a valid expression for the value. * The variable assignments have no meaning outside the in expression, not even within the vars block itself. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in The expression to evaluate. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $in The expression to evaluate. */ public static function let( Document|Serializable|stdClass|array $vars, @@ -1084,7 +1084,7 @@ public static function linearFill(Decimal128|Int64|ResolvesToNumber|float|int $e * Return a value without parsing. Use for values that the aggregation pipeline may interpret as an expression. For example, use a $literal expression to a string that starts with a dollar sign ($) to avoid parsing as a field path. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/literal/ - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value If the value is an expression, $literal does not evaluate the expression but instead returns the unparsed expression. + * @param Type|array|bool|float|int|null|stdClass|string $value If the value is an expression, $literal does not evaluate the expression but instead returns the unparsed expression. */ public static function literal(Type|stdClass|array|bool|float|int|null|string $value): LiteralOperator { @@ -1109,7 +1109,7 @@ public static function ln(Decimal128|Int64|ResolvesToNumber|float|int $number): * New in MongoDB 5.2. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function locf( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -1148,8 +1148,8 @@ public static function log10(Decimal128|Int64|ResolvesToNumber|float|int $number * Returns true if the first value is less than the second. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lt/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public static function lt( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, @@ -1163,8 +1163,8 @@ public static function lt( * Returns true if the first value is less than or equal to the second. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lte/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public static function lte( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, @@ -1179,8 +1179,8 @@ public static function lte( * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ltrim/ - * @param ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. - * @param Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * @param ResolvesToString|string $input The string to trim. The argument can be any valid expression that resolves to a string. + * @param Optional|ResolvesToString|string $chars The character(s) to trim from the beginning of the input. * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. * If unspecified, $ltrim removes whitespace characters, including the null character. */ @@ -1197,8 +1197,8 @@ public static function ltrim( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/map/ * @param BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to an array. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in An expression that is applied to each element of the input array. The expression references each element individually with the variable name specified in as. - * @param Optional|ResolvesToString|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $in An expression that is applied to each element of the input array. The expression references each element individually with the variable name specified in as. + * @param Optional|ResolvesToString|string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. */ public static function map( PackedArray|ResolvesToArray|BSONArray|array $input, @@ -1215,7 +1215,7 @@ public static function map( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/max/ * @no-named-arguments - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$expression */ public static function max( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, @@ -1249,7 +1249,7 @@ public static function maxN( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/median/ * @param BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. - * @param non-empty-string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. + * @param string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ public static function median( Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input, @@ -1277,7 +1277,7 @@ public static function mergeObjects( * Access available per-document metadata related to the aggregation operation. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/ - * @param non-empty-string $keyword + * @param string $keyword */ public static function meta(string $keyword): MetaOperator { @@ -1289,7 +1289,7 @@ public static function meta(string $keyword): MetaOperator * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/millisecond/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function millisecond( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -1305,7 +1305,7 @@ public static function millisecond( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/min/ * @no-named-arguments - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$expression */ public static function min( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, @@ -1334,7 +1334,7 @@ public static function minN( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/minute/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function minute( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -1364,7 +1364,7 @@ public static function mod( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/month/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function month( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -1391,8 +1391,8 @@ public static function multiply(Decimal128|Int64|ResolvesToNumber|float|int ...$ * Returns true if the values are not equivalent. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ne/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public static function ne( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, @@ -1406,7 +1406,7 @@ public static function ne( * Returns the boolean value that is the opposite of its argument expression. Accepts a single argument expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/not/ - * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|null|stdClass|string $expression */ public static function not( Type|ResolvesToBool|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -1433,7 +1433,7 @@ public static function objectToArray( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/or/ * @no-named-arguments - * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|null|stdClass|string ...$expression */ public static function or( Type|ResolvesToBool|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, @@ -1457,7 +1457,7 @@ public static function or( * @param BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. * @param BSONArray|PackedArray|ResolvesToArray|array $p $percentile calculates a percentile value for each element in p. The elements represent percentages and must evaluate to numeric values in the range 0.0 to 1.0, inclusive. * $percentile returns results in the same order as the elements in p. - * @param non-empty-string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. + * @param string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. */ public static function percentile( Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input, @@ -1541,8 +1541,8 @@ public static function rank(): RankOperator * @param BSONArray|PackedArray|ResolvesToArray|array $input Can be any valid expression that resolves to an array. * If the argument resolves to a value of null or refers to a missing field, $reduce returns null. * If the argument does not resolve to an array or null nor refers to a missing field, $reduce returns an error. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $initialValue The initial cumulative value set before in is applied to the first element of the input array. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in A valid expression that $reduce applies to each element in the input array in left-to-right order. Wrap the input value with $reverseArray to yield the equivalent of applying the combining expression from right-to-left. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $initialValue The initial cumulative value set before in is applied to the first element of the input array. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $in A valid expression that $reduce applies to each element in the input array in left-to-right order. Wrap the input value with $reverseArray to yield the equivalent of applying the combining expression from right-to-left. * During evaluation of the in expression, two variables will be available: * - value is the variable that represents the cumulative value of the expression. * - this is the variable that refers to the element being processed. @@ -1561,9 +1561,9 @@ public static function reduce( * New in MongoDB 4.2. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFind/ - * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. - * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) - * @param Optional|non-empty-string $options + * @param ResolvesToString|string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|string $options */ public static function regexFind( ResolvesToString|string $input, @@ -1579,9 +1579,9 @@ public static function regexFind( * New in MongoDB 4.2. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexFindAll/ - * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. - * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) - * @param Optional|non-empty-string $options + * @param ResolvesToString|string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|string $options */ public static function regexFindAll( ResolvesToString|string $input, @@ -1597,9 +1597,9 @@ public static function regexFindAll( * New in MongoDB 4.2. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/regexMatch/ - * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. - * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) - * @param Optional|non-empty-string $options + * @param ResolvesToString|string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|string $options */ public static function regexMatch( ResolvesToString|string $input, @@ -1616,9 +1616,9 @@ public static function regexMatch( * New in MongoDB 4.4. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceAll/ - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. + * @param ResolvesToNull|ResolvesToString|null|string $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|null|string $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ public static function replaceAll( ResolvesToNull|ResolvesToString|null|string $input, @@ -1634,9 +1634,9 @@ public static function replaceAll( * New in MongoDB 4.4. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceOne/ - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. + * @param ResolvesToNull|ResolvesToString|null|string $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|null|string $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ public static function replaceOne( ResolvesToNull|ResolvesToString|null|string $input, @@ -1678,8 +1678,8 @@ public static function round( * Removes whitespace characters, including null, or the specified characters from the end of a string. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rtrim/ - * @param ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. - * @param Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * @param ResolvesToString|string $input The string to trim. The argument can be any valid expression that resolves to a string. + * @param Optional|ResolvesToString|string $chars The character(s) to trim from the beginning of the input. * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. * If unspecified, $ltrim removes whitespace characters, including the null character. */ @@ -1696,7 +1696,7 @@ public static function rtrim( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/second/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function second( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -1738,9 +1738,9 @@ public static function setEquals(PackedArray|ResolvesToArray|BSONArray|array ... * New in MongoDB 5.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setField/ - * @param ResolvesToString|non-empty-string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. + * @param ResolvesToString|string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. * @param Document|ResolvesToObject|Serializable|array|stdClass $input Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $value The value that you want to assign to field. value can be any valid expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $value The value that you want to assign to field. value can be any valid expression. * Set to $$REMOVE to remove field from the input document. */ public static function setField( @@ -1870,8 +1870,8 @@ public static function sortArray( * Splits a string into substrings based on a delimiter. Returns an array of substrings. If the delimiter is not found within the string, returns an array containing the original string. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/split/ - * @param ResolvesToString|non-empty-string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. - * @param ResolvesToString|non-empty-string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ public static function split(ResolvesToString|string $string, ResolvesToString|string $delimiter): SplitOperator { @@ -1920,8 +1920,8 @@ public static function stdDevSamp(Decimal128|Int64|ResolvesToNumber|float|int .. * Performs case-insensitive string comparison and returns: 0 if two strings are equivalent, 1 if the first string is greater than the second, and -1 if the first string is less than the second. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/strcasecmp/ - * @param ResolvesToString|non-empty-string $expression1 - * @param ResolvesToString|non-empty-string $expression2 + * @param ResolvesToString|string $expression1 + * @param ResolvesToString|string $expression2 */ public static function strcasecmp( ResolvesToString|string $expression1, @@ -1935,7 +1935,7 @@ public static function strcasecmp( * Returns the number of UTF-8 encoded bytes in a string. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenBytes/ - * @param ResolvesToString|non-empty-string $expression + * @param ResolvesToString|string $expression */ public static function strLenBytes(ResolvesToString|string $expression): StrLenBytesOperator { @@ -1946,7 +1946,7 @@ public static function strLenBytes(ResolvesToString|string $expression): StrLenB * Returns the number of UTF-8 code points in a string. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/strLenCP/ - * @param ResolvesToString|non-empty-string $expression + * @param ResolvesToString|string $expression */ public static function strLenCP(ResolvesToString|string $expression): StrLenCPOperator { @@ -1957,7 +1957,7 @@ public static function strLenCP(ResolvesToString|string $expression): StrLenCPOp * Deprecated. Use $substrBytes or $substrCP. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substr/ - * @param ResolvesToString|non-empty-string $string + * @param ResolvesToString|string $string * @param ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". * @param ResolvesToInt|int $length If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. */ @@ -1974,7 +1974,7 @@ public static function substr( * Returns the substring of a string. Starts with the character at the specified UTF-8 byte index (zero-based) in the string and continues for the specified number of bytes. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrBytes/ - * @param ResolvesToString|non-empty-string $string + * @param ResolvesToString|string $string * @param ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". * @param ResolvesToInt|int $length If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. */ @@ -1991,7 +1991,7 @@ public static function substrBytes( * Returns the substring of a string. Starts with the character at the specified UTF-8 code point (CP) index (zero-based) in the string and continues for the number of code points specified. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/substrCP/ - * @param ResolvesToString|non-empty-string $string + * @param ResolvesToString|string $string * @param ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". * @param ResolvesToInt|int $length If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. */ @@ -2040,7 +2040,7 @@ public static function sum(Decimal128|Int64|ResolvesToNumber|float|int ...$expre * - case Can be any valid expression that resolves to a boolean. If the result is not a boolean, it is coerced to a boolean value. More information about how MongoDB evaluates expressions as either true or false can be found here. * - then Can be any valid expression. * The branches array must contain at least one branch document. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $default The path to take if no branch case expression evaluates to true. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $default The path to take if no branch case expression evaluates to true. * Although optional, if default is unspecified and no branch case evaluates to true, $switch returns an error. */ public static function switch( @@ -2080,7 +2080,7 @@ public static function tanh(Decimal128|Int64|ResolvesToNumber|float|int $express * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toBool/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function toBool( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -2094,7 +2094,7 @@ public static function toBool( * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDate/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function toDate( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -2108,7 +2108,7 @@ public static function toDate( * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDecimal/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function toDecimal( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -2122,7 +2122,7 @@ public static function toDecimal( * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDouble/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function toDouble( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -2135,7 +2135,7 @@ public static function toDouble( * Computes and returns the hash value of the input expression using the same hash function that MongoDB uses to create a hashed index. A hash function maps a key or string to a fixed-size numeric value. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toHashedIndexKey/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $value key or string to hash + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $value key or string to hash */ public static function toHashedIndexKey( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $value, @@ -2149,7 +2149,7 @@ public static function toHashedIndexKey( * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toInt/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function toInt( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -2163,7 +2163,7 @@ public static function toInt( * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLong/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function toLong( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -2176,7 +2176,7 @@ public static function toLong( * Converts a string to lowercase. Accepts a single argument expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLower/ - * @param ResolvesToString|non-empty-string $expression + * @param ResolvesToString|string $expression */ public static function toLower(ResolvesToString|string $expression): ToLowerOperator { @@ -2188,7 +2188,7 @@ public static function toLower(ResolvesToString|string $expression): ToLowerOper * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toObjectId/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function toObjectId( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -2202,7 +2202,7 @@ public static function toObjectId( * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toString/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function toString( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -2215,7 +2215,7 @@ public static function toString( * Converts a string to uppercase. Accepts a single argument expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toUpper/ - * @param ResolvesToString|non-empty-string $expression + * @param ResolvesToString|string $expression */ public static function toUpper(ResolvesToString|string $expression): ToUpperOperator { @@ -2227,8 +2227,8 @@ public static function toUpper(ResolvesToString|string $expression): ToUpperOper * New in MongoDB 4.0. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/trim/ - * @param ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. - * @param Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * @param ResolvesToString|string $input The string to trim. The argument can be any valid expression that resolves to a string. + * @param Optional|ResolvesToString|string $chars The character(s) to trim from the beginning of the input. * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. * If unspecified, $ltrim removes whitespace characters, including the null character. */ @@ -2284,7 +2284,7 @@ public static function tsSecond(Timestamp|ResolvesToTimestamp|int $expression): * Return the BSON data type of the field. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/type/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function type( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -2298,7 +2298,7 @@ public static function type( * $unsetField is an alias for $setField using $$REMOVE to remove fields. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unsetField/ - * @param ResolvesToString|non-empty-string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. + * @param ResolvesToString|string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. * @param Document|ResolvesToObject|Serializable|array|stdClass $input Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. */ public static function unsetField( @@ -2314,7 +2314,7 @@ public static function unsetField( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/week/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function week( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, @@ -2329,7 +2329,7 @@ public static function week( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/year/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function year( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/FilterOperator.php b/src/Builder/Expression/FilterOperator.php index 261332a26..6fc372e06 100644 --- a/src/Builder/Expression/FilterOperator.php +++ b/src/Builder/Expression/FilterOperator.php @@ -33,7 +33,7 @@ class FilterOperator implements ResolvesToArray, OperatorInterface /** @var ResolvesToBool|bool $cond An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. */ public readonly ResolvesToBool|bool $cond; - /** @var Optional|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. */ + /** @var Optional|string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. */ public readonly Optional|string $as; /** @@ -45,7 +45,7 @@ class FilterOperator implements ResolvesToArray, OperatorInterface /** * @param BSONArray|PackedArray|ResolvesToArray|array $input * @param ResolvesToBool|bool $cond An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. - * @param Optional|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + * @param Optional|string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. * @param Optional|ResolvesToInt|int $limit A number expression that restricts the number of matching array elements that $filter returns. You cannot specify a limit less than 1. The matching array elements are returned in the order they appear in the input array. * If the specified limit is greater than the number of matching array elements, $filter returns all matching array elements. If the limit is null, $filter returns all matching array elements. */ diff --git a/src/Builder/Expression/FunctionOperator.php b/src/Builder/Expression/FunctionOperator.php index a48d58e05..2a59fd6a3 100644 --- a/src/Builder/Expression/FunctionOperator.php +++ b/src/Builder/Expression/FunctionOperator.php @@ -30,7 +30,7 @@ class FunctionOperator implements ResolvesToAny, OperatorInterface public const ENCODE = Encode::Object; /** - * @var Javascript|non-empty-string $body The function definition. You can specify the function definition as either BSON\JavaScript or string. + * @var Javascript|string $body The function definition. You can specify the function definition as either BSON\JavaScript or string. * function(arg1, arg2, ...) { ... } */ public readonly Javascript|string $body; @@ -38,14 +38,14 @@ class FunctionOperator implements ResolvesToAny, OperatorInterface /** @var BSONArray|PackedArray|array $args Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ]. */ public readonly PackedArray|BSONArray|array $args; - /** @var non-empty-string $lang */ + /** @var string $lang */ public readonly string $lang; /** - * @param Javascript|non-empty-string $body The function definition. You can specify the function definition as either BSON\JavaScript or string. + * @param Javascript|string $body The function definition. You can specify the function definition as either BSON\JavaScript or string. * function(arg1, arg2, ...) { ... } * @param BSONArray|PackedArray|array $args Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ]. - * @param non-empty-string $lang + * @param string $lang */ public function __construct(Javascript|string $body, PackedArray|BSONArray|array $args = [], string $lang = 'js') { diff --git a/src/Builder/Expression/GetFieldOperator.php b/src/Builder/Expression/GetFieldOperator.php index c1b14933e..45007b3fd 100644 --- a/src/Builder/Expression/GetFieldOperator.php +++ b/src/Builder/Expression/GetFieldOperator.php @@ -26,21 +26,21 @@ class GetFieldOperator implements ResolvesToAny, OperatorInterface public const ENCODE = Encode::Object; /** - * @var ResolvesToString|non-empty-string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. + * @var ResolvesToString|string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. * If field begins with a dollar sign ($), place the field name inside of a $literal expression to return its value. */ public readonly ResolvesToString|string $field; /** - * @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input Default: $$CURRENT + * @var Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $input Default: $$CURRENT * A valid expression that contains the field for which you want to return a value. input must resolve to an object, missing, null, or undefined. If omitted, defaults to the document currently being processed in the pipeline ($$CURRENT). */ public readonly Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $input; /** - * @param ResolvesToString|non-empty-string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. + * @param ResolvesToString|string $field Field in the input object for which you want to return a value. field can be any valid expression that resolves to a string constant. * If field begins with a dollar sign ($), place the field name inside of a $literal expression to return its value. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $input Default: $$CURRENT + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $input Default: $$CURRENT * A valid expression that contains the field for which you want to return a value. input must resolve to an object, missing, null, or undefined. If omitted, defaults to the document currently being processed in the pipeline ($$CURRENT). */ public function __construct( diff --git a/src/Builder/Expression/GtOperator.php b/src/Builder/Expression/GtOperator.php index 5252fbf01..1f4287eaa 100644 --- a/src/Builder/Expression/GtOperator.php +++ b/src/Builder/Expression/GtOperator.php @@ -23,15 +23,15 @@ class GtOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public function __construct( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, diff --git a/src/Builder/Expression/GteOperator.php b/src/Builder/Expression/GteOperator.php index 0df0de975..8ed633ae8 100644 --- a/src/Builder/Expression/GteOperator.php +++ b/src/Builder/Expression/GteOperator.php @@ -23,15 +23,15 @@ class GteOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public function __construct( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, diff --git a/src/Builder/Expression/HourOperator.php b/src/Builder/Expression/HourOperator.php index b53c0ed4c..2d0598589 100644 --- a/src/Builder/Expression/HourOperator.php +++ b/src/Builder/Expression/HourOperator.php @@ -27,12 +27,12 @@ class HourOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/IfNullOperator.php b/src/Builder/Expression/IfNullOperator.php index 838b36a38..eae888e69 100644 --- a/src/Builder/Expression/IfNullOperator.php +++ b/src/Builder/Expression/IfNullOperator.php @@ -26,11 +26,11 @@ class IfNullOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ + /** @var list $expression */ public readonly array $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$expression * @no-named-arguments */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) diff --git a/src/Builder/Expression/InOperator.php b/src/Builder/Expression/InOperator.php index 62811a191..90ddab23b 100644 --- a/src/Builder/Expression/InOperator.php +++ b/src/Builder/Expression/InOperator.php @@ -29,14 +29,14 @@ class InOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression Any valid expression expression. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression Any valid expression expression. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** @var BSONArray|PackedArray|ResolvesToArray|array $array Any valid expression that resolves to an array. */ public readonly PackedArray|ResolvesToArray|BSONArray|array $array; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression Any valid expression expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression Any valid expression expression. * @param BSONArray|PackedArray|ResolvesToArray|array $array Any valid expression that resolves to an array. */ public function __construct( diff --git a/src/Builder/Expression/IndexOfArrayOperator.php b/src/Builder/Expression/IndexOfArrayOperator.php index 9d48f69eb..d04497fb5 100644 --- a/src/Builder/Expression/IndexOfArrayOperator.php +++ b/src/Builder/Expression/IndexOfArrayOperator.php @@ -37,7 +37,7 @@ class IndexOfArrayOperator implements ResolvesToInt, OperatorInterface */ public readonly PackedArray|ResolvesToArray|BSONArray|array $array; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $search */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $search */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $search; /** @@ -56,7 +56,7 @@ class IndexOfArrayOperator implements ResolvesToInt, OperatorInterface * @param BSONArray|PackedArray|ResolvesToArray|array $array Can be any valid expression as long as it resolves to an array. * If the array expression resolves to a value of null or refers to a field that is missing, $indexOfArray returns null. * If the array expression does not resolve to an array or null nor refers to a missing field, $indexOfArray returns an error. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $search + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $search * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. * If unspecified, the starting index position for the search is the beginning of the string. * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. diff --git a/src/Builder/Expression/IndexOfBytesOperator.php b/src/Builder/Expression/IndexOfBytesOperator.php index fe696a5f1..ce778fa25 100644 --- a/src/Builder/Expression/IndexOfBytesOperator.php +++ b/src/Builder/Expression/IndexOfBytesOperator.php @@ -22,13 +22,13 @@ class IndexOfBytesOperator implements ResolvesToInt, OperatorInterface public const ENCODE = Encode::Array; /** - * @var ResolvesToString|non-empty-string $string Can be any valid expression as long as it resolves to a string. + * @var ResolvesToString|string $string Can be any valid expression as long as it resolves to a string. * If the string expression resolves to a value of null or refers to a field that is missing, $indexOfBytes returns null. * If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfBytes returns an error. */ public readonly ResolvesToString|string $string; - /** @var ResolvesToString|non-empty-string $substring Can be any valid expression as long as it resolves to a string. */ + /** @var ResolvesToString|string $substring Can be any valid expression as long as it resolves to a string. */ public readonly ResolvesToString|string $substring; /** @@ -44,10 +44,10 @@ class IndexOfBytesOperator implements ResolvesToInt, OperatorInterface public readonly Optional|ResolvesToInt|int $end; /** - * @param ResolvesToString|non-empty-string $string Can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $string Can be any valid expression as long as it resolves to a string. * If the string expression resolves to a value of null or refers to a field that is missing, $indexOfBytes returns null. * If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfBytes returns an error. - * @param ResolvesToString|non-empty-string $substring Can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $substring Can be any valid expression as long as it resolves to a string. * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. * If unspecified, the starting index position for the search is the beginning of the string. * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. diff --git a/src/Builder/Expression/IndexOfCPOperator.php b/src/Builder/Expression/IndexOfCPOperator.php index e6c5632f6..c4fd80d88 100644 --- a/src/Builder/Expression/IndexOfCPOperator.php +++ b/src/Builder/Expression/IndexOfCPOperator.php @@ -22,13 +22,13 @@ class IndexOfCPOperator implements ResolvesToInt, OperatorInterface public const ENCODE = Encode::Array; /** - * @var ResolvesToString|non-empty-string $string Can be any valid expression as long as it resolves to a string. + * @var ResolvesToString|string $string Can be any valid expression as long as it resolves to a string. * If the string expression resolves to a value of null or refers to a field that is missing, $indexOfCP returns null. * If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfCP returns an error. */ public readonly ResolvesToString|string $string; - /** @var ResolvesToString|non-empty-string $substring Can be any valid expression as long as it resolves to a string. */ + /** @var ResolvesToString|string $substring Can be any valid expression as long as it resolves to a string. */ public readonly ResolvesToString|string $substring; /** @@ -44,10 +44,10 @@ class IndexOfCPOperator implements ResolvesToInt, OperatorInterface public readonly Optional|ResolvesToInt|int $end; /** - * @param ResolvesToString|non-empty-string $string Can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $string Can be any valid expression as long as it resolves to a string. * If the string expression resolves to a value of null or refers to a field that is missing, $indexOfCP returns null. * If the string expression does not resolve to a string or null nor refers to a missing field, $indexOfCP returns an error. - * @param ResolvesToString|non-empty-string $substring Can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $substring Can be any valid expression as long as it resolves to a string. * @param Optional|ResolvesToInt|int $start An integer, or a number that can be represented as integers (such as 2.0), that specifies the starting index position for the search. Can be any valid expression that resolves to a non-negative integral number. * If unspecified, the starting index position for the search is the beginning of the string. * @param Optional|ResolvesToInt|int $end An integer, or a number that can be represented as integers (such as 2.0), that specifies the ending index position for the search. Can be any valid expression that resolves to a non-negative integral number. If you specify a index value, you should also specify a index value; otherwise, $indexOfArray uses the value as the index value instead of the value. diff --git a/src/Builder/Expression/IntegralOperator.php b/src/Builder/Expression/IntegralOperator.php index 97f48957c..2f2c6efef 100644 --- a/src/Builder/Expression/IntegralOperator.php +++ b/src/Builder/Expression/IntegralOperator.php @@ -29,14 +29,14 @@ class IntegralOperator implements ResolvesToDouble, ResolvesToDecimal, OperatorI public readonly Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input; /** - * @var Optional|ResolvesToString|non-empty-string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @var Optional|ResolvesToString|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ public readonly Optional|ResolvesToString|string $unit; /** * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input - * @param Optional|ResolvesToString|non-empty-string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @param Optional|ResolvesToString|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ public function __construct( diff --git a/src/Builder/Expression/IsArrayOperator.php b/src/Builder/Expression/IsArrayOperator.php index cf764aa94..bb15c0102 100644 --- a/src/Builder/Expression/IsArrayOperator.php +++ b/src/Builder/Expression/IsArrayOperator.php @@ -23,11 +23,11 @@ class IsArrayOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/IsNumberOperator.php b/src/Builder/Expression/IsNumberOperator.php index 8ccc977a4..9d778f66f 100644 --- a/src/Builder/Expression/IsNumberOperator.php +++ b/src/Builder/Expression/IsNumberOperator.php @@ -25,11 +25,11 @@ class IsNumberOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/IsoDayOfWeekOperator.php b/src/Builder/Expression/IsoDayOfWeekOperator.php index 3f9cb49dd..ac55ea6ba 100644 --- a/src/Builder/Expression/IsoDayOfWeekOperator.php +++ b/src/Builder/Expression/IsoDayOfWeekOperator.php @@ -27,12 +27,12 @@ class IsoDayOfWeekOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/IsoWeekOperator.php b/src/Builder/Expression/IsoWeekOperator.php index 17419af5b..af6cca475 100644 --- a/src/Builder/Expression/IsoWeekOperator.php +++ b/src/Builder/Expression/IsoWeekOperator.php @@ -27,12 +27,12 @@ class IsoWeekOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/IsoWeekYearOperator.php b/src/Builder/Expression/IsoWeekYearOperator.php index d228a3339..a23d90696 100644 --- a/src/Builder/Expression/IsoWeekYearOperator.php +++ b/src/Builder/Expression/IsoWeekYearOperator.php @@ -27,12 +27,12 @@ class IsoWeekYearOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/LetOperator.php b/src/Builder/Expression/LetOperator.php index 82fde5db5..541b0b8ff 100644 --- a/src/Builder/Expression/LetOperator.php +++ b/src/Builder/Expression/LetOperator.php @@ -32,13 +32,13 @@ class LetOperator implements ResolvesToAny, OperatorInterface */ public readonly Document|Serializable|stdClass|array $vars; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in The expression to evaluate. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $in The expression to evaluate. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $in; /** * @param Document|Serializable|array|stdClass $vars Assignment block for the variables accessible in the in expression. To assign a variable, specify a string for the variable name and assign a valid expression for the value. * The variable assignments have no meaning outside the in expression, not even within the vars block itself. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in The expression to evaluate. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $in The expression to evaluate. */ public function __construct( Document|Serializable|stdClass|array $vars, diff --git a/src/Builder/Expression/LiteralOperator.php b/src/Builder/Expression/LiteralOperator.php index c8b97a59f..95e942b2f 100644 --- a/src/Builder/Expression/LiteralOperator.php +++ b/src/Builder/Expression/LiteralOperator.php @@ -22,11 +22,11 @@ class LiteralOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Type|array|bool|float|int|non-empty-string|null|stdClass $value If the value is an expression, $literal does not evaluate the expression but instead returns the unparsed expression. */ + /** @var Type|array|bool|float|int|null|stdClass|string $value If the value is an expression, $literal does not evaluate the expression but instead returns the unparsed expression. */ public readonly Type|stdClass|array|bool|float|int|null|string $value; /** - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value If the value is an expression, $literal does not evaluate the expression but instead returns the unparsed expression. + * @param Type|array|bool|float|int|null|stdClass|string $value If the value is an expression, $literal does not evaluate the expression but instead returns the unparsed expression. */ public function __construct(Type|stdClass|array|bool|float|int|null|string $value) { diff --git a/src/Builder/Expression/LocfOperator.php b/src/Builder/Expression/LocfOperator.php index bd4feef7d..64d437a6d 100644 --- a/src/Builder/Expression/LocfOperator.php +++ b/src/Builder/Expression/LocfOperator.php @@ -25,11 +25,11 @@ class LocfOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/LtOperator.php b/src/Builder/Expression/LtOperator.php index 78e4e12f0..a3cbad988 100644 --- a/src/Builder/Expression/LtOperator.php +++ b/src/Builder/Expression/LtOperator.php @@ -23,15 +23,15 @@ class LtOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public function __construct( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, diff --git a/src/Builder/Expression/LteOperator.php b/src/Builder/Expression/LteOperator.php index dd60b848a..10e3d299a 100644 --- a/src/Builder/Expression/LteOperator.php +++ b/src/Builder/Expression/LteOperator.php @@ -23,15 +23,15 @@ class LteOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public function __construct( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, diff --git a/src/Builder/Expression/LtrimOperator.php b/src/Builder/Expression/LtrimOperator.php index 944ced074..a0f4b7a5a 100644 --- a/src/Builder/Expression/LtrimOperator.php +++ b/src/Builder/Expression/LtrimOperator.php @@ -22,19 +22,19 @@ class LtrimOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. */ + /** @var ResolvesToString|string $input The string to trim. The argument can be any valid expression that resolves to a string. */ public readonly ResolvesToString|string $input; /** - * @var Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * @var Optional|ResolvesToString|string $chars The character(s) to trim from the beginning of the input. * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. * If unspecified, $ltrim removes whitespace characters, including the null character. */ public readonly Optional|ResolvesToString|string $chars; /** - * @param ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. - * @param Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * @param ResolvesToString|string $input The string to trim. The argument can be any valid expression that resolves to a string. + * @param Optional|ResolvesToString|string $chars The character(s) to trim from the beginning of the input. * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. * If unspecified, $ltrim removes whitespace characters, including the null character. */ diff --git a/src/Builder/Expression/MapOperator.php b/src/Builder/Expression/MapOperator.php index a9b9386f3..98274395c 100644 --- a/src/Builder/Expression/MapOperator.php +++ b/src/Builder/Expression/MapOperator.php @@ -33,16 +33,16 @@ class MapOperator implements ResolvesToArray, OperatorInterface /** @var BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to an array. */ public readonly PackedArray|ResolvesToArray|BSONArray|array $input; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in An expression that is applied to each element of the input array. The expression references each element individually with the variable name specified in as. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $in An expression that is applied to each element of the input array. The expression references each element individually with the variable name specified in as. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $in; - /** @var Optional|ResolvesToString|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. */ + /** @var Optional|ResolvesToString|string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. */ public readonly Optional|ResolvesToString|string $as; /** * @param BSONArray|PackedArray|ResolvesToArray|array $input An expression that resolves to an array. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in An expression that is applied to each element of the input array. The expression references each element individually with the variable name specified in as. - * @param Optional|ResolvesToString|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $in An expression that is applied to each element of the input array. The expression references each element individually with the variable name specified in as. + * @param Optional|ResolvesToString|string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. */ public function __construct( PackedArray|ResolvesToArray|BSONArray|array $input, diff --git a/src/Builder/Expression/MaxOperator.php b/src/Builder/Expression/MaxOperator.php index e23940c26..ca5a1cfe4 100644 --- a/src/Builder/Expression/MaxOperator.php +++ b/src/Builder/Expression/MaxOperator.php @@ -27,11 +27,11 @@ class MaxOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ + /** @var list $expression */ public readonly array $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$expression * @no-named-arguments */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) diff --git a/src/Builder/Expression/MedianOperator.php b/src/Builder/Expression/MedianOperator.php index cc5a2ff5a..f9c4f458d 100644 --- a/src/Builder/Expression/MedianOperator.php +++ b/src/Builder/Expression/MedianOperator.php @@ -36,12 +36,12 @@ class MedianOperator implements ResolvesToDouble, OperatorInterface /** @var BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. */ public readonly Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input; - /** @var non-empty-string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ + /** @var string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ public readonly string $method; /** * @param BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $median calculates the 50th percentile value of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $median calculation ignores it. - * @param non-empty-string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. + * @param string $method The method that mongod uses to calculate the 50th percentile value. The method must be 'approximate'. */ public function __construct( Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input, diff --git a/src/Builder/Expression/MetaOperator.php b/src/Builder/Expression/MetaOperator.php index e8b37e308..a02b54ee9 100644 --- a/src/Builder/Expression/MetaOperator.php +++ b/src/Builder/Expression/MetaOperator.php @@ -20,11 +20,11 @@ class MetaOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Single; - /** @var non-empty-string $keyword */ + /** @var string $keyword */ public readonly string $keyword; /** - * @param non-empty-string $keyword + * @param string $keyword */ public function __construct(string $keyword) { diff --git a/src/Builder/Expression/MillisecondOperator.php b/src/Builder/Expression/MillisecondOperator.php index 9fbd11294..63b53b460 100644 --- a/src/Builder/Expression/MillisecondOperator.php +++ b/src/Builder/Expression/MillisecondOperator.php @@ -27,12 +27,12 @@ class MillisecondOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/MinOperator.php b/src/Builder/Expression/MinOperator.php index 6d5d89de5..3ec303a4e 100644 --- a/src/Builder/Expression/MinOperator.php +++ b/src/Builder/Expression/MinOperator.php @@ -27,11 +27,11 @@ class MinOperator implements ResolvesToAny, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ + /** @var list $expression */ public readonly array $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$expression * @no-named-arguments */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) diff --git a/src/Builder/Expression/MinuteOperator.php b/src/Builder/Expression/MinuteOperator.php index aa961cf9a..7ab514ed0 100644 --- a/src/Builder/Expression/MinuteOperator.php +++ b/src/Builder/Expression/MinuteOperator.php @@ -27,12 +27,12 @@ class MinuteOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/MonthOperator.php b/src/Builder/Expression/MonthOperator.php index 9e3ccfd88..531bc946a 100644 --- a/src/Builder/Expression/MonthOperator.php +++ b/src/Builder/Expression/MonthOperator.php @@ -27,12 +27,12 @@ class MonthOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/NeOperator.php b/src/Builder/Expression/NeOperator.php index f2d4d675d..7bc322571 100644 --- a/src/Builder/Expression/NeOperator.php +++ b/src/Builder/Expression/NeOperator.php @@ -23,15 +23,15 @@ class NeOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression2; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression1 - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression2 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression1 + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression2 */ public function __construct( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression1, diff --git a/src/Builder/Expression/NotOperator.php b/src/Builder/Expression/NotOperator.php index 9934c35a2..170538e15 100644 --- a/src/Builder/Expression/NotOperator.php +++ b/src/Builder/Expression/NotOperator.php @@ -23,11 +23,11 @@ class NotOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ResolvesToBool|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct( Type|ResolvesToBool|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, diff --git a/src/Builder/Expression/OrOperator.php b/src/Builder/Expression/OrOperator.php index 16753c635..f544c39a3 100644 --- a/src/Builder/Expression/OrOperator.php +++ b/src/Builder/Expression/OrOperator.php @@ -26,11 +26,11 @@ class OrOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ + /** @var list $expression */ public readonly array $expression; /** - * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression + * @param ExpressionInterface|ResolvesToBool|Type|array|bool|float|int|null|stdClass|string ...$expression * @no-named-arguments */ public function __construct( diff --git a/src/Builder/Expression/PercentileOperator.php b/src/Builder/Expression/PercentileOperator.php index e4f8929c2..981719772 100644 --- a/src/Builder/Expression/PercentileOperator.php +++ b/src/Builder/Expression/PercentileOperator.php @@ -45,14 +45,14 @@ class PercentileOperator implements ResolvesToArray, OperatorInterface */ public readonly PackedArray|ResolvesToArray|BSONArray|array $p; - /** @var non-empty-string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. */ + /** @var string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. */ public readonly string $method; /** * @param BSONArray|Decimal128|Int64|PackedArray|ResolvesToNumber|array|float|int $input $percentile calculates the percentile values of this data. input must be a field name or an expression that evaluates to a numeric type. If the expression cannot be converted to a numeric type, the $percentile calculation ignores it. * @param BSONArray|PackedArray|ResolvesToArray|array $p $percentile calculates a percentile value for each element in p. The elements represent percentages and must evaluate to numeric values in the range 0.0 to 1.0, inclusive. * $percentile returns results in the same order as the elements in p. - * @param non-empty-string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. + * @param string $method The method that mongod uses to calculate the percentile value. The method must be 'approximate'. */ public function __construct( Decimal128|Int64|PackedArray|ResolvesToNumber|BSONArray|array|float|int $input, diff --git a/src/Builder/Expression/ReduceOperator.php b/src/Builder/Expression/ReduceOperator.php index dee41f06e..f57ef2dac 100644 --- a/src/Builder/Expression/ReduceOperator.php +++ b/src/Builder/Expression/ReduceOperator.php @@ -36,11 +36,11 @@ class ReduceOperator implements ResolvesToAny, OperatorInterface */ public readonly PackedArray|ResolvesToArray|BSONArray|array $input; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $initialValue The initial cumulative value set before in is applied to the first element of the input array. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $initialValue The initial cumulative value set before in is applied to the first element of the input array. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $initialValue; /** - * @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in A valid expression that $reduce applies to each element in the input array in left-to-right order. Wrap the input value with $reverseArray to yield the equivalent of applying the combining expression from right-to-left. + * @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $in A valid expression that $reduce applies to each element in the input array in left-to-right order. Wrap the input value with $reverseArray to yield the equivalent of applying the combining expression from right-to-left. * During evaluation of the in expression, two variables will be available: * - value is the variable that represents the cumulative value of the expression. * - this is the variable that refers to the element being processed. @@ -51,8 +51,8 @@ class ReduceOperator implements ResolvesToAny, OperatorInterface * @param BSONArray|PackedArray|ResolvesToArray|array $input Can be any valid expression that resolves to an array. * If the argument resolves to a value of null or refers to a missing field, $reduce returns null. * If the argument does not resolve to an array or null nor refers to a missing field, $reduce returns an error. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $initialValue The initial cumulative value set before in is applied to the first element of the input array. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $in A valid expression that $reduce applies to each element in the input array in left-to-right order. Wrap the input value with $reverseArray to yield the equivalent of applying the combining expression from right-to-left. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $initialValue The initial cumulative value set before in is applied to the first element of the input array. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $in A valid expression that $reduce applies to each element in the input array in left-to-right order. Wrap the input value with $reverseArray to yield the equivalent of applying the combining expression from right-to-left. * During evaluation of the in expression, two variables will be available: * - value is the variable that represents the cumulative value of the expression. * - this is the variable that refers to the element being processed. diff --git a/src/Builder/Expression/RegexFindAllOperator.php b/src/Builder/Expression/RegexFindAllOperator.php index c05bb081a..e4d2a8d0d 100644 --- a/src/Builder/Expression/RegexFindAllOperator.php +++ b/src/Builder/Expression/RegexFindAllOperator.php @@ -23,19 +23,19 @@ class RegexFindAllOperator implements ResolvesToArray, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. */ + /** @var ResolvesToString|string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. */ public readonly ResolvesToString|string $input; - /** @var Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) */ + /** @var Regex|ResolvesToString|string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) */ public readonly Regex|ResolvesToString|string $regex; - /** @var Optional|non-empty-string $options */ + /** @var Optional|string $options */ public readonly Optional|string $options; /** - * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. - * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) - * @param Optional|non-empty-string $options + * @param ResolvesToString|string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|string $options */ public function __construct( ResolvesToString|string $input, diff --git a/src/Builder/Expression/RegexFindOperator.php b/src/Builder/Expression/RegexFindOperator.php index 315a4460d..0b5f30f08 100644 --- a/src/Builder/Expression/RegexFindOperator.php +++ b/src/Builder/Expression/RegexFindOperator.php @@ -23,19 +23,19 @@ class RegexFindOperator implements ResolvesToObject, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. */ + /** @var ResolvesToString|string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. */ public readonly ResolvesToString|string $input; - /** @var Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) */ + /** @var Regex|ResolvesToString|string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) */ public readonly Regex|ResolvesToString|string $regex; - /** @var Optional|non-empty-string $options */ + /** @var Optional|string $options */ public readonly Optional|string $options; /** - * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. - * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) - * @param Optional|non-empty-string $options + * @param ResolvesToString|string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|string $options */ public function __construct( ResolvesToString|string $input, diff --git a/src/Builder/Expression/RegexMatchOperator.php b/src/Builder/Expression/RegexMatchOperator.php index c5acfd66d..ed9707b53 100644 --- a/src/Builder/Expression/RegexMatchOperator.php +++ b/src/Builder/Expression/RegexMatchOperator.php @@ -23,19 +23,19 @@ class RegexMatchOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. */ + /** @var ResolvesToString|string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. */ public readonly ResolvesToString|string $input; - /** @var Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) */ + /** @var Regex|ResolvesToString|string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) */ public readonly Regex|ResolvesToString|string $regex; - /** @var Optional|non-empty-string $options */ + /** @var Optional|string $options */ public readonly Optional|string $options; /** - * @param ResolvesToString|non-empty-string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. - * @param Regex|ResolvesToString|non-empty-string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) - * @param Optional|non-empty-string $options + * @param ResolvesToString|string $input The string on which you wish to apply the regex pattern. Can be a string or any valid expression that resolves to a string. + * @param Regex|ResolvesToString|string $regex The regex pattern to apply. Can be any valid expression that resolves to either a string or regex pattern //. When using the regex //, you can also specify the regex options i and m (but not the s or x options) + * @param Optional|string $options */ public function __construct( ResolvesToString|string $input, diff --git a/src/Builder/Expression/ReplaceAllOperator.php b/src/Builder/Expression/ReplaceAllOperator.php index f38b4c259..d2fa2a602 100644 --- a/src/Builder/Expression/ReplaceAllOperator.php +++ b/src/Builder/Expression/ReplaceAllOperator.php @@ -22,19 +22,19 @@ class ReplaceAllOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToNull|ResolvesToString|non-empty-string|null $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. */ + /** @var ResolvesToNull|ResolvesToString|null|string $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. */ public readonly ResolvesToNull|ResolvesToString|null|string $input; - /** @var ResolvesToNull|ResolvesToString|non-empty-string|null $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. */ + /** @var ResolvesToNull|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. */ public readonly ResolvesToNull|ResolvesToString|null|string $find; - /** @var ResolvesToNull|ResolvesToString|non-empty-string|null $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ + /** @var ResolvesToNull|ResolvesToString|null|string $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ public readonly ResolvesToNull|ResolvesToString|null|string $replacement; /** - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. + * @param ResolvesToNull|ResolvesToString|null|string $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|null|string $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ public function __construct( ResolvesToNull|ResolvesToString|null|string $input, diff --git a/src/Builder/Expression/ReplaceOneOperator.php b/src/Builder/Expression/ReplaceOneOperator.php index 8d005ba70..609a0060d 100644 --- a/src/Builder/Expression/ReplaceOneOperator.php +++ b/src/Builder/Expression/ReplaceOneOperator.php @@ -21,19 +21,19 @@ class ReplaceOneOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToNull|ResolvesToString|non-empty-string|null $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. */ + /** @var ResolvesToNull|ResolvesToString|null|string $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. */ public readonly ResolvesToNull|ResolvesToString|null|string $input; - /** @var ResolvesToNull|ResolvesToString|non-empty-string|null $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. */ + /** @var ResolvesToNull|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. */ public readonly ResolvesToNull|ResolvesToString|null|string $find; - /** @var ResolvesToNull|ResolvesToString|non-empty-string|null $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ + /** @var ResolvesToNull|ResolvesToString|null|string $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ public readonly ResolvesToNull|ResolvesToString|null|string $replacement; /** - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. - * @param ResolvesToNull|ResolvesToString|non-empty-string|null $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. + * @param ResolvesToNull|ResolvesToString|null|string $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. + * @param ResolvesToNull|ResolvesToString|null|string $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ public function __construct( ResolvesToNull|ResolvesToString|null|string $input, diff --git a/src/Builder/Expression/RtrimOperator.php b/src/Builder/Expression/RtrimOperator.php index 039f791c3..f9a6bc145 100644 --- a/src/Builder/Expression/RtrimOperator.php +++ b/src/Builder/Expression/RtrimOperator.php @@ -21,19 +21,19 @@ class RtrimOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. */ + /** @var ResolvesToString|string $input The string to trim. The argument can be any valid expression that resolves to a string. */ public readonly ResolvesToString|string $input; /** - * @var Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * @var Optional|ResolvesToString|string $chars The character(s) to trim from the beginning of the input. * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. * If unspecified, $ltrim removes whitespace characters, including the null character. */ public readonly Optional|ResolvesToString|string $chars; /** - * @param ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. - * @param Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * @param ResolvesToString|string $input The string to trim. The argument can be any valid expression that resolves to a string. + * @param Optional|ResolvesToString|string $chars The character(s) to trim from the beginning of the input. * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. * If unspecified, $ltrim removes whitespace characters, including the null character. */ diff --git a/src/Builder/Expression/SecondOperator.php b/src/Builder/Expression/SecondOperator.php index 08587bb75..4c9da6db6 100644 --- a/src/Builder/Expression/SecondOperator.php +++ b/src/Builder/Expression/SecondOperator.php @@ -27,12 +27,12 @@ class SecondOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/SetFieldOperator.php b/src/Builder/Expression/SetFieldOperator.php index 635d61082..f4449ded1 100644 --- a/src/Builder/Expression/SetFieldOperator.php +++ b/src/Builder/Expression/SetFieldOperator.php @@ -26,22 +26,22 @@ class SetFieldOperator implements ResolvesToObject, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToString|non-empty-string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. */ + /** @var ResolvesToString|string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. */ public readonly ResolvesToString|string $field; /** @var Document|ResolvesToObject|Serializable|array|stdClass $input Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. */ public readonly Document|Serializable|ResolvesToObject|stdClass|array $input; /** - * @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $value The value that you want to assign to field. value can be any valid expression. + * @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $value The value that you want to assign to field. value can be any valid expression. * Set to $$REMOVE to remove field from the input document. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $value; /** - * @param ResolvesToString|non-empty-string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. + * @param ResolvesToString|string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. * @param Document|ResolvesToObject|Serializable|array|stdClass $input Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $value The value that you want to assign to field. value can be any valid expression. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $value The value that you want to assign to field. value can be any valid expression. * Set to $$REMOVE to remove field from the input document. */ public function __construct( diff --git a/src/Builder/Expression/SplitOperator.php b/src/Builder/Expression/SplitOperator.php index b5aa192e4..6453cbfe5 100644 --- a/src/Builder/Expression/SplitOperator.php +++ b/src/Builder/Expression/SplitOperator.php @@ -20,15 +20,15 @@ class SplitOperator implements ResolvesToArray, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ResolvesToString|non-empty-string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. */ + /** @var ResolvesToString|string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. */ public readonly ResolvesToString|string $string; - /** @var ResolvesToString|non-empty-string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ + /** @var ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ public readonly ResolvesToString|string $delimiter; /** - * @param ResolvesToString|non-empty-string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. - * @param ResolvesToString|non-empty-string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. + * @param ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ public function __construct(ResolvesToString|string $string, ResolvesToString|string $delimiter) { diff --git a/src/Builder/Expression/StrLenBytesOperator.php b/src/Builder/Expression/StrLenBytesOperator.php index e12308161..1f859fce9 100644 --- a/src/Builder/Expression/StrLenBytesOperator.php +++ b/src/Builder/Expression/StrLenBytesOperator.php @@ -20,11 +20,11 @@ class StrLenBytesOperator implements ResolvesToInt, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ResolvesToString|non-empty-string $expression */ + /** @var ResolvesToString|string $expression */ public readonly ResolvesToString|string $expression; /** - * @param ResolvesToString|non-empty-string $expression + * @param ResolvesToString|string $expression */ public function __construct(ResolvesToString|string $expression) { diff --git a/src/Builder/Expression/StrLenCPOperator.php b/src/Builder/Expression/StrLenCPOperator.php index 6dec4b7ae..7b63f98ff 100644 --- a/src/Builder/Expression/StrLenCPOperator.php +++ b/src/Builder/Expression/StrLenCPOperator.php @@ -20,11 +20,11 @@ class StrLenCPOperator implements ResolvesToInt, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ResolvesToString|non-empty-string $expression */ + /** @var ResolvesToString|string $expression */ public readonly ResolvesToString|string $expression; /** - * @param ResolvesToString|non-empty-string $expression + * @param ResolvesToString|string $expression */ public function __construct(ResolvesToString|string $expression) { diff --git a/src/Builder/Expression/StrcasecmpOperator.php b/src/Builder/Expression/StrcasecmpOperator.php index 013f7cb79..ff6f54423 100644 --- a/src/Builder/Expression/StrcasecmpOperator.php +++ b/src/Builder/Expression/StrcasecmpOperator.php @@ -20,15 +20,15 @@ class StrcasecmpOperator implements ResolvesToInt, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ResolvesToString|non-empty-string $expression1 */ + /** @var ResolvesToString|string $expression1 */ public readonly ResolvesToString|string $expression1; - /** @var ResolvesToString|non-empty-string $expression2 */ + /** @var ResolvesToString|string $expression2 */ public readonly ResolvesToString|string $expression2; /** - * @param ResolvesToString|non-empty-string $expression1 - * @param ResolvesToString|non-empty-string $expression2 + * @param ResolvesToString|string $expression1 + * @param ResolvesToString|string $expression2 */ public function __construct(ResolvesToString|string $expression1, ResolvesToString|string $expression2) { diff --git a/src/Builder/Expression/SubstrBytesOperator.php b/src/Builder/Expression/SubstrBytesOperator.php index 7b8803ab1..3ff54354c 100644 --- a/src/Builder/Expression/SubstrBytesOperator.php +++ b/src/Builder/Expression/SubstrBytesOperator.php @@ -20,7 +20,7 @@ class SubstrBytesOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ResolvesToString|non-empty-string $string */ + /** @var ResolvesToString|string $string */ public readonly ResolvesToString|string $string; /** @var ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". */ @@ -30,7 +30,7 @@ class SubstrBytesOperator implements ResolvesToString, OperatorInterface public readonly ResolvesToInt|int $length; /** - * @param ResolvesToString|non-empty-string $string + * @param ResolvesToString|string $string * @param ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". * @param ResolvesToInt|int $length If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. */ diff --git a/src/Builder/Expression/SubstrCPOperator.php b/src/Builder/Expression/SubstrCPOperator.php index 6f32d3d26..85baea910 100644 --- a/src/Builder/Expression/SubstrCPOperator.php +++ b/src/Builder/Expression/SubstrCPOperator.php @@ -20,7 +20,7 @@ class SubstrCPOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ResolvesToString|non-empty-string $string */ + /** @var ResolvesToString|string $string */ public readonly ResolvesToString|string $string; /** @var ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". */ @@ -30,7 +30,7 @@ class SubstrCPOperator implements ResolvesToString, OperatorInterface public readonly ResolvesToInt|int $length; /** - * @param ResolvesToString|non-empty-string $string + * @param ResolvesToString|string $string * @param ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". * @param ResolvesToInt|int $length If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. */ diff --git a/src/Builder/Expression/SubstrOperator.php b/src/Builder/Expression/SubstrOperator.php index c432f4aaf..9ef174060 100644 --- a/src/Builder/Expression/SubstrOperator.php +++ b/src/Builder/Expression/SubstrOperator.php @@ -20,7 +20,7 @@ class SubstrOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Array; - /** @var ResolvesToString|non-empty-string $string */ + /** @var ResolvesToString|string $string */ public readonly ResolvesToString|string $string; /** @var ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". */ @@ -30,7 +30,7 @@ class SubstrOperator implements ResolvesToString, OperatorInterface public readonly ResolvesToInt|int $length; /** - * @param ResolvesToString|non-empty-string $string + * @param ResolvesToString|string $string * @param ResolvesToInt|int $start If start is a negative number, $substr returns an empty string "". * @param ResolvesToInt|int $length If length is a negative number, $substr returns a substring that starts at the specified index and includes the rest of the string. */ diff --git a/src/Builder/Expression/SwitchOperator.php b/src/Builder/Expression/SwitchOperator.php index 93e942dfb..3545477ea 100644 --- a/src/Builder/Expression/SwitchOperator.php +++ b/src/Builder/Expression/SwitchOperator.php @@ -39,7 +39,7 @@ class SwitchOperator implements ResolvesToAny, OperatorInterface public readonly PackedArray|BSONArray|array $branches; /** - * @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $default The path to take if no branch case expression evaluates to true. + * @var Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $default The path to take if no branch case expression evaluates to true. * Although optional, if default is unspecified and no branch case evaluates to true, $switch returns an error. */ public readonly Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $default; @@ -49,7 +49,7 @@ class SwitchOperator implements ResolvesToAny, OperatorInterface * - case Can be any valid expression that resolves to a boolean. If the result is not a boolean, it is coerced to a boolean value. More information about how MongoDB evaluates expressions as either true or false can be found here. * - then Can be any valid expression. * The branches array must contain at least one branch document. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $default The path to take if no branch case expression evaluates to true. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $default The path to take if no branch case expression evaluates to true. * Although optional, if default is unspecified and no branch case evaluates to true, $switch returns an error. */ public function __construct( diff --git a/src/Builder/Expression/ToBoolOperator.php b/src/Builder/Expression/ToBoolOperator.php index 3aaa7089a..2986fc8d3 100644 --- a/src/Builder/Expression/ToBoolOperator.php +++ b/src/Builder/Expression/ToBoolOperator.php @@ -24,11 +24,11 @@ class ToBoolOperator implements ResolvesToBool, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/ToDateOperator.php b/src/Builder/Expression/ToDateOperator.php index 605f41b4a..d6ff4b2bd 100644 --- a/src/Builder/Expression/ToDateOperator.php +++ b/src/Builder/Expression/ToDateOperator.php @@ -24,11 +24,11 @@ class ToDateOperator implements ResolvesToDate, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/ToDecimalOperator.php b/src/Builder/Expression/ToDecimalOperator.php index 7af799af0..d8dd60379 100644 --- a/src/Builder/Expression/ToDecimalOperator.php +++ b/src/Builder/Expression/ToDecimalOperator.php @@ -24,11 +24,11 @@ class ToDecimalOperator implements ResolvesToDecimal, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/ToDoubleOperator.php b/src/Builder/Expression/ToDoubleOperator.php index 2e78b0707..a21fe9ad5 100644 --- a/src/Builder/Expression/ToDoubleOperator.php +++ b/src/Builder/Expression/ToDoubleOperator.php @@ -24,11 +24,11 @@ class ToDoubleOperator implements ResolvesToDouble, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/ToHashedIndexKeyOperator.php b/src/Builder/Expression/ToHashedIndexKeyOperator.php index 1bc3a5ec7..e3038832a 100644 --- a/src/Builder/Expression/ToHashedIndexKeyOperator.php +++ b/src/Builder/Expression/ToHashedIndexKeyOperator.php @@ -23,11 +23,11 @@ class ToHashedIndexKeyOperator implements ResolvesToLong, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $value key or string to hash */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $value key or string to hash */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $value; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $value key or string to hash + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $value key or string to hash */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $value) { diff --git a/src/Builder/Expression/ToIntOperator.php b/src/Builder/Expression/ToIntOperator.php index d74106a57..d03980572 100644 --- a/src/Builder/Expression/ToIntOperator.php +++ b/src/Builder/Expression/ToIntOperator.php @@ -24,11 +24,11 @@ class ToIntOperator implements ResolvesToInt, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/ToLongOperator.php b/src/Builder/Expression/ToLongOperator.php index 5eadcc6ae..301113251 100644 --- a/src/Builder/Expression/ToLongOperator.php +++ b/src/Builder/Expression/ToLongOperator.php @@ -24,11 +24,11 @@ class ToLongOperator implements ResolvesToLong, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/ToLowerOperator.php b/src/Builder/Expression/ToLowerOperator.php index 1ab21491c..d680f3296 100644 --- a/src/Builder/Expression/ToLowerOperator.php +++ b/src/Builder/Expression/ToLowerOperator.php @@ -20,11 +20,11 @@ class ToLowerOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ResolvesToString|non-empty-string $expression */ + /** @var ResolvesToString|string $expression */ public readonly ResolvesToString|string $expression; /** - * @param ResolvesToString|non-empty-string $expression + * @param ResolvesToString|string $expression */ public function __construct(ResolvesToString|string $expression) { diff --git a/src/Builder/Expression/ToObjectIdOperator.php b/src/Builder/Expression/ToObjectIdOperator.php index 57dbae3c6..8e204f67a 100644 --- a/src/Builder/Expression/ToObjectIdOperator.php +++ b/src/Builder/Expression/ToObjectIdOperator.php @@ -24,11 +24,11 @@ class ToObjectIdOperator implements ResolvesToObjectId, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/ToStringOperator.php b/src/Builder/Expression/ToStringOperator.php index f7ec7c077..c549a6f22 100644 --- a/src/Builder/Expression/ToStringOperator.php +++ b/src/Builder/Expression/ToStringOperator.php @@ -24,11 +24,11 @@ class ToStringOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/ToUpperOperator.php b/src/Builder/Expression/ToUpperOperator.php index e943a61b0..f57c2cbbf 100644 --- a/src/Builder/Expression/ToUpperOperator.php +++ b/src/Builder/Expression/ToUpperOperator.php @@ -20,11 +20,11 @@ class ToUpperOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ResolvesToString|non-empty-string $expression */ + /** @var ResolvesToString|string $expression */ public readonly ResolvesToString|string $expression; /** - * @param ResolvesToString|non-empty-string $expression + * @param ResolvesToString|string $expression */ public function __construct(ResolvesToString|string $expression) { diff --git a/src/Builder/Expression/TrimOperator.php b/src/Builder/Expression/TrimOperator.php index 6c6ca574a..def9d3a5a 100644 --- a/src/Builder/Expression/TrimOperator.php +++ b/src/Builder/Expression/TrimOperator.php @@ -22,19 +22,19 @@ class TrimOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. */ + /** @var ResolvesToString|string $input The string to trim. The argument can be any valid expression that resolves to a string. */ public readonly ResolvesToString|string $input; /** - * @var Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * @var Optional|ResolvesToString|string $chars The character(s) to trim from the beginning of the input. * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. * If unspecified, $ltrim removes whitespace characters, including the null character. */ public readonly Optional|ResolvesToString|string $chars; /** - * @param ResolvesToString|non-empty-string $input The string to trim. The argument can be any valid expression that resolves to a string. - * @param Optional|ResolvesToString|non-empty-string $chars The character(s) to trim from the beginning of the input. + * @param ResolvesToString|string $input The string to trim. The argument can be any valid expression that resolves to a string. + * @param Optional|ResolvesToString|string $chars The character(s) to trim from the beginning of the input. * The argument can be any valid expression that resolves to a string. The $ltrim operator breaks down the string into individual UTF code point to trim from input. * If unspecified, $ltrim removes whitespace characters, including the null character. */ diff --git a/src/Builder/Expression/TypeOperator.php b/src/Builder/Expression/TypeOperator.php index d1e134b6e..a65d1dd2e 100644 --- a/src/Builder/Expression/TypeOperator.php +++ b/src/Builder/Expression/TypeOperator.php @@ -23,11 +23,11 @@ class TypeOperator implements ResolvesToString, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Expression/UnsetFieldOperator.php b/src/Builder/Expression/UnsetFieldOperator.php index df1a20d5b..45e9c41e2 100644 --- a/src/Builder/Expression/UnsetFieldOperator.php +++ b/src/Builder/Expression/UnsetFieldOperator.php @@ -24,14 +24,14 @@ class UnsetFieldOperator implements ResolvesToObject, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ResolvesToString|non-empty-string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. */ + /** @var ResolvesToString|string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. */ public readonly ResolvesToString|string $field; /** @var Document|ResolvesToObject|Serializable|array|stdClass $input Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. */ public readonly Document|Serializable|ResolvesToObject|stdClass|array $input; /** - * @param ResolvesToString|non-empty-string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. + * @param ResolvesToString|string $field Field in the input object that you want to add, update, or remove. field can be any valid expression that resolves to a string constant. * @param Document|ResolvesToObject|Serializable|array|stdClass $input Document that contains the field that you want to add or update. input must resolve to an object, missing, null, or undefined. */ public function __construct( diff --git a/src/Builder/Expression/WeekOperator.php b/src/Builder/Expression/WeekOperator.php index 96ba4cc57..a24ee935d 100644 --- a/src/Builder/Expression/WeekOperator.php +++ b/src/Builder/Expression/WeekOperator.php @@ -27,12 +27,12 @@ class WeekOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Expression/YearOperator.php b/src/Builder/Expression/YearOperator.php index 191975c28..c2b869260 100644 --- a/src/Builder/Expression/YearOperator.php +++ b/src/Builder/Expression/YearOperator.php @@ -27,12 +27,12 @@ class YearOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; - /** @var Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ + /** @var Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to which the operator is applied. date must be a valid expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param Optional|ResolvesToString|non-empty-string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. + * @param Optional|ResolvesToString|string $timezone The timezone of the operation result. timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, diff --git a/src/Builder/Projection/FactoryTrait.php b/src/Builder/Projection/FactoryTrait.php index ea87adac2..d3f1d2382 100644 --- a/src/Builder/Projection/FactoryTrait.php +++ b/src/Builder/Projection/FactoryTrait.php @@ -38,7 +38,7 @@ public static function elemMatch(QueryInterface|array $query): ElemMatchOperator * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/ * @param BSONArray|PackedArray|ResolvesToArray|array $input * @param ResolvesToBool|bool $cond An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. - * @param Optional|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + * @param Optional|string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. * @param Optional|ResolvesToInt|int $limit A number expression that restricts the number of matching array elements that $filter returns. You cannot specify a limit less than 1. The matching array elements are returned in the order they appear in the input array. * If the specified limit is greater than the number of matching array elements, $filter returns all matching array elements. If the limit is null, $filter returns all matching array elements. */ diff --git a/src/Builder/Projection/FilterOperator.php b/src/Builder/Projection/FilterOperator.php index ee7cbb40e..3b29e287d 100644 --- a/src/Builder/Projection/FilterOperator.php +++ b/src/Builder/Projection/FilterOperator.php @@ -37,7 +37,7 @@ class FilterOperator implements ProjectionInterface, OperatorInterface /** @var ResolvesToBool|bool $cond An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. */ public readonly ResolvesToBool|bool $cond; - /** @var Optional|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. */ + /** @var Optional|string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. */ public readonly Optional|string $as; /** @@ -49,7 +49,7 @@ class FilterOperator implements ProjectionInterface, OperatorInterface /** * @param BSONArray|PackedArray|ResolvesToArray|array $input * @param ResolvesToBool|bool $cond An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. - * @param Optional|non-empty-string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. + * @param Optional|string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. * @param Optional|ResolvesToInt|int $limit A number expression that restricts the number of matching array elements that $filter returns. You cannot specify a limit less than 1. The matching array elements are returned in the order they appear in the input array. * If the specified limit is greater than the number of matching array elements, $filter returns all matching array elements. If the limit is null, $filter returns all matching array elements. */ diff --git a/src/Builder/Query/AllOperator.php b/src/Builder/Query/AllOperator.php index 537f74ffa..1b51961fd 100644 --- a/src/Builder/Query/AllOperator.php +++ b/src/Builder/Query/AllOperator.php @@ -26,11 +26,11 @@ class AllOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $value */ + /** @var list $value */ public readonly array $value; /** - * @param FieldQueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$value + * @param FieldQueryInterface|Type|array|bool|float|int|null|stdClass|string ...$value * @no-named-arguments */ public function __construct(Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string ...$value) diff --git a/src/Builder/Query/BitsAllClearOperator.php b/src/Builder/Query/BitsAllClearOperator.php index 61221fac3..6f056d30b 100644 --- a/src/Builder/Query/BitsAllClearOperator.php +++ b/src/Builder/Query/BitsAllClearOperator.php @@ -28,11 +28,11 @@ class BitsAllClearOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask */ + /** @var BSONArray|Binary|PackedArray|array|int|string $bitmask */ public readonly Binary|PackedArray|BSONArray|array|int|string $bitmask; /** - * @param BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask + * @param BSONArray|Binary|PackedArray|array|int|string $bitmask */ public function __construct(Binary|PackedArray|BSONArray|array|int|string $bitmask) { diff --git a/src/Builder/Query/BitsAllSetOperator.php b/src/Builder/Query/BitsAllSetOperator.php index 9d2e1d972..0278b81ee 100644 --- a/src/Builder/Query/BitsAllSetOperator.php +++ b/src/Builder/Query/BitsAllSetOperator.php @@ -28,11 +28,11 @@ class BitsAllSetOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask */ + /** @var BSONArray|Binary|PackedArray|array|int|string $bitmask */ public readonly Binary|PackedArray|BSONArray|array|int|string $bitmask; /** - * @param BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask + * @param BSONArray|Binary|PackedArray|array|int|string $bitmask */ public function __construct(Binary|PackedArray|BSONArray|array|int|string $bitmask) { diff --git a/src/Builder/Query/BitsAnyClearOperator.php b/src/Builder/Query/BitsAnyClearOperator.php index de3a52a89..c2b899ea7 100644 --- a/src/Builder/Query/BitsAnyClearOperator.php +++ b/src/Builder/Query/BitsAnyClearOperator.php @@ -28,11 +28,11 @@ class BitsAnyClearOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask */ + /** @var BSONArray|Binary|PackedArray|array|int|string $bitmask */ public readonly Binary|PackedArray|BSONArray|array|int|string $bitmask; /** - * @param BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask + * @param BSONArray|Binary|PackedArray|array|int|string $bitmask */ public function __construct(Binary|PackedArray|BSONArray|array|int|string $bitmask) { diff --git a/src/Builder/Query/BitsAnySetOperator.php b/src/Builder/Query/BitsAnySetOperator.php index b5a5b701e..4dcf3c33e 100644 --- a/src/Builder/Query/BitsAnySetOperator.php +++ b/src/Builder/Query/BitsAnySetOperator.php @@ -28,11 +28,11 @@ class BitsAnySetOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask */ + /** @var BSONArray|Binary|PackedArray|array|int|string $bitmask */ public readonly Binary|PackedArray|BSONArray|array|int|string $bitmask; /** - * @param BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask + * @param BSONArray|Binary|PackedArray|array|int|string $bitmask */ public function __construct(Binary|PackedArray|BSONArray|array|int|string $bitmask) { diff --git a/src/Builder/Query/CommentOperator.php b/src/Builder/Query/CommentOperator.php index c99b096d9..121c0e3b6 100644 --- a/src/Builder/Query/CommentOperator.php +++ b/src/Builder/Query/CommentOperator.php @@ -21,11 +21,11 @@ class CommentOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var non-empty-string $comment */ + /** @var string $comment */ public readonly string $comment; /** - * @param non-empty-string $comment + * @param string $comment */ public function __construct(string $comment) { diff --git a/src/Builder/Query/ElemMatchOperator.php b/src/Builder/Query/ElemMatchOperator.php index 97a02c32d..073619874 100644 --- a/src/Builder/Query/ElemMatchOperator.php +++ b/src/Builder/Query/ElemMatchOperator.php @@ -27,11 +27,11 @@ class ElemMatchOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var FieldQueryInterface|QueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $query */ + /** @var FieldQueryInterface|QueryInterface|Type|array|bool|float|int|null|stdClass|string $query */ public readonly Type|FieldQueryInterface|QueryInterface|stdClass|array|bool|float|int|null|string $query; /** - * @param FieldQueryInterface|QueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $query + * @param FieldQueryInterface|QueryInterface|Type|array|bool|float|int|null|stdClass|string $query */ public function __construct( Type|FieldQueryInterface|QueryInterface|stdClass|array|bool|float|int|null|string $query, diff --git a/src/Builder/Query/EqOperator.php b/src/Builder/Query/EqOperator.php index 1ed738f2f..2dcd605fd 100644 --- a/src/Builder/Query/EqOperator.php +++ b/src/Builder/Query/EqOperator.php @@ -23,11 +23,11 @@ class EqOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Type|array|bool|float|int|non-empty-string|null|stdClass $value */ + /** @var Type|array|bool|float|int|null|stdClass|string $value */ public readonly Type|stdClass|array|bool|float|int|null|string $value; /** - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public function __construct(Type|stdClass|array|bool|float|int|null|string $value) { diff --git a/src/Builder/Query/ExprOperator.php b/src/Builder/Query/ExprOperator.php index 98f35a751..a6235cb67 100644 --- a/src/Builder/Query/ExprOperator.php +++ b/src/Builder/Query/ExprOperator.php @@ -24,11 +24,11 @@ class ExprOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index 6f01589c9..cc480de67 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -36,7 +36,7 @@ trait FactoryTrait * * @see https://www.mongodb.com/docs/manual/reference/operator/query/all/ * @no-named-arguments - * @param FieldQueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$value + * @param FieldQueryInterface|Type|array|bool|float|int|null|stdClass|string ...$value */ public static function all( Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string ...$value, @@ -61,7 +61,7 @@ public static function and(QueryInterface|array ...$queries): AndOperator * Matches numeric or binary values in which a set of bit positions all have a value of 0. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/bitsAllClear/ - * @param BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask + * @param BSONArray|Binary|PackedArray|array|int|string $bitmask */ public static function bitsAllClear(Binary|PackedArray|BSONArray|array|int|string $bitmask): BitsAllClearOperator { @@ -72,7 +72,7 @@ public static function bitsAllClear(Binary|PackedArray|BSONArray|array|int|strin * Matches numeric or binary values in which a set of bit positions all have a value of 1. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/bitsAllSet/ - * @param BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask + * @param BSONArray|Binary|PackedArray|array|int|string $bitmask */ public static function bitsAllSet(Binary|PackedArray|BSONArray|array|int|string $bitmask): BitsAllSetOperator { @@ -83,7 +83,7 @@ public static function bitsAllSet(Binary|PackedArray|BSONArray|array|int|string * Matches numeric or binary values in which any bit from a set of bit positions has a value of 0. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/bitsAnyClear/ - * @param BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask + * @param BSONArray|Binary|PackedArray|array|int|string $bitmask */ public static function bitsAnyClear(Binary|PackedArray|BSONArray|array|int|string $bitmask): BitsAnyClearOperator { @@ -94,7 +94,7 @@ public static function bitsAnyClear(Binary|PackedArray|BSONArray|array|int|strin * Matches numeric or binary values in which any bit from a set of bit positions has a value of 1. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/bitsAnySet/ - * @param BSONArray|Binary|PackedArray|array|int|non-empty-string $bitmask + * @param BSONArray|Binary|PackedArray|array|int|string $bitmask */ public static function bitsAnySet(Binary|PackedArray|BSONArray|array|int|string $bitmask): BitsAnySetOperator { @@ -138,7 +138,7 @@ public static function centerSphere(PackedArray|BSONArray|array $value): CenterS * Adds a comment to a query predicate. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/comment/ - * @param non-empty-string $comment + * @param string $comment */ public static function comment(string $comment): CommentOperator { @@ -149,7 +149,7 @@ public static function comment(string $comment): CommentOperator * The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/elemMatch/ - * @param FieldQueryInterface|QueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $query + * @param FieldQueryInterface|QueryInterface|Type|array|bool|float|int|null|stdClass|string $query */ public static function elemMatch( Type|FieldQueryInterface|QueryInterface|stdClass|array|bool|float|int|null|string $query, @@ -162,7 +162,7 @@ public static function elemMatch( * Matches values that are equal to a specified value. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/eq/ - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public static function eq(Type|stdClass|array|bool|float|int|null|string $value): EqOperator { @@ -184,7 +184,7 @@ public static function exists(bool $exists): ExistsOperator * Allows use of aggregation expressions within the query language. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/expr/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function expr( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -210,7 +210,7 @@ public static function geoIntersects( * Specifies a geometry in GeoJSON format to geospatial query operators. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/geometry/ - * @param non-empty-string $type + * @param string $type * @param BSONArray|PackedArray|array $coordinates * @param Optional|Document|Serializable|array|stdClass $crs */ @@ -240,7 +240,7 @@ public static function geoWithin( * Matches values that are greater than a specified value. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/gt/ - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public static function gt(Type|stdClass|array|bool|float|int|null|string $value): GtOperator { @@ -251,7 +251,7 @@ public static function gt(Type|stdClass|array|bool|float|int|null|string $value) * Matches values that are greater than or equal to a specified value. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/gte/ - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public static function gte(Type|stdClass|array|bool|float|int|null|string $value): GteOperator { @@ -284,7 +284,7 @@ public static function jsonSchema(Document|Serializable|stdClass|array $schema): * Matches values that are less than a specified value. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/lt/ - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public static function lt(Type|stdClass|array|bool|float|int|null|string $value): LtOperator { @@ -295,7 +295,7 @@ public static function lt(Type|stdClass|array|bool|float|int|null|string $value) * Matches values that are less than or equal to a specified value. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/lte/ - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public static function lte(Type|stdClass|array|bool|float|int|null|string $value): LteOperator { @@ -353,7 +353,7 @@ public static function natural(): NaturalOperator * Matches all values that are not equal to a specified value. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/ne/ - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public static function ne(Type|stdClass|array|bool|float|int|null|string $value): NeOperator { @@ -421,7 +421,7 @@ public static function nor(QueryInterface|array ...$queries): NorOperator * Inverts the effect of a query expression and returns documents that do not match the query expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/not/ - * @param FieldQueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param FieldQueryInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function not( Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression, @@ -501,8 +501,8 @@ public static function size(int $value): SizeOperator * Performs text search. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/text/ - * @param non-empty-string $search A string of terms that MongoDB parses and uses to query the text index. MongoDB performs a logical OR search of the terms unless specified as a phrase. - * @param Optional|non-empty-string $language The language that determines the list of stop words for the search and the rules for the stemmer and tokenizer. If not specified, the search uses the default language of the index. + * @param string $search A string of terms that MongoDB parses and uses to query the text index. MongoDB performs a logical OR search of the terms unless specified as a phrase. + * @param Optional|string $language The language that determines the list of stop words for the search and the rules for the stemmer and tokenizer. If not specified, the search uses the default language of the index. * If you specify a default_language value of none, then the text index parses through each word in the field, including stop words, and ignores suffix stemming. * @param Optional|bool $caseSensitive A boolean flag to enable or disable case sensitive search. Defaults to false; i.e. the search defers to the case insensitivity of the text index. * @param Optional|bool $diacriticSensitive A boolean flag to enable or disable diacritic sensitive search against version 3 text indexes. Defaults to false; i.e. the search defers to the diacritic insensitivity of the text index. @@ -523,7 +523,7 @@ public static function text( * * @see https://www.mongodb.com/docs/manual/reference/operator/query/type/ * @no-named-arguments - * @param int|non-empty-string ...$type + * @param int|string ...$type */ public static function type(int|string ...$type): TypeOperator { @@ -534,7 +534,7 @@ public static function type(int|string ...$type): TypeOperator * Matches documents that satisfy a JavaScript expression. * * @see https://www.mongodb.com/docs/manual/reference/operator/query/where/ - * @param Javascript|non-empty-string $function + * @param Javascript|string $function */ public static function where(Javascript|string $function): WhereOperator { diff --git a/src/Builder/Query/GeometryOperator.php b/src/Builder/Query/GeometryOperator.php index b9970da12..efdd38a1f 100644 --- a/src/Builder/Query/GeometryOperator.php +++ b/src/Builder/Query/GeometryOperator.php @@ -31,7 +31,7 @@ class GeometryOperator implements GeometryInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var non-empty-string $type */ + /** @var string $type */ public readonly string $type; /** @var BSONArray|PackedArray|array $coordinates */ @@ -41,7 +41,7 @@ class GeometryOperator implements GeometryInterface, OperatorInterface public readonly Optional|Document|Serializable|stdClass|array $crs; /** - * @param non-empty-string $type + * @param string $type * @param BSONArray|PackedArray|array $coordinates * @param Optional|Document|Serializable|array|stdClass $crs */ diff --git a/src/Builder/Query/GtOperator.php b/src/Builder/Query/GtOperator.php index 515c0abdd..31c03e485 100644 --- a/src/Builder/Query/GtOperator.php +++ b/src/Builder/Query/GtOperator.php @@ -23,11 +23,11 @@ class GtOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Type|array|bool|float|int|non-empty-string|null|stdClass $value */ + /** @var Type|array|bool|float|int|null|stdClass|string $value */ public readonly Type|stdClass|array|bool|float|int|null|string $value; /** - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public function __construct(Type|stdClass|array|bool|float|int|null|string $value) { diff --git a/src/Builder/Query/GteOperator.php b/src/Builder/Query/GteOperator.php index b8e06f860..2ef771f1d 100644 --- a/src/Builder/Query/GteOperator.php +++ b/src/Builder/Query/GteOperator.php @@ -23,11 +23,11 @@ class GteOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Type|array|bool|float|int|non-empty-string|null|stdClass $value */ + /** @var Type|array|bool|float|int|null|stdClass|string $value */ public readonly Type|stdClass|array|bool|float|int|null|string $value; /** - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public function __construct(Type|stdClass|array|bool|float|int|null|string $value) { diff --git a/src/Builder/Query/LtOperator.php b/src/Builder/Query/LtOperator.php index aab7dc8b8..f29c73b57 100644 --- a/src/Builder/Query/LtOperator.php +++ b/src/Builder/Query/LtOperator.php @@ -23,11 +23,11 @@ class LtOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Type|array|bool|float|int|non-empty-string|null|stdClass $value */ + /** @var Type|array|bool|float|int|null|stdClass|string $value */ public readonly Type|stdClass|array|bool|float|int|null|string $value; /** - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public function __construct(Type|stdClass|array|bool|float|int|null|string $value) { diff --git a/src/Builder/Query/LteOperator.php b/src/Builder/Query/LteOperator.php index 2fc005269..18453cb58 100644 --- a/src/Builder/Query/LteOperator.php +++ b/src/Builder/Query/LteOperator.php @@ -23,11 +23,11 @@ class LteOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Type|array|bool|float|int|non-empty-string|null|stdClass $value */ + /** @var Type|array|bool|float|int|null|stdClass|string $value */ public readonly Type|stdClass|array|bool|float|int|null|string $value; /** - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public function __construct(Type|stdClass|array|bool|float|int|null|string $value) { diff --git a/src/Builder/Query/NeOperator.php b/src/Builder/Query/NeOperator.php index 71521a7ad..9c12c851c 100644 --- a/src/Builder/Query/NeOperator.php +++ b/src/Builder/Query/NeOperator.php @@ -23,11 +23,11 @@ class NeOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Type|array|bool|float|int|non-empty-string|null|stdClass $value */ + /** @var Type|array|bool|float|int|null|stdClass|string $value */ public readonly Type|stdClass|array|bool|float|int|null|string $value; /** - * @param Type|array|bool|float|int|non-empty-string|null|stdClass $value + * @param Type|array|bool|float|int|null|stdClass|string $value */ public function __construct(Type|stdClass|array|bool|float|int|null|string $value) { diff --git a/src/Builder/Query/NotOperator.php b/src/Builder/Query/NotOperator.php index 6c48f9308..c6d135445 100644 --- a/src/Builder/Query/NotOperator.php +++ b/src/Builder/Query/NotOperator.php @@ -23,11 +23,11 @@ class NotOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var FieldQueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var FieldQueryInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param FieldQueryInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param FieldQueryInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Query/TextOperator.php b/src/Builder/Query/TextOperator.php index 1fbdd65e1..47b88ede1 100644 --- a/src/Builder/Query/TextOperator.php +++ b/src/Builder/Query/TextOperator.php @@ -22,11 +22,11 @@ class TextOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::DollarObject; - /** @var non-empty-string $search A string of terms that MongoDB parses and uses to query the text index. MongoDB performs a logical OR search of the terms unless specified as a phrase. */ + /** @var string $search A string of terms that MongoDB parses and uses to query the text index. MongoDB performs a logical OR search of the terms unless specified as a phrase. */ public readonly string $search; /** - * @var Optional|non-empty-string $language The language that determines the list of stop words for the search and the rules for the stemmer and tokenizer. If not specified, the search uses the default language of the index. + * @var Optional|string $language The language that determines the list of stop words for the search and the rules for the stemmer and tokenizer. If not specified, the search uses the default language of the index. * If you specify a default_language value of none, then the text index parses through each word in the field, including stop words, and ignores suffix stemming. */ public readonly Optional|string $language; @@ -41,8 +41,8 @@ class TextOperator implements QueryInterface, OperatorInterface public readonly Optional|bool $diacriticSensitive; /** - * @param non-empty-string $search A string of terms that MongoDB parses and uses to query the text index. MongoDB performs a logical OR search of the terms unless specified as a phrase. - * @param Optional|non-empty-string $language The language that determines the list of stop words for the search and the rules for the stemmer and tokenizer. If not specified, the search uses the default language of the index. + * @param string $search A string of terms that MongoDB parses and uses to query the text index. MongoDB performs a logical OR search of the terms unless specified as a phrase. + * @param Optional|string $language The language that determines the list of stop words for the search and the rules for the stemmer and tokenizer. If not specified, the search uses the default language of the index. * If you specify a default_language value of none, then the text index parses through each word in the field, including stop words, and ignores suffix stemming. * @param Optional|bool $caseSensitive A boolean flag to enable or disable case sensitive search. Defaults to false; i.e. the search defers to the case insensitivity of the text index. * @param Optional|bool $diacriticSensitive A boolean flag to enable or disable diacritic sensitive search against version 3 text indexes. Defaults to false; i.e. the search defers to the diacritic insensitivity of the text index. diff --git a/src/Builder/Query/TypeOperator.php b/src/Builder/Query/TypeOperator.php index d59e899e2..1f839c09a 100644 --- a/src/Builder/Query/TypeOperator.php +++ b/src/Builder/Query/TypeOperator.php @@ -24,11 +24,11 @@ class TypeOperator implements FieldQueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $type */ + /** @var list $type */ public readonly array $type; /** - * @param int|non-empty-string ...$type + * @param int|string ...$type * @no-named-arguments */ public function __construct(int|string ...$type) diff --git a/src/Builder/Query/WhereOperator.php b/src/Builder/Query/WhereOperator.php index 8be4a96dc..5a38eb8c6 100644 --- a/src/Builder/Query/WhereOperator.php +++ b/src/Builder/Query/WhereOperator.php @@ -24,11 +24,11 @@ class WhereOperator implements QueryInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Javascript|non-empty-string $function */ + /** @var Javascript|string $function */ public readonly Javascript|string $function; /** - * @param Javascript|non-empty-string $function + * @param Javascript|string $function */ public function __construct(Javascript|string $function) { diff --git a/src/Builder/Stage/AddFieldsStage.php b/src/Builder/Stage/AddFieldsStage.php index 02a8440d1..8e2e346d5 100644 --- a/src/Builder/Stage/AddFieldsStage.php +++ b/src/Builder/Stage/AddFieldsStage.php @@ -27,11 +27,11 @@ class AddFieldsStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var stdClass $expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. */ + /** @var stdClass $expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. */ public readonly stdClass $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) { diff --git a/src/Builder/Stage/BucketAutoStage.php b/src/Builder/Stage/BucketAutoStage.php index 40dd3fd91..341421f44 100644 --- a/src/Builder/Stage/BucketAutoStage.php +++ b/src/Builder/Stage/BucketAutoStage.php @@ -27,7 +27,7 @@ class BucketAutoStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $groupBy; /** @var int $buckets A positive 32-bit integer that specifies the number of buckets into which input documents are grouped. */ @@ -46,7 +46,7 @@ class BucketAutoStage implements StageInterface, OperatorInterface public readonly Optional|Document|Serializable|stdClass|array $granularity; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. * @param int $buckets A positive 32-bit integer that specifies the number of buckets into which input documents are grouped. * @param Optional|Document|Serializable|array|stdClass $output A document that specifies the fields to include in the output documents in addition to the _id field. To specify the field to include, you must use accumulator expressions. * The default count field is not included in the output document when output is specified. Explicitly specify the count expression as part of the output document to include it. diff --git a/src/Builder/Stage/BucketStage.php b/src/Builder/Stage/BucketStage.php index 53955c734..47a497153 100644 --- a/src/Builder/Stage/BucketStage.php +++ b/src/Builder/Stage/BucketStage.php @@ -34,7 +34,7 @@ class BucketStage implements StageInterface, OperatorInterface public const ENCODE = Encode::Object; /** - * @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. + * @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. * Unless $bucket includes a default specification, each input document must resolve the groupBy field path or expression to a value that falls within one of the ranges specified by the boundaries. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $groupBy; @@ -46,7 +46,7 @@ class BucketStage implements StageInterface, OperatorInterface public readonly PackedArray|BSONArray|array $boundaries; /** - * @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $default A literal that specifies the _id of an additional bucket that contains all documents whose groupBy expression result does not fall into a bucket specified by boundaries. + * @var Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $default A literal that specifies the _id of an additional bucket that contains all documents whose groupBy expression result does not fall into a bucket specified by boundaries. * If unspecified, each input document must resolve the groupBy expression to a value within one of the bucket ranges specified by boundaries or the operation throws an error. * The default value must be less than the lowest boundaries value, or greater than or equal to the highest boundaries value. * The default value can be of a different type than the entries in boundaries. @@ -61,11 +61,11 @@ class BucketStage implements StageInterface, OperatorInterface public readonly Optional|Document|Serializable|stdClass|array $output; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. * Unless $bucket includes a default specification, each input document must resolve the groupBy field path or expression to a value that falls within one of the ranges specified by the boundaries. * @param BSONArray|PackedArray|array $boundaries An array of values based on the groupBy expression that specify the boundaries for each bucket. Each adjacent pair of values acts as the inclusive lower boundary and the exclusive upper boundary for the bucket. You must specify at least two boundaries. * The specified values must be in ascending order and all of the same type. The exception is if the values are of mixed numeric types, such as: - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $default A literal that specifies the _id of an additional bucket that contains all documents whose groupBy expression result does not fall into a bucket specified by boundaries. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $default A literal that specifies the _id of an additional bucket that contains all documents whose groupBy expression result does not fall into a bucket specified by boundaries. * If unspecified, each input document must resolve the groupBy expression to a value within one of the bucket ranges specified by boundaries or the operation throws an error. * The default value must be less than the lowest boundaries value, or greater than or equal to the highest boundaries value. * The default value can be of a different type than the entries in boundaries. diff --git a/src/Builder/Stage/ChangeStreamStage.php b/src/Builder/Stage/ChangeStreamStage.php index 551842a86..184ae96ee 100644 --- a/src/Builder/Stage/ChangeStreamStage.php +++ b/src/Builder/Stage/ChangeStreamStage.php @@ -29,10 +29,10 @@ class ChangeStreamStage implements StageInterface, OperatorInterface /** @var Optional|bool $allChangesForCluster A flag indicating whether the stream should report all changes that occur on the deployment, aside from those on internal databases or collections. */ public readonly Optional|bool $allChangesForCluster; - /** @var Optional|non-empty-string $fullDocument Specifies whether change notifications include a copy of the full document when modified by update operations. */ + /** @var Optional|string $fullDocument Specifies whether change notifications include a copy of the full document when modified by update operations. */ public readonly Optional|string $fullDocument; - /** @var Optional|non-empty-string $fullDocumentBeforeChange Valid values are "off", "whenAvailable", or "required". If set to "off", the "fullDocumentBeforeChange" field of the output document is always omitted. If set to "whenAvailable", the "fullDocumentBeforeChange" field will be populated with the pre-image of the document modified by the current change event if such a pre-image is available, and will be omitted otherwise. If set to "required", then the "fullDocumentBeforeChange" field is always populated and an exception is thrown if the pre-image is not available. */ + /** @var Optional|string $fullDocumentBeforeChange Valid values are "off", "whenAvailable", or "required". If set to "off", the "fullDocumentBeforeChange" field of the output document is always omitted. If set to "whenAvailable", the "fullDocumentBeforeChange" field will be populated with the pre-image of the document modified by the current change event if such a pre-image is available, and will be omitted otherwise. If set to "required", then the "fullDocumentBeforeChange" field is always populated and an exception is thrown if the pre-image is not available. */ public readonly Optional|string $fullDocumentBeforeChange; /** @var Optional|int $resumeAfter Specifies a resume token as the logical starting point for the change stream. Cannot be used with startAfter or startAtOperationTime fields. */ @@ -52,8 +52,8 @@ class ChangeStreamStage implements StageInterface, OperatorInterface /** * @param Optional|bool $allChangesForCluster A flag indicating whether the stream should report all changes that occur on the deployment, aside from those on internal databases or collections. - * @param Optional|non-empty-string $fullDocument Specifies whether change notifications include a copy of the full document when modified by update operations. - * @param Optional|non-empty-string $fullDocumentBeforeChange Valid values are "off", "whenAvailable", or "required". If set to "off", the "fullDocumentBeforeChange" field of the output document is always omitted. If set to "whenAvailable", the "fullDocumentBeforeChange" field will be populated with the pre-image of the document modified by the current change event if such a pre-image is available, and will be omitted otherwise. If set to "required", then the "fullDocumentBeforeChange" field is always populated and an exception is thrown if the pre-image is not available. + * @param Optional|string $fullDocument Specifies whether change notifications include a copy of the full document when modified by update operations. + * @param Optional|string $fullDocumentBeforeChange Valid values are "off", "whenAvailable", or "required". If set to "off", the "fullDocumentBeforeChange" field of the output document is always omitted. If set to "whenAvailable", the "fullDocumentBeforeChange" field will be populated with the pre-image of the document modified by the current change event if such a pre-image is available, and will be omitted otherwise. If set to "required", then the "fullDocumentBeforeChange" field is always populated and an exception is thrown if the pre-image is not available. * @param Optional|int $resumeAfter Specifies a resume token as the logical starting point for the change stream. Cannot be used with startAfter or startAtOperationTime fields. * @param Optional|bool $showExpandedEvents Specifies whether to include additional change events, such as such as DDL and index operations. * New in MongoDB 6.0. diff --git a/src/Builder/Stage/CountStage.php b/src/Builder/Stage/CountStage.php index fec4a28da..3864f00c5 100644 --- a/src/Builder/Stage/CountStage.php +++ b/src/Builder/Stage/CountStage.php @@ -22,11 +22,11 @@ class CountStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var non-empty-string $field Name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character. */ + /** @var string $field Name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character. */ public readonly string $field; /** - * @param non-empty-string $field Name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character. + * @param string $field Name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character. */ public function __construct(string $field) { diff --git a/src/Builder/Stage/DensifyStage.php b/src/Builder/Stage/DensifyStage.php index cf4fc2092..3abfcb3fe 100644 --- a/src/Builder/Stage/DensifyStage.php +++ b/src/Builder/Stage/DensifyStage.php @@ -32,7 +32,7 @@ class DensifyStage implements StageInterface, OperatorInterface public const ENCODE = Encode::Object; /** - * @var non-empty-string $field The field to densify. The values of the specified field must either be all numeric values or all dates. + * @var string $field The field to densify. The values of the specified field must either be all numeric values or all dates. * Documents that do not contain the specified field continue through the pipeline unmodified. * To specify a in an embedded document or in an array, use dot notation. */ @@ -45,7 +45,7 @@ class DensifyStage implements StageInterface, OperatorInterface public readonly Optional|PackedArray|BSONArray|array $partitionByFields; /** - * @param non-empty-string $field The field to densify. The values of the specified field must either be all numeric values or all dates. + * @param string $field The field to densify. The values of the specified field must either be all numeric values or all dates. * Documents that do not contain the specified field continue through the pipeline unmodified. * To specify a in an embedded document or in an array, use dot notation. * @param Document|Serializable|array|stdClass $range Specification for range based densification. diff --git a/src/Builder/Stage/FactoryTrait.php b/src/Builder/Stage/FactoryTrait.php index 4441381c5..012fe99b4 100644 --- a/src/Builder/Stage/FactoryTrait.php +++ b/src/Builder/Stage/FactoryTrait.php @@ -37,7 +37,7 @@ trait FactoryTrait * Adds new fields to documents. Outputs documents that contain all existing fields from the input documents and newly added fields. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. */ public static function addFields( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, @@ -50,11 +50,11 @@ public static function addFields( * Categorizes incoming documents into groups, called buckets, based on a specified expression and bucket boundaries. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucket/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. * Unless $bucket includes a default specification, each input document must resolve the groupBy field path or expression to a value that falls within one of the ranges specified by the boundaries. * @param BSONArray|PackedArray|array $boundaries An array of values based on the groupBy expression that specify the boundaries for each bucket. Each adjacent pair of values acts as the inclusive lower boundary and the exclusive upper boundary for the bucket. You must specify at least two boundaries. * The specified values must be in ascending order and all of the same type. The exception is if the values are of mixed numeric types, such as: - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $default A literal that specifies the _id of an additional bucket that contains all documents whose groupBy expression result does not fall into a bucket specified by boundaries. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $default A literal that specifies the _id of an additional bucket that contains all documents whose groupBy expression result does not fall into a bucket specified by boundaries. * If unspecified, each input document must resolve the groupBy expression to a value within one of the bucket ranges specified by boundaries or the operation throws an error. * The default value must be less than the lowest boundaries value, or greater than or equal to the highest boundaries value. * The default value can be of a different type than the entries in boundaries. @@ -76,7 +76,7 @@ public static function bucket( * Categorizes incoming documents into a specific number of groups, called buckets, based on a specified expression. Bucket boundaries are automatically determined in an attempt to evenly distribute the documents into the specified number of buckets. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucketAuto/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. * @param int $buckets A positive 32-bit integer that specifies the number of buckets into which input documents are grouped. * @param Optional|Document|Serializable|array|stdClass $output A document that specifies the fields to include in the output documents in addition to the _id field. To specify the field to include, you must use accumulator expressions. * The default count field is not included in the output document when output is specified. Explicitly specify the count expression as part of the output document to include it. @@ -98,8 +98,8 @@ public static function bucketAuto( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/changeStream/ * @param Optional|bool $allChangesForCluster A flag indicating whether the stream should report all changes that occur on the deployment, aside from those on internal databases or collections. - * @param Optional|non-empty-string $fullDocument Specifies whether change notifications include a copy of the full document when modified by update operations. - * @param Optional|non-empty-string $fullDocumentBeforeChange Valid values are "off", "whenAvailable", or "required". If set to "off", the "fullDocumentBeforeChange" field of the output document is always omitted. If set to "whenAvailable", the "fullDocumentBeforeChange" field will be populated with the pre-image of the document modified by the current change event if such a pre-image is available, and will be omitted otherwise. If set to "required", then the "fullDocumentBeforeChange" field is always populated and an exception is thrown if the pre-image is not available. + * @param Optional|string $fullDocument Specifies whether change notifications include a copy of the full document when modified by update operations. + * @param Optional|string $fullDocumentBeforeChange Valid values are "off", "whenAvailable", or "required". If set to "off", the "fullDocumentBeforeChange" field of the output document is always omitted. If set to "whenAvailable", the "fullDocumentBeforeChange" field will be populated with the pre-image of the document modified by the current change event if such a pre-image is available, and will be omitted otherwise. If set to "required", then the "fullDocumentBeforeChange" field is always populated and an exception is thrown if the pre-image is not available. * @param Optional|int $resumeAfter Specifies a resume token as the logical starting point for the change stream. Cannot be used with startAfter or startAtOperationTime fields. * @param Optional|bool $showExpandedEvents Specifies whether to include additional change events, such as such as DDL and index operations. * New in MongoDB 6.0. @@ -146,7 +146,7 @@ public static function collStats(Document|Serializable|stdClass|array $config): * Distinct from the $count aggregation accumulator. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/count/ - * @param non-empty-string $field Name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character. + * @param string $field Name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character. */ public static function count(string $field): CountStage { @@ -167,7 +167,7 @@ public static function currentOp(): CurrentOpStage * Creates new documents in a sequence of documents where certain values in a field are missing. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/densify/ - * @param non-empty-string $field The field to densify. The values of the specified field must either be all numeric values or all dates. + * @param string $field The field to densify. The values of the specified field must either be all numeric values or all dates. * Documents that do not contain the specified field continue through the pipeline unmodified. * To specify a in an embedded document or in an array, use dot notation. * @param Document|Serializable|array|stdClass $range Specification for range based densification. @@ -214,7 +214,7 @@ public static function facet(PackedArray|Pipeline|BSONArray|array ...$facet): Fa * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/ * @param Document|Serializable|array|stdClass $output Specifies an object containing each field for which to fill missing values. You can specify multiple fields in the output object. * The object name is the name of the field to fill. The object value specifies how the field is filled. - * @param Optional|Document|Serializable|array|non-empty-string|stdClass $partitionBy Specifies an expression to group the documents. In the $fill stage, a group of documents is known as a partition. + * @param Optional|Document|Serializable|array|stdClass|string $partitionBy Specifies an expression to group the documents. In the $fill stage, a group of documents is known as a partition. * If you omit partitionBy and partitionByFields, $fill uses one partition for the entire collection. * partitionBy and partitionByFields are mutually exclusive. * @param Optional|BSONArray|PackedArray|array $partitionByFields Specifies an array of fields as the compound key to group the documents. In the $fill stage, each group of documents is known as a partition. @@ -236,11 +236,11 @@ public static function fill( * Returns an ordered stream of documents based on the proximity to a geospatial point. Incorporates the functionality of $match, $sort, and $limit for geospatial data. The output documents include an additional distance field and can include a location identifier field. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/ - * @param non-empty-string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. + * @param string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. * @param Document|Serializable|array|stdClass $near The point for which to find the closest documents. * @param Optional|Decimal128|Int64|float|int $distanceMultiplier The factor to multiply all distances returned by the query. For example, use the distanceMultiplier to convert radians, as returned by a spherical query, to kilometers by multiplying by the radius of the Earth. - * @param Optional|non-empty-string $includeLocs This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. - * @param Optional|non-empty-string $key Specify the geospatial indexed field to use when calculating the distance. + * @param Optional|string $includeLocs This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. + * @param Optional|string $key Specify the geospatial indexed field to use when calculating the distance. * @param Optional|Decimal128|Int64|float|int $maxDistance The maximum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall within the specified distance from the center point. * Specify the distance in meters if the specified point is GeoJSON and in radians if the specified point is legacy coordinate pairs. * @param Optional|Decimal128|Int64|float|int $minDistance The minimum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall outside the specified distance from the center point. @@ -271,14 +271,14 @@ public static function geoNear( * Performs a recursive search on a collection. To each output document, adds a new array field that contains the traversal results of the recursive search for that document. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/ - * @param non-empty-string $from Target collection for the $graphLookup operation to search, recursively matching the connectFromField to the connectToField. The from collection must be in the same database as any other collections used in the operation. + * @param string $from Target collection for the $graphLookup operation to search, recursively matching the connectFromField to the connectToField. The from collection must be in the same database as any other collections used in the operation. * Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. - * @param BSONArray|ExpressionInterface|PackedArray|Type|array|bool|float|int|non-empty-string|null|stdClass $startWith Expression that specifies the value of the connectFromField with which to start the recursive search. Optionally, startWith may be array of values, each of which is individually followed through the traversal process. - * @param non-empty-string $connectFromField Field name whose value $graphLookup uses to recursively match against the connectToField of other documents in the collection. If the value is an array, each element is individually followed through the traversal process. - * @param non-empty-string $connectToField Field name in other documents against which to match the value of the field specified by the connectFromField parameter. - * @param non-empty-string $as Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. + * @param BSONArray|ExpressionInterface|PackedArray|Type|array|bool|float|int|null|stdClass|string $startWith Expression that specifies the value of the connectFromField with which to start the recursive search. Optionally, startWith may be array of values, each of which is individually followed through the traversal process. + * @param string $connectFromField Field name whose value $graphLookup uses to recursively match against the connectToField of other documents in the collection. If the value is an array, each element is individually followed through the traversal process. + * @param string $connectToField Field name in other documents against which to match the value of the field specified by the connectFromField parameter. + * @param string $as Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. * @param Optional|int $maxDepth Non-negative integral number specifying the maximum recursion depth. - * @param Optional|non-empty-string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. + * @param Optional|string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. * @param Optional|QueryInterface|array $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. */ public static function graphLookup( @@ -299,7 +299,7 @@ public static function graphLookup( * Groups input documents by a specified identifier expression and applies the accumulator expression(s), if specified, to each group. Consumes all input documents and outputs one document per each distinct group. The output documents only contain the identifier field and, if specified, accumulated fields. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $_id The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $_id The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. * @param AccumulatorInterface|Document|Serializable|array|stdClass ...$field Computed using the accumulator operators. */ public static function group( @@ -350,7 +350,7 @@ public static function listLocalSessions( * Lists sampled queries for all collections or a specific collection. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSampledQueries/ - * @param Optional|non-empty-string $namespace + * @param Optional|string $namespace */ public static function listSampledQueries( Optional|string $namespace = Optional::Undefined, @@ -363,8 +363,8 @@ public static function listSampledQueries( * Returns information about existing Atlas Search indexes on a specified collection. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/ - * @param Optional|non-empty-string $id The id of the index to return information about. - * @param Optional|non-empty-string $name The name of the index to return information about. + * @param Optional|string $id The id of the index to return information about. + * @param Optional|string $name The name of the index to return information about. */ public static function listSearchIndexes( Optional|string $id = Optional::Undefined, @@ -393,12 +393,12 @@ public static function listSessions( * Performs a left outer join to another collection in the same database to filter in documents from the "joined" collection for processing. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/ - * @param non-empty-string $as Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten. - * @param Optional|non-empty-string $from Specifies the collection in the same database to perform the join with. + * @param string $as Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten. + * @param Optional|string $from Specifies the collection in the same database to perform the join with. * from is optional, you can use a $documents stage in a $lookup stage instead. For an example, see Use a $documents Stage in a $lookup Stage. * Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. - * @param Optional|non-empty-string $localField Specifies the field from the documents input to the $lookup stage. $lookup performs an equality match on the localField to the foreignField from the documents of the from collection. If an input document does not contain the localField, the $lookup treats the field as having a value of null for matching purposes. - * @param Optional|non-empty-string $foreignField Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. + * @param Optional|string $localField Specifies the field from the documents input to the $lookup stage. $lookup performs an equality match on the localField to the foreignField from the documents of the from collection. If an input document does not contain the localField, the $lookup treats the field as having a value of null for matching purposes. + * @param Optional|string $foreignField Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. * @param Optional|Document|Serializable|array|stdClass $let Specifies variables to use in the pipeline stages. Use the variable expressions to access the fields from the joined collection's documents that are input to the pipeline. * @param Optional|BSONArray|PackedArray|Pipeline|array $pipeline Specifies the pipeline to run on the joined collection. The pipeline determines the resulting documents from the joined collection. To return all documents, specify an empty pipeline []. * The pipeline cannot include the $out stage or the $merge stage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. @@ -432,11 +432,11 @@ public static function match(QueryInterface|array $query): MatchStage * New in MongoDB 4.2. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/ - * @param non-empty-string $into The output collection. - * @param Optional|BSONArray|PackedArray|array|non-empty-string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. + * @param string $into The output collection. + * @param Optional|BSONArray|PackedArray|array|string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. * @param Optional|Document|Serializable|array|stdClass $let Specifies variables for use in the whenMatched pipeline. - * @param Optional|non-empty-string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). - * @param Optional|non-empty-string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. + * @param Optional|string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). + * @param Optional|string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. */ public static function merge( string $into, @@ -453,8 +453,8 @@ public static function merge( * Writes the resulting documents of the aggregation pipeline to a collection. To use the $out stage, it must be the last stage in the pipeline. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/ - * @param non-empty-string $db Target collection name to write documents from $out to. - * @param non-empty-string $coll Target database name to write documents from $out to. + * @param string $db Target collection name to write documents from $out to. + * @param string $coll Target database name to write documents from $out to. * @param Optional|Document|Serializable|array|stdClass $timeseries If set, the aggregation stage will use these options to create or replace a time-series collection in the given namespace. */ public static function out( @@ -480,7 +480,7 @@ public static function planCacheStats(): PlanCacheStatsStage * Reshapes each document in the stream, such as by adding new fields or removing existing fields. For each input document, outputs one document. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/ - * @param Document|ExpressionInterface|ProjectionInterface|Serializable|Type|array|bool|float|int|non-empty-string|null|stdClass ...$specification + * @param Document|ExpressionInterface|ProjectionInterface|Serializable|Type|array|bool|float|int|null|stdClass|string ...$specification */ public static function project( Document|Serializable|Type|ExpressionInterface|ProjectionInterface|stdClass|array|bool|float|int|null|string ...$specification, @@ -493,7 +493,7 @@ public static function project( * Reshapes each document in the stream by restricting the content for each document based on information stored in the documents themselves. Incorporates the functionality of $project and $match. Can be used to implement field level redaction. For each input document, outputs either one or zero documents. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function redact( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -569,7 +569,7 @@ public static function searchMeta(Document|Serializable|stdClass|array $meta): S * Alias for $addFields. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$field + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$field */ public static function set(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$field): SetStage { @@ -584,7 +584,7 @@ public static function set(Type|ExpressionInterface|stdClass|array|bool|float|in * @param Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. * @param Document|Serializable|array|stdClass $output Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. * A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. */ public static function setWindowFields( Document|Serializable|stdClass|array $sortBy, @@ -632,7 +632,7 @@ public static function sort(Document|Serializable|stdClass|array $sort): SortSta * Groups incoming documents based on the value of a specified expression, then computes the count of documents in each distinct group. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortByCount/ - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public static function sortByCount( Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, @@ -646,7 +646,7 @@ public static function sortByCount( * New in MongoDB 4.4. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unionWith/ - * @param non-empty-string $coll The collection or view whose pipeline results you wish to include in the result set. + * @param string $coll The collection or view whose pipeline results you wish to include in the result set. * @param Optional|BSONArray|PackedArray|Pipeline|array $pipeline An aggregation pipeline to apply to the specified coll. * The pipeline cannot include the $out and $merge stages. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. */ @@ -664,7 +664,7 @@ public static function unionWith( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/ * @no-named-arguments - * @param FieldPath|non-empty-string ...$field + * @param FieldPath|string ...$field */ public static function unset(FieldPath|string ...$field): UnsetStage { @@ -675,8 +675,8 @@ public static function unset(FieldPath|string ...$field): UnsetStage * Deconstructs an array field from the input documents to output a document for each element. Each output document replaces the array with an element value. For each input document, outputs n documents where n is the number of array elements and can be zero for an empty array. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/ - * @param ArrayFieldPath|non-empty-string $path Field path to an array field. - * @param Optional|non-empty-string $includeArrayIndex The name of a new field to hold the array index of the element. The name cannot start with a dollar sign $. + * @param ArrayFieldPath|string $path Field path to an array field. + * @param Optional|string $includeArrayIndex The name of a new field to hold the array index of the element. The name cannot start with a dollar sign $. * @param Optional|bool $preserveNullAndEmptyArrays If true, if the path is null, missing, or an empty array, $unwind outputs the document. * If false, if path is null, missing, or an empty array, $unwind does not output a document. * The default value is false. diff --git a/src/Builder/Stage/FillStage.php b/src/Builder/Stage/FillStage.php index 00b47ea83..634c05b8d 100644 --- a/src/Builder/Stage/FillStage.php +++ b/src/Builder/Stage/FillStage.php @@ -38,7 +38,7 @@ class FillStage implements StageInterface, OperatorInterface public readonly Document|Serializable|stdClass|array $output; /** - * @var Optional|Document|Serializable|array|non-empty-string|stdClass $partitionBy Specifies an expression to group the documents. In the $fill stage, a group of documents is known as a partition. + * @var Optional|Document|Serializable|array|stdClass|string $partitionBy Specifies an expression to group the documents. In the $fill stage, a group of documents is known as a partition. * If you omit partitionBy and partitionByFields, $fill uses one partition for the entire collection. * partitionBy and partitionByFields are mutually exclusive. */ @@ -57,7 +57,7 @@ class FillStage implements StageInterface, OperatorInterface /** * @param Document|Serializable|array|stdClass $output Specifies an object containing each field for which to fill missing values. You can specify multiple fields in the output object. * The object name is the name of the field to fill. The object value specifies how the field is filled. - * @param Optional|Document|Serializable|array|non-empty-string|stdClass $partitionBy Specifies an expression to group the documents. In the $fill stage, a group of documents is known as a partition. + * @param Optional|Document|Serializable|array|stdClass|string $partitionBy Specifies an expression to group the documents. In the $fill stage, a group of documents is known as a partition. * If you omit partitionBy and partitionByFields, $fill uses one partition for the entire collection. * partitionBy and partitionByFields are mutually exclusive. * @param Optional|BSONArray|PackedArray|array $partitionByFields Specifies an array of fields as the compound key to group the documents. In the $fill stage, each group of documents is known as a partition. diff --git a/src/Builder/Stage/GeoNearStage.php b/src/Builder/Stage/GeoNearStage.php index b7b8ec193..65996e343 100644 --- a/src/Builder/Stage/GeoNearStage.php +++ b/src/Builder/Stage/GeoNearStage.php @@ -31,7 +31,7 @@ class GeoNearStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var non-empty-string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. */ + /** @var string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. */ public readonly string $distanceField; /** @var Document|Serializable|array|stdClass $near The point for which to find the closest documents. */ @@ -40,10 +40,10 @@ class GeoNearStage implements StageInterface, OperatorInterface /** @var Optional|Decimal128|Int64|float|int $distanceMultiplier The factor to multiply all distances returned by the query. For example, use the distanceMultiplier to convert radians, as returned by a spherical query, to kilometers by multiplying by the radius of the Earth. */ public readonly Optional|Decimal128|Int64|float|int $distanceMultiplier; - /** @var Optional|non-empty-string $includeLocs This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. */ + /** @var Optional|string $includeLocs This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. */ public readonly Optional|string $includeLocs; - /** @var Optional|non-empty-string $key Specify the geospatial indexed field to use when calculating the distance. */ + /** @var Optional|string $key Specify the geospatial indexed field to use when calculating the distance. */ public readonly Optional|string $key; /** @@ -73,11 +73,11 @@ class GeoNearStage implements StageInterface, OperatorInterface public readonly Optional|bool $spherical; /** - * @param non-empty-string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. + * @param string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. * @param Document|Serializable|array|stdClass $near The point for which to find the closest documents. * @param Optional|Decimal128|Int64|float|int $distanceMultiplier The factor to multiply all distances returned by the query. For example, use the distanceMultiplier to convert radians, as returned by a spherical query, to kilometers by multiplying by the radius of the Earth. - * @param Optional|non-empty-string $includeLocs This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. - * @param Optional|non-empty-string $key Specify the geospatial indexed field to use when calculating the distance. + * @param Optional|string $includeLocs This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. + * @param Optional|string $key Specify the geospatial indexed field to use when calculating the distance. * @param Optional|Decimal128|Int64|float|int $maxDistance The maximum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall within the specified distance from the center point. * Specify the distance in meters if the specified point is GeoJSON and in radians if the specified point is legacy coordinate pairs. * @param Optional|Decimal128|Int64|float|int $minDistance The minimum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall outside the specified distance from the center point. diff --git a/src/Builder/Stage/GraphLookupStage.php b/src/Builder/Stage/GraphLookupStage.php index 93edd68a3..ceaa7a4b1 100644 --- a/src/Builder/Stage/GraphLookupStage.php +++ b/src/Builder/Stage/GraphLookupStage.php @@ -34,41 +34,41 @@ class GraphLookupStage implements StageInterface, OperatorInterface public const ENCODE = Encode::Object; /** - * @var non-empty-string $from Target collection for the $graphLookup operation to search, recursively matching the connectFromField to the connectToField. The from collection must be in the same database as any other collections used in the operation. + * @var string $from Target collection for the $graphLookup operation to search, recursively matching the connectFromField to the connectToField. The from collection must be in the same database as any other collections used in the operation. * Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. */ public readonly string $from; - /** @var BSONArray|ExpressionInterface|PackedArray|Type|array|bool|float|int|non-empty-string|null|stdClass $startWith Expression that specifies the value of the connectFromField with which to start the recursive search. Optionally, startWith may be array of values, each of which is individually followed through the traversal process. */ + /** @var BSONArray|ExpressionInterface|PackedArray|Type|array|bool|float|int|null|stdClass|string $startWith Expression that specifies the value of the connectFromField with which to start the recursive search. Optionally, startWith may be array of values, each of which is individually followed through the traversal process. */ public readonly PackedArray|Type|ExpressionInterface|BSONArray|stdClass|array|bool|float|int|null|string $startWith; - /** @var non-empty-string $connectFromField Field name whose value $graphLookup uses to recursively match against the connectToField of other documents in the collection. If the value is an array, each element is individually followed through the traversal process. */ + /** @var string $connectFromField Field name whose value $graphLookup uses to recursively match against the connectToField of other documents in the collection. If the value is an array, each element is individually followed through the traversal process. */ public readonly string $connectFromField; - /** @var non-empty-string $connectToField Field name in other documents against which to match the value of the field specified by the connectFromField parameter. */ + /** @var string $connectToField Field name in other documents against which to match the value of the field specified by the connectFromField parameter. */ public readonly string $connectToField; - /** @var non-empty-string $as Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. */ + /** @var string $as Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. */ public readonly string $as; /** @var Optional|int $maxDepth Non-negative integral number specifying the maximum recursion depth. */ public readonly Optional|int $maxDepth; - /** @var Optional|non-empty-string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. */ + /** @var Optional|string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. */ public readonly Optional|string $depthField; /** @var Optional|QueryInterface|array $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. */ public readonly Optional|QueryInterface|array $restrictSearchWithMatch; /** - * @param non-empty-string $from Target collection for the $graphLookup operation to search, recursively matching the connectFromField to the connectToField. The from collection must be in the same database as any other collections used in the operation. + * @param string $from Target collection for the $graphLookup operation to search, recursively matching the connectFromField to the connectToField. The from collection must be in the same database as any other collections used in the operation. * Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. - * @param BSONArray|ExpressionInterface|PackedArray|Type|array|bool|float|int|non-empty-string|null|stdClass $startWith Expression that specifies the value of the connectFromField with which to start the recursive search. Optionally, startWith may be array of values, each of which is individually followed through the traversal process. - * @param non-empty-string $connectFromField Field name whose value $graphLookup uses to recursively match against the connectToField of other documents in the collection. If the value is an array, each element is individually followed through the traversal process. - * @param non-empty-string $connectToField Field name in other documents against which to match the value of the field specified by the connectFromField parameter. - * @param non-empty-string $as Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. + * @param BSONArray|ExpressionInterface|PackedArray|Type|array|bool|float|int|null|stdClass|string $startWith Expression that specifies the value of the connectFromField with which to start the recursive search. Optionally, startWith may be array of values, each of which is individually followed through the traversal process. + * @param string $connectFromField Field name whose value $graphLookup uses to recursively match against the connectToField of other documents in the collection. If the value is an array, each element is individually followed through the traversal process. + * @param string $connectToField Field name in other documents against which to match the value of the field specified by the connectFromField parameter. + * @param string $as Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. * @param Optional|int $maxDepth Non-negative integral number specifying the maximum recursion depth. - * @param Optional|non-empty-string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. + * @param Optional|string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. * @param Optional|QueryInterface|array $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. */ public function __construct( diff --git a/src/Builder/Stage/GroupStage.php b/src/Builder/Stage/GroupStage.php index fc784487b..bc1466205 100644 --- a/src/Builder/Stage/GroupStage.php +++ b/src/Builder/Stage/GroupStage.php @@ -30,14 +30,14 @@ class GroupStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Group; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $_id The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $_id The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $_id; /** @var stdClass $field Computed using the accumulator operators. */ public readonly stdClass $field; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $_id The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $_id The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. * @param AccumulatorInterface|Document|Serializable|array|stdClass ...$field Computed using the accumulator operators. */ public function __construct( diff --git a/src/Builder/Stage/ListSampledQueriesStage.php b/src/Builder/Stage/ListSampledQueriesStage.php index 945a21e3b..8669c0fe8 100644 --- a/src/Builder/Stage/ListSampledQueriesStage.php +++ b/src/Builder/Stage/ListSampledQueriesStage.php @@ -22,11 +22,11 @@ class ListSampledQueriesStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var Optional|non-empty-string $namespace */ + /** @var Optional|string $namespace */ public readonly Optional|string $namespace; /** - * @param Optional|non-empty-string $namespace + * @param Optional|string $namespace */ public function __construct(Optional|string $namespace = Optional::Undefined) { diff --git a/src/Builder/Stage/ListSearchIndexesStage.php b/src/Builder/Stage/ListSearchIndexesStage.php index 053a37ae0..a2850eab2 100644 --- a/src/Builder/Stage/ListSearchIndexesStage.php +++ b/src/Builder/Stage/ListSearchIndexesStage.php @@ -22,15 +22,15 @@ class ListSearchIndexesStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var Optional|non-empty-string $id The id of the index to return information about. */ + /** @var Optional|string $id The id of the index to return information about. */ public readonly Optional|string $id; - /** @var Optional|non-empty-string $name The name of the index to return information about. */ + /** @var Optional|string $name The name of the index to return information about. */ public readonly Optional|string $name; /** - * @param Optional|non-empty-string $id The id of the index to return information about. - * @param Optional|non-empty-string $name The name of the index to return information about. + * @param Optional|string $id The id of the index to return information about. + * @param Optional|string $name The name of the index to return information about. */ public function __construct( Optional|string $id = Optional::Undefined, diff --git a/src/Builder/Stage/LookupStage.php b/src/Builder/Stage/LookupStage.php index a3b89619e..34f0fb66f 100644 --- a/src/Builder/Stage/LookupStage.php +++ b/src/Builder/Stage/LookupStage.php @@ -32,20 +32,20 @@ class LookupStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var non-empty-string $as Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten. */ + /** @var string $as Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten. */ public readonly string $as; /** - * @var Optional|non-empty-string $from Specifies the collection in the same database to perform the join with. + * @var Optional|string $from Specifies the collection in the same database to perform the join with. * from is optional, you can use a $documents stage in a $lookup stage instead. For an example, see Use a $documents Stage in a $lookup Stage. * Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. */ public readonly Optional|string $from; - /** @var Optional|non-empty-string $localField Specifies the field from the documents input to the $lookup stage. $lookup performs an equality match on the localField to the foreignField from the documents of the from collection. If an input document does not contain the localField, the $lookup treats the field as having a value of null for matching purposes. */ + /** @var Optional|string $localField Specifies the field from the documents input to the $lookup stage. $lookup performs an equality match on the localField to the foreignField from the documents of the from collection. If an input document does not contain the localField, the $lookup treats the field as having a value of null for matching purposes. */ public readonly Optional|string $localField; - /** @var Optional|non-empty-string $foreignField Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. */ + /** @var Optional|string $foreignField Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. */ public readonly Optional|string $foreignField; /** @var Optional|Document|Serializable|array|stdClass $let Specifies variables to use in the pipeline stages. Use the variable expressions to access the fields from the joined collection's documents that are input to the pipeline. */ @@ -59,12 +59,12 @@ class LookupStage implements StageInterface, OperatorInterface public readonly Optional|PackedArray|Pipeline|BSONArray|array $pipeline; /** - * @param non-empty-string $as Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten. - * @param Optional|non-empty-string $from Specifies the collection in the same database to perform the join with. + * @param string $as Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten. + * @param Optional|string $from Specifies the collection in the same database to perform the join with. * from is optional, you can use a $documents stage in a $lookup stage instead. For an example, see Use a $documents Stage in a $lookup Stage. * Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. - * @param Optional|non-empty-string $localField Specifies the field from the documents input to the $lookup stage. $lookup performs an equality match on the localField to the foreignField from the documents of the from collection. If an input document does not contain the localField, the $lookup treats the field as having a value of null for matching purposes. - * @param Optional|non-empty-string $foreignField Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. + * @param Optional|string $localField Specifies the field from the documents input to the $lookup stage. $lookup performs an equality match on the localField to the foreignField from the documents of the from collection. If an input document does not contain the localField, the $lookup treats the field as having a value of null for matching purposes. + * @param Optional|string $foreignField Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. * @param Optional|Document|Serializable|array|stdClass $let Specifies variables to use in the pipeline stages. Use the variable expressions to access the fields from the joined collection's documents that are input to the pipeline. * @param Optional|BSONArray|PackedArray|Pipeline|array $pipeline Specifies the pipeline to run on the joined collection. The pipeline determines the resulting documents from the joined collection. To return all documents, specify an empty pipeline []. * The pipeline cannot include the $out stage or the $merge stage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. diff --git a/src/Builder/Stage/MergeStage.php b/src/Builder/Stage/MergeStage.php index 0b2aff165..6af1e28d0 100644 --- a/src/Builder/Stage/MergeStage.php +++ b/src/Builder/Stage/MergeStage.php @@ -32,27 +32,27 @@ class MergeStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var non-empty-string $into The output collection. */ + /** @var string $into The output collection. */ public readonly string $into; - /** @var Optional|BSONArray|PackedArray|array|non-empty-string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. */ + /** @var Optional|BSONArray|PackedArray|array|string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. */ public readonly Optional|PackedArray|BSONArray|array|string $on; /** @var Optional|Document|Serializable|array|stdClass $let Specifies variables for use in the whenMatched pipeline. */ public readonly Optional|Document|Serializable|stdClass|array $let; - /** @var Optional|non-empty-string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). */ + /** @var Optional|string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). */ public readonly Optional|string $whenMatched; - /** @var Optional|non-empty-string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. */ + /** @var Optional|string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. */ public readonly Optional|string $whenNotMatched; /** - * @param non-empty-string $into The output collection. - * @param Optional|BSONArray|PackedArray|array|non-empty-string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. + * @param string $into The output collection. + * @param Optional|BSONArray|PackedArray|array|string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. * @param Optional|Document|Serializable|array|stdClass $let Specifies variables for use in the whenMatched pipeline. - * @param Optional|non-empty-string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). - * @param Optional|non-empty-string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. + * @param Optional|string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). + * @param Optional|string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. */ public function __construct( string $into, diff --git a/src/Builder/Stage/OutStage.php b/src/Builder/Stage/OutStage.php index 8c48a1281..e87a3f896 100644 --- a/src/Builder/Stage/OutStage.php +++ b/src/Builder/Stage/OutStage.php @@ -25,18 +25,18 @@ class OutStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var non-empty-string $db Target collection name to write documents from $out to. */ + /** @var string $db Target collection name to write documents from $out to. */ public readonly string $db; - /** @var non-empty-string $coll Target database name to write documents from $out to. */ + /** @var string $coll Target database name to write documents from $out to. */ public readonly string $coll; /** @var Optional|Document|Serializable|array|stdClass $timeseries If set, the aggregation stage will use these options to create or replace a time-series collection in the given namespace. */ public readonly Optional|Document|Serializable|stdClass|array $timeseries; /** - * @param non-empty-string $db Target collection name to write documents from $out to. - * @param non-empty-string $coll Target database name to write documents from $out to. + * @param string $db Target collection name to write documents from $out to. + * @param string $coll Target database name to write documents from $out to. * @param Optional|Document|Serializable|array|stdClass $timeseries If set, the aggregation stage will use these options to create or replace a time-series collection in the given namespace. */ public function __construct( diff --git a/src/Builder/Stage/ProjectStage.php b/src/Builder/Stage/ProjectStage.php index 3c9e57909..ab2c16faf 100644 --- a/src/Builder/Stage/ProjectStage.php +++ b/src/Builder/Stage/ProjectStage.php @@ -30,11 +30,11 @@ class ProjectStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var stdClass $specification */ + /** @var stdClass $specification */ public readonly stdClass $specification; /** - * @param Document|ExpressionInterface|ProjectionInterface|Serializable|Type|array|bool|float|int|non-empty-string|null|stdClass ...$specification + * @param Document|ExpressionInterface|ProjectionInterface|Serializable|Type|array|bool|float|int|null|stdClass|string ...$specification */ public function __construct( Document|Serializable|Type|ExpressionInterface|ProjectionInterface|stdClass|array|bool|float|int|null|string ...$specification, diff --git a/src/Builder/Stage/RedactStage.php b/src/Builder/Stage/RedactStage.php index 28b1bac41..96dcab949 100644 --- a/src/Builder/Stage/RedactStage.php +++ b/src/Builder/Stage/RedactStage.php @@ -24,11 +24,11 @@ class RedactStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Stage/SetStage.php b/src/Builder/Stage/SetStage.php index 2ec1dd16e..3814914d5 100644 --- a/src/Builder/Stage/SetStage.php +++ b/src/Builder/Stage/SetStage.php @@ -28,11 +28,11 @@ class SetStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var stdClass $field */ + /** @var stdClass $field */ public readonly stdClass $field; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$field + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$field */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$field) { diff --git a/src/Builder/Stage/SetWindowFieldsStage.php b/src/Builder/Stage/SetWindowFieldsStage.php index 4537e45a6..cb1354e66 100644 --- a/src/Builder/Stage/SetWindowFieldsStage.php +++ b/src/Builder/Stage/SetWindowFieldsStage.php @@ -37,14 +37,14 @@ class SetWindowFieldsStage implements StageInterface, OperatorInterface */ public readonly Document|Serializable|stdClass|array $output; - /** @var Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. */ + /** @var Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. */ public readonly Optional|Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $partitionBy; /** * @param Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. * @param Document|Serializable|array|stdClass $output Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. * A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. - * @param Optional|ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. */ public function __construct( Document|Serializable|stdClass|array $sortBy, diff --git a/src/Builder/Stage/SortByCountStage.php b/src/Builder/Stage/SortByCountStage.php index 0ff9a0184..8fda2cbee 100644 --- a/src/Builder/Stage/SortByCountStage.php +++ b/src/Builder/Stage/SortByCountStage.php @@ -24,11 +24,11 @@ class SortByCountStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */ + /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; /** - * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression) { diff --git a/src/Builder/Stage/UnionWithStage.php b/src/Builder/Stage/UnionWithStage.php index f5fc7a153..0882f74cb 100644 --- a/src/Builder/Stage/UnionWithStage.php +++ b/src/Builder/Stage/UnionWithStage.php @@ -30,7 +30,7 @@ class UnionWithStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var non-empty-string $coll The collection or view whose pipeline results you wish to include in the result set. */ + /** @var string $coll The collection or view whose pipeline results you wish to include in the result set. */ public readonly string $coll; /** @@ -40,7 +40,7 @@ class UnionWithStage implements StageInterface, OperatorInterface public readonly Optional|PackedArray|Pipeline|BSONArray|array $pipeline; /** - * @param non-empty-string $coll The collection or view whose pipeline results you wish to include in the result set. + * @param string $coll The collection or view whose pipeline results you wish to include in the result set. * @param Optional|BSONArray|PackedArray|Pipeline|array $pipeline An aggregation pipeline to apply to the specified coll. * The pipeline cannot include the $out and $merge stages. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. */ diff --git a/src/Builder/Stage/UnsetStage.php b/src/Builder/Stage/UnsetStage.php index 83f33e340..7cf9f7baa 100644 --- a/src/Builder/Stage/UnsetStage.php +++ b/src/Builder/Stage/UnsetStage.php @@ -26,11 +26,11 @@ class UnsetStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $field */ + /** @var list $field */ public readonly array $field; /** - * @param FieldPath|non-empty-string ...$field + * @param FieldPath|string ...$field * @no-named-arguments */ public function __construct(FieldPath|string ...$field) diff --git a/src/Builder/Stage/UnwindStage.php b/src/Builder/Stage/UnwindStage.php index bb7dded0c..09c9f1e82 100644 --- a/src/Builder/Stage/UnwindStage.php +++ b/src/Builder/Stage/UnwindStage.php @@ -23,10 +23,10 @@ class UnwindStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var ArrayFieldPath|non-empty-string $path Field path to an array field. */ + /** @var ArrayFieldPath|string $path Field path to an array field. */ public readonly ArrayFieldPath|string $path; - /** @var Optional|non-empty-string $includeArrayIndex The name of a new field to hold the array index of the element. The name cannot start with a dollar sign $. */ + /** @var Optional|string $includeArrayIndex The name of a new field to hold the array index of the element. The name cannot start with a dollar sign $. */ public readonly Optional|string $includeArrayIndex; /** @@ -37,8 +37,8 @@ class UnwindStage implements StageInterface, OperatorInterface public readonly Optional|bool $preserveNullAndEmptyArrays; /** - * @param ArrayFieldPath|non-empty-string $path Field path to an array field. - * @param Optional|non-empty-string $includeArrayIndex The name of a new field to hold the array index of the element. The name cannot start with a dollar sign $. + * @param ArrayFieldPath|string $path Field path to an array field. + * @param Optional|string $includeArrayIndex The name of a new field to hold the array index of the element. The name cannot start with a dollar sign $. * @param Optional|bool $preserveNullAndEmptyArrays If true, if the path is null, missing, or an empty array, $unwind outputs the document. * If false, if path is null, missing, or an empty array, $unwind does not output a document. * The default value is false. From aa7cffcc22fcb99a96d1507a85a553abb3da0db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 26 Jan 2024 09:51:34 +0100 Subject: [PATCH 49/95] PHPLIB-1362 Add tests on Window Operators (#51) * PHPLIB-1362 Add tests on Window Operators * Add tests on $minN accumulator --- .../config/accumulator/covariancePop.yaml | 22 + .../config/accumulator/covarianceSamp.yaml | 22 + generator/config/accumulator/denseRank.yaml | 25 + generator/config/accumulator/derivative.yaml | 24 + .../config/accumulator/documentNumber.yaml | 13 + .../config/accumulator/expMovingAvg.yaml | 29 + generator/config/accumulator/integral.yaml | 43 ++ generator/config/accumulator/linearFill.yaml | 40 ++ generator/config/accumulator/locf.yaml | 27 + generator/config/accumulator/minN.yaml | 51 ++ generator/config/accumulator/rank.yaml | 34 + generator/config/accumulator/shift.yaml | 31 + generator/config/expression/integral.yaml | 24 - generator/config/expression/linearFill.yaml | 15 - generator/config/expression/locf.yaml | 15 - generator/config/expression/rank.yaml | 9 - src/Builder/Accumulator/FactoryTrait.php | 57 ++ .../IntegralAccumulator.php} | 8 +- .../LinearFillAccumulator.php} | 6 +- .../LocfAccumulator.php} | 5 +- .../RankAccumulator.php} | 5 +- src/Builder/Expression/FactoryTrait.php | 56 -- .../CovariancePopAccumulatorTest.php | 44 ++ .../CovarianceSampAccumulatorTest.php | 44 ++ .../Accumulator/DenseRankAccumulatorTest.php | 56 ++ .../Accumulator/DerivativeAccumulatorTest.php | 47 ++ .../DocumentNumberAccumulatorTest.php | 36 ++ .../ExpMovingAvgAccumulatorTest.php | 59 ++ .../Accumulator/IntegralAccumulatorTest.php | 43 ++ .../Accumulator/LinearFillAccumulatorTest.php | 58 ++ .../Accumulator/LocfAccumulatorTest.php | 37 ++ .../Accumulator/MinNAccumulatorTest.php | 85 +++ tests/Builder/Accumulator/Pipelines.php | 579 ++++++++++++++++++ .../Accumulator/RankAccumulatorTest.php | 53 ++ .../Accumulator/ShiftAccumulatorTest.php | 61 ++ 35 files changed, 1636 insertions(+), 127 deletions(-) create mode 100644 generator/config/accumulator/integral.yaml create mode 100644 generator/config/accumulator/linearFill.yaml create mode 100644 generator/config/accumulator/locf.yaml create mode 100644 generator/config/accumulator/rank.yaml delete mode 100644 generator/config/expression/integral.yaml delete mode 100644 generator/config/expression/linearFill.yaml delete mode 100644 generator/config/expression/locf.yaml delete mode 100644 generator/config/expression/rank.yaml rename src/Builder/{Expression/IntegralOperator.php => Accumulator/IntegralAccumulator.php} (86%) rename src/Builder/{Expression/LinearFillOperator.php => Accumulator/LinearFillAccumulator.php} (83%) rename src/Builder/{Expression/LocfOperator.php => Accumulator/LocfAccumulator.php} (88%) rename src/Builder/{Expression/RankOperator.php => Accumulator/RankAccumulator.php} (79%) create mode 100644 tests/Builder/Accumulator/CovariancePopAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/CovarianceSampAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/DenseRankAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/DerivativeAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/DocumentNumberAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/ExpMovingAvgAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/IntegralAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/LinearFillAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/LocfAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/MinNAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/RankAccumulatorTest.php create mode 100644 tests/Builder/Accumulator/ShiftAccumulatorTest.php diff --git a/generator/config/accumulator/covariancePop.yaml b/generator/config/accumulator/covariancePop.yaml index 758b90bb7..5b41e6a3d 100644 --- a/generator/config/accumulator/covariancePop.yaml +++ b/generator/config/accumulator/covariancePop.yaml @@ -16,3 +16,25 @@ arguments: name: expression2 type: - resolvesToNumber +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/covariancePop/#example' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + covariancePopForState: + $covariancePop: + - + # $year: '$orderDate' + $year: + date: '$orderDate' + - '$quantity' + window: + documents: + - 'unbounded' + - 'current' diff --git a/generator/config/accumulator/covarianceSamp.yaml b/generator/config/accumulator/covarianceSamp.yaml index 072c5a7de..0c3bb8637 100644 --- a/generator/config/accumulator/covarianceSamp.yaml +++ b/generator/config/accumulator/covarianceSamp.yaml @@ -16,3 +16,25 @@ arguments: name: expression2 type: - resolvesToNumber +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/covarianceSamp/#example' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + covarianceSampForState: + $covarianceSamp: + - + # $year: '$orderDate' + $year: + date: '$orderDate' + - '$quantity' + window: + documents: + - 'unbounded' + - 'current' diff --git a/generator/config/accumulator/denseRank.yaml b/generator/config/accumulator/denseRank.yaml index 2a587ccea..0c50dd901 100644 --- a/generator/config/accumulator/denseRank.yaml +++ b/generator/config/accumulator/denseRank.yaml @@ -7,3 +7,28 @@ encode: object description: | Returns the document position (known as the rank) relative to other documents in the $setWindowFields stage partition. There are no gaps in the ranks. Ties receive the same rank. New in MongoDB 5.0. +tests: + - + name: 'Dense Rank Partitions by an Integer Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/denseRank/#dense-rank-partitions-by-an-integer-field' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + quantity: -1 + output: + denseRankQuantityForState: + $denseRank: {} + - + name: 'Dense Rank Partitions by a Date Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/denseRank/#dense-rank-partitions-by-a-date-field' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + denseRankOrderDateForState: + $denseRank: {} diff --git a/generator/config/accumulator/derivative.yaml b/generator/config/accumulator/derivative.yaml index 974b4d669..90c2ee0ea 100644 --- a/generator/config/accumulator/derivative.yaml +++ b/generator/config/accumulator/derivative.yaml @@ -21,3 +21,27 @@ arguments: description: | A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/derivative/#example' + pipeline: + - + $setWindowFields: + partitionBy: '$truckID' + sortBy: + timeStamp: 1 + output: + truckAverageSpeed: + $derivative: + input: '$miles' + unit: 'hour' + window: + range: + - -30 + - 0 + unit: 'second' + - + $match: + truckAverageSpeed: + $gt: 50 diff --git a/generator/config/accumulator/documentNumber.yaml b/generator/config/accumulator/documentNumber.yaml index 3ae8c8c01..b810ccd44 100644 --- a/generator/config/accumulator/documentNumber.yaml +++ b/generator/config/accumulator/documentNumber.yaml @@ -7,3 +7,16 @@ encode: object description: | Returns the position of a document (known as the document number) in the $setWindowFields stage partition. Ties result in different adjacent document numbers. New in MongoDB 5.0. +tests: + - + name: 'Document Number for Each State' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/documentNumber/#document-number-for-each-state' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + quantity: -1 + output: + documentNumberForState: + $documentNumber: {} diff --git a/generator/config/accumulator/expMovingAvg.yaml b/generator/config/accumulator/expMovingAvg.yaml index 981cde465..3009dd115 100644 --- a/generator/config/accumulator/expMovingAvg.yaml +++ b/generator/config/accumulator/expMovingAvg.yaml @@ -29,3 +29,32 @@ arguments: description: | A double that specifies the exponential decay value to use in the exponential moving average calculation. A higher alpha value assigns a lower mathematical significance to previous results from the calculation. You must specify either N or alpha. You cannot specify both. +tests: + - + name: 'Exponential Moving Average Using N' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/expMovingAvg/#exponential-moving-average-using-n' + pipeline: + - + $setWindowFields: + partitionBy: '$stock' + sortBy: + date: 1 + output: + expMovingAvgForStock: + $expMovingAvg: + input: '$price' + N: 2 + - + name: 'Exponential Moving Average Using alpha' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/expMovingAvg/#exponential-moving-average-using-alpha' + pipeline: + - + $setWindowFields: + partitionBy: '$stock' + sortBy: + date: 1 + output: + expMovingAvgForStock: + $expMovingAvg: + input: '$price' + alpha: 0.75 diff --git a/generator/config/accumulator/integral.yaml b/generator/config/accumulator/integral.yaml new file mode 100644 index 000000000..6c84548d1 --- /dev/null +++ b/generator/config/accumulator/integral.yaml @@ -0,0 +1,43 @@ +# $schema: ../schema.json +name: $integral +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/' +type: + - window +encode: object +description: | + Returns the approximation of the area under a curve. + New in MongoDB 5.0. +arguments: + - + name: input + type: + - resolvesToNumber + - resolvesToDate + - + name: unit + type: + - resolvesToString + optional: true + description: | + A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/#example' + pipeline: + - + $setWindowFields: + partitionBy: '$powerMeterID' + sortBy: + timeStamp: 1 + output: + powerMeterKilowattHours: + $integral: + input: '$kilowatts' + unit: 'hour' + window: + range: + - 'unbounded' + - 'current' + unit: 'hour' diff --git a/generator/config/accumulator/linearFill.yaml b/generator/config/accumulator/linearFill.yaml new file mode 100644 index 000000000..034e6ab9e --- /dev/null +++ b/generator/config/accumulator/linearFill.yaml @@ -0,0 +1,40 @@ +# $schema: ../schema.json +name: $linearFill +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/' +type: + - window +encode: single +description: | + Fills null and missing fields in a window using linear interpolation based on surrounding field values. + Available in the $setWindowFields stage. + New in MongoDB 5.3. +arguments: + - + name: expression + type: + - resolvesToNumber +tests: + - + name: 'Fill Missing Values with Linear Interpolation' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/#fill-missing-values-with-linear-interpolation' + pipeline: + - + $setWindowFields: + sortBy: + time: 1 + output: + price: + $linearFill: '$price' + - + name: 'Use Multiple Fill Methods in a Single Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/#use-multiple-fill-methods-in-a-single-stage' + pipeline: + - + $setWindowFields: + sortBy: + time: 1 + output: + linearFillPrice: + $linearFill: '$price' + locfPrice: + $locf: '$price' diff --git a/generator/config/accumulator/locf.yaml b/generator/config/accumulator/locf.yaml new file mode 100644 index 000000000..63979bca4 --- /dev/null +++ b/generator/config/accumulator/locf.yaml @@ -0,0 +1,27 @@ +# $schema: ../schema.json +name: $locf +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf/' +type: + - window +encode: single +description: | + Last observation carried forward. Sets values for null and missing fields in a window to the last non-null value for the field. + Available in the $setWindowFields stage. + New in MongoDB 5.2. +arguments: + - + name: expression + type: + - expression +tests: + - + name: 'Fill Missing Values with the Last Observed Value' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf/#fill-missing-values-with-the-last-observed-value' + pipeline: + - + $setWindowFields: + sortBy: + time: 1 + output: + price: + $locf: '$price' diff --git a/generator/config/accumulator/minN.yaml b/generator/config/accumulator/minN.yaml index 50737b084..24719a22a 100644 --- a/generator/config/accumulator/minN.yaml +++ b/generator/config/accumulator/minN.yaml @@ -20,3 +20,54 @@ arguments: - resolvesToInt description: | An expression that resolves to a positive integer. The integer specifies the number of array elements that $maxN returns. +tests: + - + name: 'Find the Minimum Three Scores for a Single Game' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN/#find-the-minimum-three-scores-for-a-single-game' + pipeline: + - + $match: + gameId: 'G1' + - + $group: + _id: '$gameId' + minScores: + $minN: + input: + - '$score' + - '$playerId' + n: 3 + - + name: 'Finding the Minimum Three Documents Across Multiple Games' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN/#finding-the-minimum-three-documents-across-multiple-games' + pipeline: + - + $group: + _id: '$gameId' + minScores: + $minN: + input: + - '$score' + - '$playerId' + n: 3 + - + name: 'Computing n Based on the Group Key for $group' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN/#computing-n-based-on-the-group-key-for--group' + pipeline: + - + $group: + _id: + gameId: '$gameId' + gamescores: + $minN: + input: + - '$score' + - '$playerId' + n: + $cond: + if: + $eq: + - '$gameId' + - 'G2' + then: 1 + else: 3 diff --git a/generator/config/accumulator/rank.yaml b/generator/config/accumulator/rank.yaml new file mode 100644 index 000000000..8b8fd041b --- /dev/null +++ b/generator/config/accumulator/rank.yaml @@ -0,0 +1,34 @@ +# $schema: ../schema.json +name: $rank +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/' +type: + - window +encode: object +description: | + Returns the document position (known as the rank) relative to other documents in the $setWindowFields stage partition. + New in MongoDB 5.0. +tests: + - + name: 'Rank Partitions by an Integer Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/#rank-partitions-by-an-integer-field' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + quantity: -1 + output: + rankQuantityForState: + $rank: {} + - + name: 'Rank Partitions by a Date Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/#rank-partitions-by-a-date-field' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + rankOrderDateForState: + $rank: {} diff --git a/generator/config/accumulator/shift.yaml b/generator/config/accumulator/shift.yaml index 38bc36308..f4984f056 100644 --- a/generator/config/accumulator/shift.yaml +++ b/generator/config/accumulator/shift.yaml @@ -32,3 +32,34 @@ arguments: Specifies an optional default expression to evaluate if the document position is outside of the implicit $setWindowFields stage window. The implicit window contains all the documents in the partition. The default expression must evaluate to a constant value. If you do not specify a default expression, $shift returns null for documents whose positions are outside of the implicit $setWindowFields stage window. +tests: + - + name: 'Shift Using a Positive Integer' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/shift/#shift-using-a-positive-integer' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + quantity: -1 + output: + shiftQuantityForState: + $shift: + output: '$quantity' + by: 1 + default: 'Not available' + - + name: 'Shift Using a Negative Integer' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/shift/#shift-using-a-negative-integer' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + quantity: -1 + output: + shiftQuantityForState: + $shift: + output: '$quantity' + by: -1 + default: 'Not available' diff --git a/generator/config/expression/integral.yaml b/generator/config/expression/integral.yaml deleted file mode 100644 index 55228ea72..000000000 --- a/generator/config/expression/integral.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# $schema: ../schema.json -name: $integral -link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/' -type: - - resolvesToDouble - - resolvesToDecimal -encode: object -description: | - Returns the approximation of the area under a curve. - New in MongoDB 5.0. -arguments: - - - name: input - type: - - resolvesToNumber - - resolvesToDate - - - name: unit - type: - - resolvesToString - optional: true - description: | - A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". - If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. diff --git a/generator/config/expression/linearFill.yaml b/generator/config/expression/linearFill.yaml deleted file mode 100644 index a53ebe572..000000000 --- a/generator/config/expression/linearFill.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# $schema: ../schema.json -name: $linearFill -link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/' -type: - - resolvesToNumber -encode: single -description: | - Fills null and missing fields in a window using linear interpolation based on surrounding field values. - Available in the $setWindowFields stage. - New in MongoDB 5.3. -arguments: - - - name: expression - type: - - resolvesToNumber diff --git a/generator/config/expression/locf.yaml b/generator/config/expression/locf.yaml deleted file mode 100644 index 5e079620c..000000000 --- a/generator/config/expression/locf.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# $schema: ../schema.json -name: $locf -link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf/' -type: - - resolvesToAny -encode: single -description: | - Last observation carried forward. Sets values for null and missing fields in a window to the last non-null value for the field. - Available in the $setWindowFields stage. - New in MongoDB 5.2. -arguments: - - - name: expression - type: - - expression diff --git a/generator/config/expression/rank.yaml b/generator/config/expression/rank.yaml deleted file mode 100644 index dde42047c..000000000 --- a/generator/config/expression/rank.yaml +++ /dev/null @@ -1,9 +0,0 @@ -# $schema: ../schema.json -name: $rank -link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/' -type: - - resolvesToInt -encode: object -description: | - Returns the document position (known as the rank) relative to other documents in the $setWindowFields stage partition. - New in MongoDB 5.0. diff --git a/src/Builder/Accumulator/FactoryTrait.php b/src/Builder/Accumulator/FactoryTrait.php index 604e3c388..4b889da47 100644 --- a/src/Builder/Accumulator/FactoryTrait.php +++ b/src/Builder/Accumulator/FactoryTrait.php @@ -21,6 +21,7 @@ use MongoDB\Builder\Expression\ResolvesToInt; use MongoDB\Builder\Expression\ResolvesToNumber; use MongoDB\Builder\Expression\ResolvesToObject; +use MongoDB\Builder\Expression\ResolvesToString; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\Optional; use MongoDB\Model\BSONArray; @@ -253,6 +254,23 @@ public static function firstN( return new FirstNAccumulator($input, $n); } + /** + * Returns the approximation of the area under a curve. + * New in MongoDB 5.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/ + * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input + * @param Optional|ResolvesToString|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. + */ + public static function integral( + Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input, + Optional|ResolvesToString|string $unit = Optional::Undefined, + ): IntegralAccumulator + { + return new IntegralAccumulator($input, $unit); + } + /** * Returns the result of an expression for the last document in a group or window. * Changed in MongoDB 5.0: Available in the $setWindowFields stage. @@ -284,6 +302,34 @@ public static function lastN( return new LastNAccumulator($input, $n); } + /** + * Fills null and missing fields in a window using linear interpolation based on surrounding field values. + * Available in the $setWindowFields stage. + * New in MongoDB 5.3. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/ + * @param Decimal128|Int64|ResolvesToNumber|float|int $expression + */ + public static function linearFill(Decimal128|Int64|ResolvesToNumber|float|int $expression): LinearFillAccumulator + { + return new LinearFillAccumulator($expression); + } + + /** + * Last observation carried forward. Sets values for null and missing fields in a window to the last non-null value for the field. + * Available in the $setWindowFields stage. + * New in MongoDB 5.2. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf/ + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression + */ + public static function locf( + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, + ): LocfAccumulator + { + return new LocfAccumulator($expression); + } + /** * Returns the maximum value that results from applying an expression to each document. * Changed in MongoDB 5.0: Available in the $setWindowFields stage. @@ -415,6 +461,17 @@ public static function push( return new PushAccumulator($expression); } + /** + * Returns the document position (known as the rank) relative to other documents in the $setWindowFields stage partition. + * New in MongoDB 5.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/ + */ + public static function rank(): RankAccumulator + { + return new RankAccumulator(); + } + /** * Returns the value from an expression applied to a document in a specified position relative to the current document in the $setWindowFields stage partition. * New in MongoDB 5.0. diff --git a/src/Builder/Expression/IntegralOperator.php b/src/Builder/Accumulator/IntegralAccumulator.php similarity index 86% rename from src/Builder/Expression/IntegralOperator.php rename to src/Builder/Accumulator/IntegralAccumulator.php index 2f2c6efef..9f9921165 100644 --- a/src/Builder/Expression/IntegralOperator.php +++ b/src/Builder/Accumulator/IntegralAccumulator.php @@ -6,14 +6,18 @@ declare(strict_types=1); -namespace MongoDB\Builder\Expression; +namespace MongoDB\Builder\Accumulator; use MongoDB\BSON\Decimal128; use MongoDB\BSON\Int64; use MongoDB\BSON\UTCDateTime; +use MongoDB\Builder\Expression\ResolvesToDate; +use MongoDB\Builder\Expression\ResolvesToNumber; +use MongoDB\Builder\Expression\ResolvesToString; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\WindowInterface; /** * Returns the approximation of the area under a curve. @@ -21,7 +25,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/ */ -class IntegralOperator implements ResolvesToDouble, ResolvesToDecimal, OperatorInterface +class IntegralAccumulator implements WindowInterface, OperatorInterface { public const ENCODE = Encode::Object; diff --git a/src/Builder/Expression/LinearFillOperator.php b/src/Builder/Accumulator/LinearFillAccumulator.php similarity index 83% rename from src/Builder/Expression/LinearFillOperator.php rename to src/Builder/Accumulator/LinearFillAccumulator.php index c01873e4b..e203f7ff9 100644 --- a/src/Builder/Expression/LinearFillOperator.php +++ b/src/Builder/Accumulator/LinearFillAccumulator.php @@ -6,12 +6,14 @@ declare(strict_types=1); -namespace MongoDB\Builder\Expression; +namespace MongoDB\Builder\Accumulator; use MongoDB\BSON\Decimal128; use MongoDB\BSON\Int64; +use MongoDB\Builder\Expression\ResolvesToNumber; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\WindowInterface; /** * Fills null and missing fields in a window using linear interpolation based on surrounding field values. @@ -20,7 +22,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/ */ -class LinearFillOperator implements ResolvesToNumber, OperatorInterface +class LinearFillAccumulator implements WindowInterface, OperatorInterface { public const ENCODE = Encode::Single; diff --git a/src/Builder/Expression/LocfOperator.php b/src/Builder/Accumulator/LocfAccumulator.php similarity index 88% rename from src/Builder/Expression/LocfOperator.php rename to src/Builder/Accumulator/LocfAccumulator.php index 64d437a6d..e133cc4c9 100644 --- a/src/Builder/Expression/LocfOperator.php +++ b/src/Builder/Accumulator/LocfAccumulator.php @@ -6,12 +6,13 @@ declare(strict_types=1); -namespace MongoDB\Builder\Expression; +namespace MongoDB\Builder\Accumulator; use MongoDB\BSON\Type; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\WindowInterface; use stdClass; /** @@ -21,7 +22,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf/ */ -class LocfOperator implements ResolvesToAny, OperatorInterface +class LocfAccumulator implements WindowInterface, OperatorInterface { public const ENCODE = Encode::Single; diff --git a/src/Builder/Expression/RankOperator.php b/src/Builder/Accumulator/RankAccumulator.php similarity index 79% rename from src/Builder/Expression/RankOperator.php rename to src/Builder/Accumulator/RankAccumulator.php index e7522ae99..dd274dceb 100644 --- a/src/Builder/Expression/RankOperator.php +++ b/src/Builder/Accumulator/RankAccumulator.php @@ -6,10 +6,11 @@ declare(strict_types=1); -namespace MongoDB\Builder\Expression; +namespace MongoDB\Builder\Accumulator; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\WindowInterface; /** * Returns the document position (known as the rank) relative to other documents in the $setWindowFields stage partition. @@ -17,7 +18,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/ */ -class RankOperator implements ResolvesToInt, OperatorInterface +class RankAccumulator implements WindowInterface, OperatorInterface { public const ENCODE = Encode::Object; diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 4611c0835..0cc96a96d 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -934,23 +934,6 @@ public static function indexOfCP( return new IndexOfCPOperator($string, $substring, $start, $end); } - /** - * Returns the approximation of the area under a curve. - * New in MongoDB 5.0. - * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/ - * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input - * @param Optional|ResolvesToString|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". - * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. - */ - public static function integral( - Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input, - Optional|ResolvesToString|string $unit = Optional::Undefined, - ): IntegralOperator - { - return new IntegralOperator($input, $unit); - } - /** * Determines if the operand is an array. Returns a boolean. * @@ -1067,19 +1050,6 @@ public static function let( return new LetOperator($vars, $in); } - /** - * Fills null and missing fields in a window using linear interpolation based on surrounding field values. - * Available in the $setWindowFields stage. - * New in MongoDB 5.3. - * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/ - * @param Decimal128|Int64|ResolvesToNumber|float|int $expression - */ - public static function linearFill(Decimal128|Int64|ResolvesToNumber|float|int $expression): LinearFillOperator - { - return new LinearFillOperator($expression); - } - /** * Return a value without parsing. Use for values that the aggregation pipeline may interpret as an expression. For example, use a $literal expression to a string that starts with a dollar sign ($) to avoid parsing as a field path. * @@ -1103,21 +1073,6 @@ public static function ln(Decimal128|Int64|ResolvesToNumber|float|int $number): return new LnOperator($number); } - /** - * Last observation carried forward. Sets values for null and missing fields in a window to the last non-null value for the field. - * Available in the $setWindowFields stage. - * New in MongoDB 5.2. - * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf/ - * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression - */ - public static function locf( - Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression, - ): LocfOperator - { - return new LocfOperator($expression); - } - /** * Calculates the log of a number in the specified base. * @@ -1523,17 +1478,6 @@ public static function range( return new RangeOperator($start, $end, $step); } - /** - * Returns the document position (known as the rank) relative to other documents in the $setWindowFields stage partition. - * New in MongoDB 5.0. - * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/ - */ - public static function rank(): RankOperator - { - return new RankOperator(); - } - /** * Applies an expression to each element in an array and combines them into a single value. * diff --git a/tests/Builder/Accumulator/CovariancePopAccumulatorTest.php b/tests/Builder/Accumulator/CovariancePopAccumulatorTest.php new file mode 100644 index 000000000..a07eb3e83 --- /dev/null +++ b/tests/Builder/Accumulator/CovariancePopAccumulatorTest.php @@ -0,0 +1,44 @@ +assertSamePipeline(Pipelines::CovariancePopExample, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/CovarianceSampAccumulatorTest.php b/tests/Builder/Accumulator/CovarianceSampAccumulatorTest.php new file mode 100644 index 000000000..a0db117ce --- /dev/null +++ b/tests/Builder/Accumulator/CovarianceSampAccumulatorTest.php @@ -0,0 +1,44 @@ +assertSamePipeline(Pipelines::CovarianceSampExample, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/DenseRankAccumulatorTest.php b/tests/Builder/Accumulator/DenseRankAccumulatorTest.php new file mode 100644 index 000000000..54dac4bc4 --- /dev/null +++ b/tests/Builder/Accumulator/DenseRankAccumulatorTest.php @@ -0,0 +1,56 @@ +assertSamePipeline(Pipelines::DenseRankDenseRankPartitionsByADateField, $pipeline); + } + + public function testDenseRankPartitionsByAnIntegerField(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::stringFieldPath('state'), + sortBy: object( + quantity: -1, + ), + output: object( + // The outputWindow is optional when no window property is set. + denseRankQuantityForState: Accumulator::denseRank(), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::DenseRankDenseRankPartitionsByAnIntegerField, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/DerivativeAccumulatorTest.php b/tests/Builder/Accumulator/DerivativeAccumulatorTest.php new file mode 100644 index 000000000..448d11387 --- /dev/null +++ b/tests/Builder/Accumulator/DerivativeAccumulatorTest.php @@ -0,0 +1,47 @@ +assertSamePipeline(Pipelines::DerivativeExample, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/DocumentNumberAccumulatorTest.php b/tests/Builder/Accumulator/DocumentNumberAccumulatorTest.php new file mode 100644 index 000000000..832fd4c30 --- /dev/null +++ b/tests/Builder/Accumulator/DocumentNumberAccumulatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::DocumentNumberDocumentNumberForEachState, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/ExpMovingAvgAccumulatorTest.php b/tests/Builder/Accumulator/ExpMovingAvgAccumulatorTest.php new file mode 100644 index 000000000..2e1a33b08 --- /dev/null +++ b/tests/Builder/Accumulator/ExpMovingAvgAccumulatorTest.php @@ -0,0 +1,59 @@ +assertSamePipeline(Pipelines::ExpMovingAvgExponentialMovingAverageUsingAlpha, $pipeline); + } + + public function testExponentialMovingAverageUsingN(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::stringFieldPath('stock'), + sortBy: object( + date: 1, + ), + output: object( + expMovingAvgForStock: Accumulator::expMovingAvg( + input: Expression::numberFieldPath('price'), + N: 2, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ExpMovingAvgExponentialMovingAverageUsingN, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/IntegralAccumulatorTest.php b/tests/Builder/Accumulator/IntegralAccumulatorTest.php new file mode 100644 index 000000000..d596d12e4 --- /dev/null +++ b/tests/Builder/Accumulator/IntegralAccumulatorTest.php @@ -0,0 +1,43 @@ +assertSamePipeline(Pipelines::IntegralExample, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/LinearFillAccumulatorTest.php b/tests/Builder/Accumulator/LinearFillAccumulatorTest.php new file mode 100644 index 000000000..32c4a0202 --- /dev/null +++ b/tests/Builder/Accumulator/LinearFillAccumulatorTest.php @@ -0,0 +1,58 @@ +assertSamePipeline(Pipelines::LinearFillFillMissingValuesWithLinearInterpolation, $pipeline); + } + + public function testUseMultipleFillMethodsInASingleStage(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + sortBy: object( + time: 1, + ), + output: object( + linearFillPrice: Accumulator::linearFill( + Expression::numberFieldPath('price'), + ), + locfPrice: Accumulator::locf( + Expression::numberFieldPath('price'), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::LinearFillUseMultipleFillMethodsInASingleStage, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/LocfAccumulatorTest.php b/tests/Builder/Accumulator/LocfAccumulatorTest.php new file mode 100644 index 000000000..0723e9acb --- /dev/null +++ b/tests/Builder/Accumulator/LocfAccumulatorTest.php @@ -0,0 +1,37 @@ +assertSamePipeline(Pipelines::LocfFillMissingValuesWithTheLastObservedValue, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/MinNAccumulatorTest.php b/tests/Builder/Accumulator/MinNAccumulatorTest.php new file mode 100644 index 000000000..c44c1f6d2 --- /dev/null +++ b/tests/Builder/Accumulator/MinNAccumulatorTest.php @@ -0,0 +1,85 @@ +assertSamePipeline(Pipelines::MinNComputingNBasedOnTheGroupKeyForGroup, $pipeline); + } + + public function testFindTheMinimumThreeScoresForASingleGame(): void + { + $pipeline = new Pipeline( + Stage::match( + gameId: 'G1', + ), + Stage::group( + _id: Expression::fieldPath('gameId'), + minScores: Accumulator::minN( + input: [ + Expression::fieldPath('score'), + Expression::fieldPath('playerId'), + ], + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::MinNFindTheMinimumThreeScoresForASingleGame, $pipeline); + } + + public function testFindingTheMinimumThreeDocumentsAcrossMultipleGames(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('gameId'), + minScores: Accumulator::minN( + input: [ + Expression::fieldPath('score'), + Expression::fieldPath('playerId'), + ], + n: 3, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::MinNFindingTheMinimumThreeDocumentsAcrossMultipleGames, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/Pipelines.php b/tests/Builder/Accumulator/Pipelines.php index d0baa961e..fedc91ab4 100644 --- a/tests/Builder/Accumulator/Pipelines.php +++ b/tests/Builder/Accumulator/Pipelines.php @@ -423,6 +423,265 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/covariancePop/#example + */ + case CovariancePopExample = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "covariancePopForState": { + "$covariancePop": [ + { + "$year": { + "date": "$orderDate" + } + }, + "$quantity" + ], + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/covarianceSamp/#example + */ + case CovarianceSampExample = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "covarianceSampForState": { + "$covarianceSamp": [ + { + "$year": { + "date": "$orderDate" + } + }, + "$quantity" + ], + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } + } + } + } + ] + JSON; + + /** + * Dense Rank Partitions by an Integer Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/denseRank/#dense-rank-partitions-by-an-integer-field + */ + case DenseRankDenseRankPartitionsByAnIntegerField = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "quantity": { + "$numberInt": "-1" + } + }, + "output": { + "denseRankQuantityForState": { + "$denseRank": {} + } + } + } + } + ] + JSON; + + /** + * Dense Rank Partitions by a Date Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/denseRank/#dense-rank-partitions-by-a-date-field + */ + case DenseRankDenseRankPartitionsByADateField = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "denseRankOrderDateForState": { + "$denseRank": {} + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/derivative/#example + */ + case DerivativeExample = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$truckID", + "sortBy": { + "timeStamp": { + "$numberInt": "1" + } + }, + "output": { + "truckAverageSpeed": { + "$derivative": { + "input": "$miles", + "unit": "hour" + }, + "window": { + "range": [ + { + "$numberInt": "-30" + }, + { + "$numberInt": "0" + } + ], + "unit": "second" + } + } + } + } + }, + { + "$match": { + "truckAverageSpeed": { + "$gt": { + "$numberInt": "50" + } + } + } + } + ] + JSON; + + /** + * Document Number for Each State + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/documentNumber/#document-number-for-each-state + */ + case DocumentNumberDocumentNumberForEachState = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "quantity": { + "$numberInt": "-1" + } + }, + "output": { + "documentNumberForState": { + "$documentNumber": {} + } + } + } + } + ] + JSON; + + /** + * Exponential Moving Average Using N + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/expMovingAvg/#exponential-moving-average-using-n + */ + case ExpMovingAvgExponentialMovingAverageUsingN = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$stock", + "sortBy": { + "date": { + "$numberInt": "1" + } + }, + "output": { + "expMovingAvgForStock": { + "$expMovingAvg": { + "input": "$price", + "N": { + "$numberInt": "2" + } + } + } + } + } + } + ] + JSON; + + /** + * Exponential Moving Average Using alpha + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/expMovingAvg/#exponential-moving-average-using-alpha + */ + case ExpMovingAvgExponentialMovingAverageUsingAlpha = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$stock", + "sortBy": { + "date": { + "$numberInt": "1" + } + }, + "output": { + "expMovingAvgForStock": { + "$expMovingAvg": { + "input": "$price", + "alpha": { + "$numberDouble": "0.75" + } + } + } + } + } + } + ] + JSON; + /** * Use in $group Stage * @@ -667,6 +926,41 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/#example + */ + case IntegralExample = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$powerMeterID", + "sortBy": { + "timeStamp": { + "$numberInt": "1" + } + }, + "output": { + "powerMeterKilowattHours": { + "$integral": { + "input": "$kilowatts", + "unit": "hour" + }, + "window": { + "range": [ + "unbounded", + "current" + ], + "unit": "hour" + } + } + } + } + } + ] + JSON; + /** * Use in $group Stage * @@ -854,6 +1148,81 @@ enum Pipelines: string ] JSON; + /** + * Fill Missing Values with Linear Interpolation + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/#fill-missing-values-with-linear-interpolation + */ + case LinearFillFillMissingValuesWithLinearInterpolation = <<<'JSON' + [ + { + "$setWindowFields": { + "sortBy": { + "time": { + "$numberInt": "1" + } + }, + "output": { + "price": { + "$linearFill": "$price" + } + } + } + } + ] + JSON; + + /** + * Use Multiple Fill Methods in a Single Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/linearFill/#use-multiple-fill-methods-in-a-single-stage + */ + case LinearFillUseMultipleFillMethodsInASingleStage = <<<'JSON' + [ + { + "$setWindowFields": { + "sortBy": { + "time": { + "$numberInt": "1" + } + }, + "output": { + "linearFillPrice": { + "$linearFill": "$price" + }, + "locfPrice": { + "$locf": "$price" + } + } + } + } + ] + JSON; + + /** + * Fill Missing Values with the Last Observed Value + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/locf/#fill-missing-values-with-the-last-observed-value + */ + case LocfFillMissingValuesWithTheLastObservedValue = <<<'JSON' + [ + { + "$setWindowFields": { + "sortBy": { + "time": { + "$numberInt": "1" + } + }, + "output": { + "price": { + "$locf": "$price" + } + } + } + } + ] + JSON; + /** * Use in $group Stage * @@ -1147,6 +1516,104 @@ enum Pipelines: string ] JSON; + /** + * Find the Minimum Three Scores for a Single Game + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN/#find-the-minimum-three-scores-for-a-single-game + */ + case MinNFindTheMinimumThreeScoresForASingleGame = <<<'JSON' + [ + { + "$match": { + "gameId": "G1" + } + }, + { + "$group": { + "_id": "$gameId", + "minScores": { + "$minN": { + "input": [ + "$score", + "$playerId" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Finding the Minimum Three Documents Across Multiple Games + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN/#finding-the-minimum-three-documents-across-multiple-games + */ + case MinNFindingTheMinimumThreeDocumentsAcrossMultipleGames = <<<'JSON' + [ + { + "$group": { + "_id": "$gameId", + "minScores": { + "$minN": { + "input": [ + "$score", + "$playerId" + ], + "n": { + "$numberInt": "3" + } + } + } + } + } + ] + JSON; + + /** + * Computing n Based on the Group Key for $group + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/minN/#computing-n-based-on-the-group-key-for--group + */ + case MinNComputingNBasedOnTheGroupKeyForGroup = <<<'JSON' + [ + { + "$group": { + "_id": { + "gameId": "$gameId" + }, + "gamescores": { + "$minN": { + "input": [ + "$score", + "$playerId" + ], + "n": { + "$cond": { + "if": { + "$eq": [ + "$gameId", + "G2" + ] + }, + "then": { + "$numberInt": "1" + }, + "else": { + "$numberInt": "3" + } + } + } + } + } + } + } + ] + JSON; + /** * Calculate a Single Value as an Accumulator * @@ -1396,6 +1863,118 @@ enum Pipelines: string ] JSON; + /** + * Rank Partitions by an Integer Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/#rank-partitions-by-an-integer-field + */ + case RankRankPartitionsByAnIntegerField = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "quantity": { + "$numberInt": "-1" + } + }, + "output": { + "rankQuantityForState": { + "$rank": {} + } + } + } + } + ] + JSON; + + /** + * Rank Partitions by a Date Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/rank/#rank-partitions-by-a-date-field + */ + case RankRankPartitionsByADateField = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "rankOrderDateForState": { + "$rank": {} + } + } + } + } + ] + JSON; + + /** + * Shift Using a Positive Integer + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/shift/#shift-using-a-positive-integer + */ + case ShiftShiftUsingAPositiveInteger = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "quantity": { + "$numberInt": "-1" + } + }, + "output": { + "shiftQuantityForState": { + "$shift": { + "output": "$quantity", + "by": { + "$numberInt": "1" + }, + "default": "Not available" + } + } + } + } + } + ] + JSON; + + /** + * Shift Using a Negative Integer + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/shift/#shift-using-a-negative-integer + */ + case ShiftShiftUsingANegativeInteger = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "quantity": { + "$numberInt": "-1" + } + }, + "output": { + "shiftQuantityForState": { + "$shift": { + "output": "$quantity", + "by": { + "$numberInt": "-1" + }, + "default": "Not available" + } + } + } + } + } + ] + JSON; + /** * Use in $group Stage * diff --git a/tests/Builder/Accumulator/RankAccumulatorTest.php b/tests/Builder/Accumulator/RankAccumulatorTest.php new file mode 100644 index 000000000..d2420a96e --- /dev/null +++ b/tests/Builder/Accumulator/RankAccumulatorTest.php @@ -0,0 +1,53 @@ +assertSamePipeline(Pipelines::RankRankPartitionsByADateField, $pipeline); + } + + public function testRankPartitionsByAnIntegerField(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::stringFieldPath('state'), + sortBy: object( + quantity: -1, + ), + output: object( + rankQuantityForState: Accumulator::rank(), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::RankRankPartitionsByAnIntegerField, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/ShiftAccumulatorTest.php b/tests/Builder/Accumulator/ShiftAccumulatorTest.php new file mode 100644 index 000000000..c36c68095 --- /dev/null +++ b/tests/Builder/Accumulator/ShiftAccumulatorTest.php @@ -0,0 +1,61 @@ +assertSamePipeline(Pipelines::ShiftShiftUsingANegativeInteger, $pipeline); + } + + public function testShiftUsingAPositiveInteger(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::stringFieldPath('state'), + sortBy: object( + quantity: -1, + ), + output: object( + shiftQuantityForState: Accumulator::shift( + output: Expression::fieldPath('quantity'), + by: 1, + default: 'Not available', + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ShiftShiftUsingAPositiveInteger, $pipeline); + } +} From 397ff5d794a5dfcc0c80351a40a5423abaddcd5b Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Mon, 29 Jan 2024 08:59:12 +0100 Subject: [PATCH 50/95] PHPLIB-1250 Split encoders and fix psalm issues (#46) * Split encoders into multiple files * Remove unnecessary factory method * Add missing template annotation * Add MixedArgument and MixedAssignment to psalm baseline * Fix type issues in query classes * Extract common builder code and fix template annotations * Update psalm baseline * Define ENCODE constant in OperatorInterface * Simplify canEncode check in BuilderEncoder * Use strict comparison * Fix checkstyle errors * Fix wrong object type check * Update todo for properties in interfaces * Fix return type of PipelineEncoder::encode * Update comment in OperatorEncoder Co-authored-by: Jeremy Mikola * Optimise cached encoders structure * Add undefined case for Encode enum --------- Co-authored-by: Jeremy Mikola --- phpcs.xml.dist | 1 - psalm-baseline.xml | 133 +++++++- src/Builder/BuilderEncoder.php | 323 +++--------------- .../Encoder/AbstractExpressionEncoder.php | 53 +++ .../Encoder/CombinedFieldQueryEncoder.php | 52 +++ src/Builder/Encoder/ExpressionEncoder.php | 19 ++ src/Builder/Encoder/FieldPathEncoder.php | 31 ++ src/Builder/Encoder/OperatorEncoder.php | 161 +++++++++ src/Builder/Encoder/OutputWindowEncoder.php | 60 ++++ src/Builder/Encoder/PipelineEncoder.php | 37 ++ src/Builder/Encoder/QueryEncoder.php | 57 ++++ src/Builder/Encoder/VariableEncoder.php | 31 ++ src/Builder/Type/CombinedFieldQuery.php | 23 +- src/Builder/Type/Encode.php | 5 + src/Builder/Type/OperatorInterface.php | 3 + src/Builder/Type/QueryObject.php | 8 +- 16 files changed, 709 insertions(+), 288 deletions(-) create mode 100644 src/Builder/Encoder/AbstractExpressionEncoder.php create mode 100644 src/Builder/Encoder/CombinedFieldQueryEncoder.php create mode 100644 src/Builder/Encoder/ExpressionEncoder.php create mode 100644 src/Builder/Encoder/FieldPathEncoder.php create mode 100644 src/Builder/Encoder/OperatorEncoder.php create mode 100644 src/Builder/Encoder/OutputWindowEncoder.php create mode 100644 src/Builder/Encoder/PipelineEncoder.php create mode 100644 src/Builder/Encoder/QueryEncoder.php create mode 100644 src/Builder/Encoder/VariableEncoder.php diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 74f7c0be6..872605b02 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -42,7 +42,6 @@ - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index f4acb63c7..a8793bf11 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,3 +1,134 @@ - + + + + $val + $val + $value[$key] + + + + + $filter + $filterValue + + + + + name]]> + + + + + $result + $result[] + $val + $val + $val + $val + $val + + + + + $result + + + + + recursiveEncode($value)]]> + + + $subValue + $value + + + + + $query + + + + + $query + + + + + $query + + + + + $expression + + + stdClass + + + + + $facet + + + stdClass + + + + + $query + + + + + $restrictSearchWithMatch + + + + + $field + + + stdClass + + + + + $query + + + + + $specification + + + stdClass + + + + + $field + + + stdClass + + + + + + + + + + + $queries[$fieldPath] + $query + + + 0]]> + + diff --git a/src/Builder/BuilderEncoder.php b/src/Builder/BuilderEncoder.php index c7c0b278c..8cc96990d 100644 --- a/src/Builder/BuilderEncoder.php +++ b/src/Builder/BuilderEncoder.php @@ -4,323 +4,98 @@ namespace MongoDB\Builder; -use LogicException; +use MongoDB\Builder\Encoder\CombinedFieldQueryEncoder; +use MongoDB\Builder\Encoder\ExpressionEncoder; +use MongoDB\Builder\Encoder\FieldPathEncoder; +use MongoDB\Builder\Encoder\OperatorEncoder; +use MongoDB\Builder\Encoder\OutputWindowEncoder; +use MongoDB\Builder\Encoder\PipelineEncoder; +use MongoDB\Builder\Encoder\QueryEncoder; +use MongoDB\Builder\Encoder\VariableEncoder; use MongoDB\Builder\Expression\Variable; -use MongoDB\Builder\Stage\GroupStage; -use MongoDB\Builder\Type\AccumulatorInterface; use MongoDB\Builder\Type\CombinedFieldQuery; -use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\FieldPathInterface; -use MongoDB\Builder\Type\FieldQueryInterface; use MongoDB\Builder\Type\OperatorInterface; -use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\OutputWindow; -use MongoDB\Builder\Type\ProjectionInterface; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Builder\Type\QueryObject; use MongoDB\Builder\Type\StageInterface; -use MongoDB\Builder\Type\WindowInterface; use MongoDB\Codec\EncodeIfSupported; use MongoDB\Codec\Encoder; use MongoDB\Exception\UnsupportedValueException; use stdClass; use function array_key_exists; -use function array_key_first; -use function assert; -use function get_debug_type; -use function get_object_vars; -use function is_array; use function is_object; -use function MongoDB\is_first_key_operator; -use function property_exists; -use function sprintf; -/** @template-implements Encoder */ +/** @template-implements Encoder */ class BuilderEncoder implements Encoder { + /** @template-use EncodeIfSupported */ use EncodeIfSupported; - /** - * {@inheritdoc} - */ - public function canEncode($value): bool + /** @var array> */ + private array $defaultEncoders = [ + Pipeline::class => PipelineEncoder::class, + Variable::class => VariableEncoder::class, + FieldPathInterface::class => FieldPathEncoder::class, + CombinedFieldQuery::class => CombinedFieldQueryEncoder::class, + QueryObject::class => QueryEncoder::class, + OutputWindow::class => OutputWindowEncoder::class, + OperatorInterface::class => OperatorEncoder::class, + ]; + + /** @var array */ + private array $cachedEncoders = []; + + /** @param array> $customEncoders */ + public function __construct(private readonly array $customEncoders = []) { - return $value instanceof Pipeline - || $value instanceof OperatorInterface - || $value instanceof ExpressionInterface - || $value instanceof QueryInterface - || $value instanceof FieldQueryInterface - || $value instanceof AccumulatorInterface - || $value instanceof ProjectionInterface - || $value instanceof WindowInterface; } - /** - * {@inheritdoc} - */ - public function encode($value): stdClass|array|string + /** @psalm-assert-if-true object $value */ + public function canEncode(mixed $value): bool { - if (! $this->canEncode($value)) { - throw UnsupportedValueException::invalidEncodableValue($value); - } - - // A pipeline is encoded as a list of stages - if ($value instanceof Pipeline) { - $encoded = []; - foreach ($value->getIterator() as $stage) { - $encoded[] = $this->encodeIfSupported($stage); - } - - return $encoded; - } - - // This specific encoding code if temporary until we have a generic way to encode stages and operators - if ($value instanceof FieldPathInterface) { - return '$' . $value->name; - } - - if ($value instanceof Variable) { - return '$$' . $value->name; - } - - if ($value instanceof QueryObject) { - return $this->encodeQueryObject($value); - } - - if ($value instanceof CombinedFieldQuery) { - return $this->encodeCombinedFilter($value); - } - - if ($value instanceof OutputWindow) { - return $this->encodeOutputWindow($value); - } - - if (! $value instanceof OperatorInterface) { - throw new LogicException(sprintf('Class "%s" does not implement OperatorInterface.', $value::class)); - } - - // The generic but incomplete encoding code - switch ($value::ENCODE) { - case Encode::Single: - return $this->encodeAsSingle($value); - - case Encode::Array: - return $this->encodeAsArray($value); - - case Encode::Object: - return $this->encodeAsObject($value); - - case Encode::DollarObject: - return $this->encodeAsDollarObject($value); - - case Encode::Group: - assert($value instanceof GroupStage); - - return $this->encodeAsGroup($value); - } - - throw new LogicException(sprintf('Class "%s" does not have a valid ENCODE constant.', $value::class)); - } - - /** - * Encode the value as an array of properties, in the order they are defined in the class. - */ - private function encodeAsArray(OperatorInterface $value): stdClass - { - $result = []; - /** @var mixed $val */ - foreach (get_object_vars($value) as $val) { - // Skip optional arguments. - // $slice operator has the optional argument in the middle of the array - if ($val === Optional::Undefined) { - continue; - } - - $result[] = $this->recursiveEncode($val); - } - - return $this->wrap($value, $result); - } - - /** - * $group stage have a specific encoding because the _id argument is required and others are variadic - */ - private function encodeAsGroup(GroupStage $value): stdClass - { - $result = new stdClass(); - $result->_id = $this->recursiveEncode($value->_id); - - foreach (get_object_vars($value->field) as $key => $val) { - $result->{$key} = $this->recursiveEncode($val); - } - - return $this->wrap($value, $result); - } - - private function encodeAsObject(OperatorInterface $value): stdClass - { - $result = new stdClass(); - foreach (get_object_vars($value) as $key => $val) { - // Skip optional arguments. If they have a default value, it is resolved by the server. - if ($val === Optional::Undefined) { - continue; - } - - $result->{$key} = $this->recursiveEncode($val); - } - - return $this->wrap($value, $result); - } - - private function encodeAsDollarObject(OperatorInterface $value): stdClass - { - $result = new stdClass(); - foreach (get_object_vars($value) as $key => $val) { - // Skip optional arguments. If they have a default value, it is resolved by the server. - if ($val === Optional::Undefined) { - continue; - } - - $val = $this->recursiveEncode($val); - - if ($key === 'geometry') { - if (is_object($val) && property_exists($val, '$geometry')) { - $result->{'$geometry'} = $val->{'$geometry'}; - } elseif (is_array($val) && array_key_exists('$geometry', $val)) { - $result->{'$geometry'} = $val->{'$geometry'}; - } else { - $result->{'$geometry'} = $val; - } - } else { - $result->{'$' . $key} = $val; - } - } - - return $this->wrap($value, $result); - } - - /** - * Get the unique property of the operator as value - */ - private function encodeAsSingle(OperatorInterface $value): stdClass - { - foreach (get_object_vars($value) as $val) { - $result = $this->recursiveEncode($val); - - return $this->wrap($value, $result); + if (! is_object($value)) { + return false; } - throw new LogicException(sprintf('Class "%s" does not have a single property.', $value::class)); + return (bool) $this->getEncoderFor($value)?->canEncode($value); } - private function encodeCombinedFilter(CombinedFieldQuery $filter): stdClass + public function encode(mixed $value): stdClass|array|string { - $result = new stdClass(); - foreach ($filter->fieldQueries as $filter) { - $filter = $this->recursiveEncode($filter); - if (is_object($filter)) { - $filter = get_object_vars($filter); - } elseif (! is_array($filter)) { - throw new LogicException(sprintf('Query filters must an array or an object. Got "%s"', get_debug_type($filter))); - } - - foreach ($filter as $key => $value) { - $result->{$key} = $value; - } - } - - return $result; - } + $encoder = $this->getEncoderFor($value); - /** - * Query objects are encoded by merging query operator with field path to filter operators in the same object. - */ - private function encodeQueryObject(QueryObject $query): stdClass - { - $result = new stdClass(); - foreach ($query->queries as $key => $value) { - if ($value instanceof QueryInterface) { - // The sub-objects is merged into the main object, replacing duplicate keys - foreach (get_object_vars($this->recursiveEncode($value)) as $subKey => $subValue) { - if (property_exists($result, (string) $subKey)) { - throw new LogicException(sprintf('Duplicate key "%s" in query object', $subKey)); - } - - $result->{$subKey} = $subValue; - } - } else { - if (property_exists($result, (string) $key)) { - throw new LogicException(sprintf('Duplicate key "%s" in query object', $key)); - } - - $result->{$key} = $this->encodeIfSupported($value); - } + if (! $encoder?->canEncode($value)) { + throw UnsupportedValueException::invalidEncodableValue($value); } - return $result; + return $encoder->encode($value); } - /** - * For the $setWindowFields stage output parameter, the optional window parameter is encoded in the same object - * of the window operator. - * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/ - */ - private function encodeOutputWindow(OutputWindow $outputWindow): stdClass + private function getEncoderFor(object $value): ExpressionEncoder|null { - $result = $this->recursiveEncode($outputWindow->operator); - - // Transform the result into an stdClass if a document is provided - if (! $outputWindow->operator instanceof WindowInterface && (is_array($result) || is_object($result))) { - if (! is_first_key_operator($result)) { - throw new LogicException(sprintf('Expected OutputWindow::$operator to be an operator. Got "%s"', array_key_first((array) $result))); - } - - $result = (object) $result; + $valueClass = $value::class; + if (array_key_exists($valueClass, $this->cachedEncoders)) { + return $this->cachedEncoders[$valueClass]; } - if (! $result instanceof stdClass) { - throw new LogicException(sprintf('Expected OutputWindow::$operator to be an stdClass, array or WindowInterface. Got "%s"', get_debug_type($result))); - } + $encoderList = $this->customEncoders + $this->defaultEncoders; - if ($outputWindow->window !== Optional::Undefined) { - $result->window = $this->recursiveEncode($outputWindow->window); + // First attempt: match class name exactly + if (isset($encoderList[$valueClass])) { + return $this->cachedEncoders[$valueClass] = new $encoderList[$valueClass]($this); } - return $result; - } - - /** - * Nested arrays and objects must be encoded recursively. - */ - private function recursiveEncode(mixed $value): mixed - { - if (is_array($value)) { - foreach ($value as $key => $val) { - $value[$key] = $this->recursiveEncode($val); - } - - return $value; - } - - if ($value instanceof stdClass) { - foreach (get_object_vars($value) as $key => $val) { - $value->{$key} = $this->recursiveEncode($val); + // Second attempt: catch child classes + foreach ($encoderList as $className => $encoderClass) { + if ($value instanceof $className) { + return $this->cachedEncoders[$valueClass] = new $encoderClass($this); } - - return $value; } - return $this->encodeIfSupported($value); - } - - private function wrap(OperatorInterface $value, mixed $result): stdClass - { - $object = new stdClass(); - $object->{$value->getOperator()} = $result; - - return $object; + return $this->cachedEncoders[$valueClass] = null; } } diff --git a/src/Builder/Encoder/AbstractExpressionEncoder.php b/src/Builder/Encoder/AbstractExpressionEncoder.php new file mode 100644 index 000000000..8bbdbaef0 --- /dev/null +++ b/src/Builder/Encoder/AbstractExpressionEncoder.php @@ -0,0 +1,53 @@ + + */ +abstract class AbstractExpressionEncoder implements ExpressionEncoder +{ + final public function __construct(protected readonly BuilderEncoder $encoder) + { + } + + /** + * Nested arrays and objects must be encoded recursively. + * + * @psalm-param T $value + * + * @psalm-return (T is stdClass ? stdClass : (T is array ? array : mixed)) + * + * @template T + */ + final protected function recursiveEncode(mixed $value): mixed + { + if (is_array($value)) { + foreach ($value as $key => $val) { + $value[$key] = $this->recursiveEncode($val); + } + + return $value; + } + + if ($value instanceof stdClass) { + foreach (get_object_vars($value) as $key => $val) { + $value->{$key} = $this->recursiveEncode($val); + } + + return $value; + } + + return $this->encoder->encodeIfSupported($value); + } +} diff --git a/src/Builder/Encoder/CombinedFieldQueryEncoder.php b/src/Builder/Encoder/CombinedFieldQueryEncoder.php new file mode 100644 index 000000000..4ad42396b --- /dev/null +++ b/src/Builder/Encoder/CombinedFieldQueryEncoder.php @@ -0,0 +1,52 @@ + */ +class CombinedFieldQueryEncoder extends AbstractExpressionEncoder +{ + /** @template-use EncodeIfSupported */ + use EncodeIfSupported; + + public function canEncode(mixed $value): bool + { + return $value instanceof CombinedFieldQuery; + } + + public function encode(mixed $value): stdClass + { + if (! $this->canEncode($value)) { + throw UnsupportedValueException::invalidEncodableValue($value); + } + + $result = new stdClass(); + foreach ($value->fieldQueries as $filter) { + $filter = $this->recursiveEncode($filter); + if (is_object($filter)) { + $filter = get_object_vars($filter); + } elseif (! is_array($filter)) { + throw new LogicException(sprintf('Query filters must an array or an object. Got "%s"', get_debug_type($filter))); + } + + foreach ($filter as $key => $filterValue) { + $result->{$key} = $filterValue; + } + } + + return $result; + } +} diff --git a/src/Builder/Encoder/ExpressionEncoder.php b/src/Builder/Encoder/ExpressionEncoder.php new file mode 100644 index 000000000..6613c45f2 --- /dev/null +++ b/src/Builder/Encoder/ExpressionEncoder.php @@ -0,0 +1,19 @@ + + */ +interface ExpressionEncoder extends Encoder +{ + public function __construct(BuilderEncoder $encoder); +} diff --git a/src/Builder/Encoder/FieldPathEncoder.php b/src/Builder/Encoder/FieldPathEncoder.php new file mode 100644 index 000000000..8fe7381c2 --- /dev/null +++ b/src/Builder/Encoder/FieldPathEncoder.php @@ -0,0 +1,31 @@ + */ +class FieldPathEncoder extends AbstractExpressionEncoder +{ + /** @template-use EncodeIfSupported */ + use EncodeIfSupported; + + public function canEncode(mixed $value): bool + { + return $value instanceof FieldPathInterface; + } + + public function encode(mixed $value): string + { + if (! $this->canEncode($value)) { + throw UnsupportedValueException::invalidEncodableValue($value); + } + + // TODO: needs method because interfaces can't have properties + return '$' . $value->name; + } +} diff --git a/src/Builder/Encoder/OperatorEncoder.php b/src/Builder/Encoder/OperatorEncoder.php new file mode 100644 index 000000000..4561f22cb --- /dev/null +++ b/src/Builder/Encoder/OperatorEncoder.php @@ -0,0 +1,161 @@ + */ +class OperatorEncoder extends AbstractExpressionEncoder +{ + /** @template-use EncodeIfSupported */ + use EncodeIfSupported; + + public function canEncode(mixed $value): bool + { + return $value instanceof OperatorInterface; + } + + public function encode(mixed $value): stdClass + { + if (! $this->canEncode($value)) { + throw UnsupportedValueException::invalidEncodableValue($value); + } + + switch ($value::ENCODE) { + case Encode::Single: + return $this->encodeAsSingle($value); + + case Encode::Array: + return $this->encodeAsArray($value); + + case Encode::Object: + return $this->encodeAsObject($value); + + case Encode::DollarObject: + return $this->encodeAsDollarObject($value); + + case Encode::Group: + assert($value instanceof GroupStage); + + return $this->encodeAsGroup($value); + } + + throw new LogicException(sprintf('Class "%s" does not have a valid ENCODE constant.', $value::class)); + } + + /** + * Encode the value as an array of properties, in the order they are defined in the class. + */ + private function encodeAsArray(OperatorInterface $value): stdClass + { + $result = []; + /** @var mixed $val */ + foreach (get_object_vars($value) as $val) { + // Skip optional arguments. For example, the $slice expression operator has an optional argument + // in the middle of the array. + if ($val === Optional::Undefined) { + continue; + } + + $result[] = $this->recursiveEncode($val); + } + + return $this->wrap($value, $result); + } + + /** + * $group stage have a specific encoding because the _id argument is required and others are variadic + */ + private function encodeAsGroup(GroupStage $value): stdClass + { + $result = new stdClass(); + $result->_id = $this->recursiveEncode($value->_id); + + foreach (get_object_vars($value->field) as $key => $val) { + $result->{$key} = $this->recursiveEncode($val); + } + + return $this->wrap($value, $result); + } + + private function encodeAsObject(OperatorInterface $value): stdClass + { + $result = new stdClass(); + foreach (get_object_vars($value) as $key => $val) { + // Skip optional arguments. If they have a default value, it is resolved by the server. + if ($val === Optional::Undefined) { + continue; + } + + $result->{$key} = $this->recursiveEncode($val); + } + + return $this->wrap($value, $result); + } + + private function encodeAsDollarObject(OperatorInterface $value): stdClass + { + $result = new stdClass(); + foreach (get_object_vars($value) as $key => $val) { + // Skip optional arguments. If they have a default value, it is resolved by the server. + if ($val === Optional::Undefined) { + continue; + } + + $val = $this->recursiveEncode($val); + + if ($key === 'geometry') { + if (is_object($val) && property_exists($val, '$geometry')) { + $result->{'$geometry'} = $val->{'$geometry'}; + } elseif (is_array($val) && array_key_exists('$geometry', $val)) { + $result->{'$geometry'} = $val['$geometry']; + } else { + $result->{'$geometry'} = $val; + } + } else { + $result->{'$' . $key} = $val; + } + } + + return $this->wrap($value, $result); + } + + /** + * Get the unique property of the operator as value + */ + private function encodeAsSingle(OperatorInterface $value): stdClass + { + foreach (get_object_vars($value) as $val) { + $result = $this->recursiveEncode($val); + + return $this->wrap($value, $result); + } + + throw new LogicException(sprintf('Class "%s" does not have a single property.', $value::class)); + } + + private function wrap(OperatorInterface $value, mixed $result): stdClass + { + $object = new stdClass(); + $object->{$value->getOperator()} = $result; + + return $object; + } +} diff --git a/src/Builder/Encoder/OutputWindowEncoder.php b/src/Builder/Encoder/OutputWindowEncoder.php new file mode 100644 index 000000000..9db567386 --- /dev/null +++ b/src/Builder/Encoder/OutputWindowEncoder.php @@ -0,0 +1,60 @@ + */ +class OutputWindowEncoder extends AbstractExpressionEncoder +{ + /** @template-use EncodeIfSupported */ + use EncodeIfSupported; + + public function canEncode(mixed $value): bool + { + return $value instanceof OutputWindow; + } + + public function encode(mixed $value): stdClass + { + if (! $this->canEncode($value)) { + throw UnsupportedValueException::invalidEncodableValue($value); + } + + $result = $this->recursiveEncode($value->operator); + + // Transform the result into an stdClass if a document is provided + if (! $value->operator instanceof WindowInterface) { + if (! is_first_key_operator($result)) { + $firstKey = array_key_first((array) $result); + + throw new LogicException(sprintf('Expected OutputWindow::$operator to be an operator. Got "%s"', $firstKey ?? 'null')); + } + + $result = (object) $result; + } + + if (! $result instanceof stdClass) { + throw new LogicException(sprintf('Expected OutputWindow::$operator to be an stdClass, array or WindowInterface. Got "%s"', get_debug_type($result))); + } + + if ($value->window !== Optional::Undefined) { + $result->window = $this->recursiveEncode($value->window); + } + + return $result; + } +} diff --git a/src/Builder/Encoder/PipelineEncoder.php b/src/Builder/Encoder/PipelineEncoder.php new file mode 100644 index 000000000..f0b319e9c --- /dev/null +++ b/src/Builder/Encoder/PipelineEncoder.php @@ -0,0 +1,37 @@ +, Pipeline> */ +class PipelineEncoder extends AbstractExpressionEncoder +{ + /** @template-use EncodeIfSupported, Pipeline> */ + use EncodeIfSupported; + + /** @psalm-assert-if-true Pipeline $value */ + public function canEncode(mixed $value): bool + { + return $value instanceof Pipeline; + } + + /** @return list */ + public function encode(mixed $value): array + { + if (! $this->canEncode($value)) { + throw UnsupportedValueException::invalidEncodableValue($value); + } + + $encoded = []; + foreach ($value->getIterator() as $stage) { + $encoded[] = $this->encoder->encodeIfSupported($stage); + } + + return $encoded; + } +} diff --git a/src/Builder/Encoder/QueryEncoder.php b/src/Builder/Encoder/QueryEncoder.php new file mode 100644 index 000000000..d5890506c --- /dev/null +++ b/src/Builder/Encoder/QueryEncoder.php @@ -0,0 +1,57 @@ + */ +class QueryEncoder extends AbstractExpressionEncoder +{ + /** @template-use EncodeIfSupported */ + use EncodeIfSupported; + + public function canEncode(mixed $value): bool + { + return $value instanceof QueryObject; + } + + public function encode(mixed $value): stdClass + { + if (! $this->canEncode($value)) { + throw UnsupportedValueException::invalidEncodableValue($value); + } + + $result = new stdClass(); + foreach ($value->queries as $key => $value) { + if ($value instanceof QueryInterface) { + // The sub-objects is merged into the main object, replacing duplicate keys + foreach (get_object_vars($this->recursiveEncode($value)) as $subKey => $subValue) { + if (property_exists($result, $subKey)) { + throw new LogicException(sprintf('Duplicate key "%s" in query object', $subKey)); + } + + $result->{$subKey} = $subValue; + } + } else { + if (property_exists($result, (string) $key)) { + throw new LogicException(sprintf('Duplicate key "%s" in query object', $key)); + } + + $result->{$key} = $this->encoder->encodeIfSupported($value); + } + } + + return $result; + } +} diff --git a/src/Builder/Encoder/VariableEncoder.php b/src/Builder/Encoder/VariableEncoder.php new file mode 100644 index 000000000..726acb9ee --- /dev/null +++ b/src/Builder/Encoder/VariableEncoder.php @@ -0,0 +1,31 @@ + */ +class VariableEncoder extends AbstractExpressionEncoder +{ + /** @template-use EncodeIfSupported */ + use EncodeIfSupported; + + public function canEncode(mixed $value): bool + { + return $value instanceof Variable; + } + + public function encode(mixed $value): string + { + if (! $this->canEncode($value)) { + throw UnsupportedValueException::invalidEncodableValue($value); + } + + // TODO: needs method because interfaces can't have properties + return '$$' . $value->name; + } +} diff --git a/src/Builder/Type/CombinedFieldQuery.php b/src/Builder/Type/CombinedFieldQuery.php index b85096e5b..7619d0ccc 100644 --- a/src/Builder/Type/CombinedFieldQuery.php +++ b/src/Builder/Type/CombinedFieldQuery.php @@ -37,15 +37,24 @@ public function __construct(array $fieldQueries) } // Flatten nested CombinedFieldQuery - $this->fieldQueries = array_reduce($fieldQueries, static function (array $fieldQueries, QueryInterface|FieldQueryInterface|Type|stdClass|array|bool|float|int|string|null $fieldQuery): array { - if ($fieldQuery instanceof CombinedFieldQuery) { - return array_merge($fieldQueries, $fieldQuery->fieldQueries); - } + $this->fieldQueries = array_reduce( + $fieldQueries, + /** + * @param list $fieldQueries + * + * @return list + */ + static function (array $fieldQueries, QueryInterface|FieldQueryInterface|Type|stdClass|array|bool|float|int|string|null $fieldQuery): array { + if ($fieldQuery instanceof CombinedFieldQuery) { + return array_merge($fieldQueries, $fieldQuery->fieldQueries); + } - $fieldQueries[] = $fieldQuery; + $fieldQueries[] = $fieldQuery; - return $fieldQueries; - }, []); + return $fieldQueries; + }, + [], + ); // Validate FieldQuery types and non-duplicate operators $seenOperators = []; diff --git a/src/Builder/Type/Encode.php b/src/Builder/Type/Encode.php index d5ec60327..c42b15806 100644 --- a/src/Builder/Type/Encode.php +++ b/src/Builder/Type/Encode.php @@ -37,4 +37,9 @@ enum Encode * Specific for $group stage */ case Group; + + /** + * Default case used in the interface; implementing classes are expected to override this value + */ + case Undefined; } diff --git a/src/Builder/Type/OperatorInterface.php b/src/Builder/Type/OperatorInterface.php index 6622dbf60..f219fb52b 100644 --- a/src/Builder/Type/OperatorInterface.php +++ b/src/Builder/Type/OperatorInterface.php @@ -9,5 +9,8 @@ */ interface OperatorInterface { + /** To be overridden by implementing classes */ + public const ENCODE = Encode::Undefined; + public function getOperator(): string; } diff --git a/src/Builder/Type/QueryObject.php b/src/Builder/Type/QueryObject.php index 293350bd9..e07877f3f 100644 --- a/src/Builder/Type/QueryObject.php +++ b/src/Builder/Type/QueryObject.php @@ -4,9 +4,7 @@ namespace MongoDB\Builder\Type; -use MongoDB\BSON\Decimal128; -use MongoDB\BSON\Int64; -use MongoDB\BSON\Regex; +use MongoDB\BSON\Type; use MongoDB\Exception\InvalidArgumentException; use stdClass; @@ -27,7 +25,7 @@ final class QueryObject implements QueryInterface { public readonly array $queries; - /** @param array $queries */ + /** @param array $queries */ public static function create(array $queries): QueryInterface { // We don't wrap a single query in a QueryObject @@ -38,7 +36,7 @@ public static function create(array $queries): QueryInterface return new self($queries); } - /** @param array $queriesOrArrayOfQueries */ + /** @param array $queriesOrArrayOfQueries */ private function __construct(array $queriesOrArrayOfQueries) { // If the first element is an array and not an operator, we assume variadic arguments were not used From a99f4723de4268d9885f117c06481c528888e3be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 29 Jan 2024 10:38:11 +0100 Subject: [PATCH 51/95] PHPLIB-1342 Add tests on Arithmetic Expression Operators (#52) --- generator/config/expression/abs.yaml | 12 + generator/config/expression/ceil.yaml | 10 + generator/config/expression/divide.yaml | 12 + generator/config/expression/exp.yaml | 12 + generator/config/expression/floor.yaml | 10 + generator/config/expression/ln.yaml | 10 + generator/config/expression/log.yaml | 15 + generator/config/expression/log10.yaml | 12 + generator/config/expression/mod.yaml | 11 + generator/config/expression/multiply.yaml | 13 + generator/config/expression/pow.yaml | 14 + generator/config/expression/round.yaml | 11 + generator/config/expression/sqrt.yaml | 24 ++ generator/config/expression/trunc.yaml | 11 + tests/Builder/Expression/AbsOperatorTest.php | 32 ++ tests/Builder/Expression/CeilOperatorTest.php | 30 ++ .../Builder/Expression/DivideOperatorTest.php | 31 ++ tests/Builder/Expression/ExpOperatorTest.php | 32 ++ .../Builder/Expression/FloorOperatorTest.php | 30 ++ tests/Builder/Expression/LnOperatorTest.php | 30 ++ .../Builder/Expression/Log10OperatorTest.php | 32 ++ tests/Builder/Expression/LogOperatorTest.php | 35 ++ tests/Builder/Expression/ModOperatorTest.php | 30 ++ .../Expression/MultiplyOperatorTest.php | 32 ++ tests/Builder/Expression/Pipelines.php | 346 ++++++++++++++++++ tests/Builder/Expression/PowOperatorTest.php | 32 ++ .../Builder/Expression/RoundOperatorTest.php | 30 ++ tests/Builder/Expression/SqrtOperatorTest.php | 44 +++ .../Builder/Expression/TruncOperatorTest.php | 30 ++ 29 files changed, 973 insertions(+) create mode 100644 tests/Builder/Expression/AbsOperatorTest.php create mode 100644 tests/Builder/Expression/CeilOperatorTest.php create mode 100644 tests/Builder/Expression/DivideOperatorTest.php create mode 100644 tests/Builder/Expression/ExpOperatorTest.php create mode 100644 tests/Builder/Expression/FloorOperatorTest.php create mode 100644 tests/Builder/Expression/LnOperatorTest.php create mode 100644 tests/Builder/Expression/Log10OperatorTest.php create mode 100644 tests/Builder/Expression/LogOperatorTest.php create mode 100644 tests/Builder/Expression/ModOperatorTest.php create mode 100644 tests/Builder/Expression/MultiplyOperatorTest.php create mode 100644 tests/Builder/Expression/PowOperatorTest.php create mode 100644 tests/Builder/Expression/RoundOperatorTest.php create mode 100644 tests/Builder/Expression/SqrtOperatorTest.php create mode 100644 tests/Builder/Expression/TruncOperatorTest.php diff --git a/generator/config/expression/abs.yaml b/generator/config/expression/abs.yaml index 029b33d4c..fe29e44e3 100644 --- a/generator/config/expression/abs.yaml +++ b/generator/config/expression/abs.yaml @@ -11,3 +11,15 @@ arguments: name: value type: - resolvesToNumber +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/abs/#example' + pipeline: + - + $project: + delta: + $abs: + $subtract: + - '$startTemp' + - '$endTemp' diff --git a/generator/config/expression/ceil.yaml b/generator/config/expression/ceil.yaml index b33567720..73c31ddb7 100644 --- a/generator/config/expression/ceil.yaml +++ b/generator/config/expression/ceil.yaml @@ -13,3 +13,13 @@ arguments: - resolvesToNumber description: | If the argument resolves to a value of null or refers to a field that is missing, $ceil returns null. If the argument resolves to NaN, $ceil returns NaN. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/ceil/#example' + pipeline: + - + $project: + value: 1 + ceilingValue: + $ceil: '$value' diff --git a/generator/config/expression/divide.yaml b/generator/config/expression/divide.yaml index 66b4eb067..3b69389d8 100644 --- a/generator/config/expression/divide.yaml +++ b/generator/config/expression/divide.yaml @@ -17,3 +17,15 @@ arguments: name: divisor type: - resolvesToNumber +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/divide/#example' + pipeline: + - + $project: + city: 1 + workdays: + $divide: + - '$hours' + - 8 diff --git a/generator/config/expression/exp.yaml b/generator/config/expression/exp.yaml index 9df1cc391..d1f6982a1 100644 --- a/generator/config/expression/exp.yaml +++ b/generator/config/expression/exp.yaml @@ -11,3 +11,15 @@ arguments: name: exponent type: - resolvesToNumber +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/exp/#example' + pipeline: + - + $project: + effectiveRate: + $subtract: + - + $exp: '$interestRate' + - 1 diff --git a/generator/config/expression/floor.yaml b/generator/config/expression/floor.yaml index f497e2694..4c5856264 100644 --- a/generator/config/expression/floor.yaml +++ b/generator/config/expression/floor.yaml @@ -11,3 +11,13 @@ arguments: name: expression type: - resolvesToNumber +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/floor/#example' + pipeline: + - + $project: + value: 1 + floorValue: + $floor: '$value' diff --git a/generator/config/expression/ln.yaml b/generator/config/expression/ln.yaml index f4660df8a..f7412aeb9 100644 --- a/generator/config/expression/ln.yaml +++ b/generator/config/expression/ln.yaml @@ -14,3 +14,13 @@ arguments: - resolvesToNumber description: | Any valid expression as long as it resolves to a non-negative number. For more information on expressions, see Expressions. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/ln/#example' + pipeline: + - + $project: + x: '$year' + y: + $ln: '$sales' diff --git a/generator/config/expression/log.yaml b/generator/config/expression/log.yaml index becc32437..462eb9492 100644 --- a/generator/config/expression/log.yaml +++ b/generator/config/expression/log.yaml @@ -19,3 +19,18 @@ arguments: - resolvesToNumber description: | Any valid expression as long as it resolves to a positive number greater than 1. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/log/#example' + pipeline: + - + $project: + bitsNeeded: + $floor: + $add: + - 1 + - + $log: + - '$int' + - 2 diff --git a/generator/config/expression/log10.yaml b/generator/config/expression/log10.yaml index 2ca26e535..77cab075a 100644 --- a/generator/config/expression/log10.yaml +++ b/generator/config/expression/log10.yaml @@ -13,3 +13,15 @@ arguments: - resolvesToNumber description: | Any valid expression as long as it resolves to a non-negative number. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/log10/#example' + pipeline: + - + $project: + pH: + $multiply: + - -1 + - + $log10: '$H3O' diff --git a/generator/config/expression/mod.yaml b/generator/config/expression/mod.yaml index 24bfa56db..0ac48bd54 100644 --- a/generator/config/expression/mod.yaml +++ b/generator/config/expression/mod.yaml @@ -17,3 +17,14 @@ arguments: name: divisor type: - resolvesToNumber +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/mod/#example' + pipeline: + - + $project: + remainder: + $mod: + - '$hours' + - '$tasks' diff --git a/generator/config/expression/multiply.yaml b/generator/config/expression/multiply.yaml index 855616e52..5a069cc8c 100644 --- a/generator/config/expression/multiply.yaml +++ b/generator/config/expression/multiply.yaml @@ -15,3 +15,16 @@ arguments: description: | The arguments can be any valid expression as long as they resolve to numbers. Starting in MongoDB 6.1 you can optimize the $multiply operation. To improve performance, group references at the end of the argument list. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/multiply/#example' + pipeline: + - + $project: + date: 1 + item: 1 + total: + $multiply: + - '$price' + - '$quantity' diff --git a/generator/config/expression/pow.yaml b/generator/config/expression/pow.yaml index 4d9d2b489..71d3be2b3 100644 --- a/generator/config/expression/pow.yaml +++ b/generator/config/expression/pow.yaml @@ -15,3 +15,17 @@ arguments: name: exponent type: - resolvesToNumber +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/pow/#example' + pipeline: + - + $project: + variance: + $pow: + - + # The builder renders $stdDevPop with the array form, even with a single value + # $stdDevPop: '$scores.score' + $stdDevPop: ['$scores.score'] + - 2 diff --git a/generator/config/expression/round.yaml b/generator/config/expression/round.yaml index 8f0fae277..52e6004a0 100644 --- a/generator/config/expression/round.yaml +++ b/generator/config/expression/round.yaml @@ -27,3 +27,14 @@ arguments: optional: true description: | Can be any valid expression that resolves to an integer between -20 and 100, exclusive. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/#example' + pipeline: + - + $project: + roundedValue: + $round: + - '$value' + - 1 diff --git a/generator/config/expression/sqrt.yaml b/generator/config/expression/sqrt.yaml index 7b7fb0680..52f5bb7c2 100644 --- a/generator/config/expression/sqrt.yaml +++ b/generator/config/expression/sqrt.yaml @@ -13,3 +13,27 @@ arguments: - resolvesToNumber description: | The argument can be any valid expression as long as it resolves to a non-negative number. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sqrt/#example' + pipeline: + - + $project: + distance: + $sqrt: + $add: + - + $pow: + - + $subtract: + - '$p2.y' + - '$p1.y' + - 2 + - + $pow: + - + $subtract: + - '$p2.x' + - '$p1.x' + - 2 diff --git a/generator/config/expression/trunc.yaml b/generator/config/expression/trunc.yaml index 3d5871305..f930cf027 100644 --- a/generator/config/expression/trunc.yaml +++ b/generator/config/expression/trunc.yaml @@ -21,3 +21,14 @@ arguments: optional: true description: | Can be any valid expression that resolves to an integer between -20 and 100, exclusive. e.g. -20 < place < 100. Defaults to 0. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/trunc/#example' + pipeline: + - + $project: + truncatedValue: + $trunc: + - '$value' + - 1 diff --git a/tests/Builder/Expression/AbsOperatorTest.php b/tests/Builder/Expression/AbsOperatorTest.php new file mode 100644 index 000000000..829c9c551 --- /dev/null +++ b/tests/Builder/Expression/AbsOperatorTest.php @@ -0,0 +1,32 @@ +assertSamePipeline(Pipelines::AbsExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/CeilOperatorTest.php b/tests/Builder/Expression/CeilOperatorTest.php new file mode 100644 index 000000000..6d78af6a3 --- /dev/null +++ b/tests/Builder/Expression/CeilOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::CeilExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/DivideOperatorTest.php b/tests/Builder/Expression/DivideOperatorTest.php new file mode 100644 index 000000000..dfccd98eb --- /dev/null +++ b/tests/Builder/Expression/DivideOperatorTest.php @@ -0,0 +1,31 @@ +assertSamePipeline(Pipelines::DivideExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ExpOperatorTest.php b/tests/Builder/Expression/ExpOperatorTest.php new file mode 100644 index 000000000..daa582518 --- /dev/null +++ b/tests/Builder/Expression/ExpOperatorTest.php @@ -0,0 +1,32 @@ +assertSamePipeline(Pipelines::ExpExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/FloorOperatorTest.php b/tests/Builder/Expression/FloorOperatorTest.php new file mode 100644 index 000000000..8aa3c8393 --- /dev/null +++ b/tests/Builder/Expression/FloorOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::FloorExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/LnOperatorTest.php b/tests/Builder/Expression/LnOperatorTest.php new file mode 100644 index 000000000..cbbb85ce2 --- /dev/null +++ b/tests/Builder/Expression/LnOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::LnExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Log10OperatorTest.php b/tests/Builder/Expression/Log10OperatorTest.php new file mode 100644 index 000000000..eb7e6b693 --- /dev/null +++ b/tests/Builder/Expression/Log10OperatorTest.php @@ -0,0 +1,32 @@ +assertSamePipeline(Pipelines::Log10Example, $pipeline); + } +} diff --git a/tests/Builder/Expression/LogOperatorTest.php b/tests/Builder/Expression/LogOperatorTest.php new file mode 100644 index 000000000..f1215d53e --- /dev/null +++ b/tests/Builder/Expression/LogOperatorTest.php @@ -0,0 +1,35 @@ +assertSamePipeline(Pipelines::LogExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/ModOperatorTest.php b/tests/Builder/Expression/ModOperatorTest.php new file mode 100644 index 000000000..5951779c4 --- /dev/null +++ b/tests/Builder/Expression/ModOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::ModExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/MultiplyOperatorTest.php b/tests/Builder/Expression/MultiplyOperatorTest.php new file mode 100644 index 000000000..546c4185c --- /dev/null +++ b/tests/Builder/Expression/MultiplyOperatorTest.php @@ -0,0 +1,32 @@ +assertSamePipeline(Pipelines::MultiplyExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index d5b78ce6d..868978d15 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -10,6 +10,28 @@ enum Pipelines: string { + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/abs/#example + */ + case AbsExample = <<<'JSON' + [ + { + "$project": { + "delta": { + "$abs": { + "$subtract": [ + "$startTemp", + "$endTemp" + ] + } + } + } + } + ] + JSON; + /** * Example * @@ -638,6 +660,26 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ceil/#example + */ + case CeilExample = <<<'JSON' + [ + { + "$project": { + "value": { + "$numberInt": "1" + }, + "ceilingValue": { + "$ceil": "$value" + } + } + } + ] + JSON; + /** * Example * @@ -1710,6 +1752,31 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/divide/#example + */ + case DivideExample = <<<'JSON' + [ + { + "$project": { + "city": { + "$numberInt": "1" + }, + "workdays": { + "$divide": [ + "$hours", + { + "$numberInt": "8" + } + ] + } + } + } + ] + JSON; + /** * Example * @@ -1741,6 +1808,30 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/exp/#example + */ + case ExpExample = <<<'JSON' + [ + { + "$project": { + "effectiveRate": { + "$subtract": [ + { + "$exp": "$interestRate" + }, + { + "$numberInt": "1" + } + ] + } + } + } + ] + JSON; + /** * Example * @@ -1939,6 +2030,26 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/floor/#example + */ + case FloorExample = <<<'JSON' + [ + { + "$project": { + "value": { + "$numberInt": "1" + }, + "floorValue": { + "$floor": "$value" + } + } + } + ] + JSON; + /** * Usage Example * @@ -2630,6 +2741,79 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ln/#example + */ + case LnExample = <<<'JSON' + [ + { + "$project": { + "x": "$year", + "y": { + "$ln": "$sales" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/log/#example + */ + case LogExample = <<<'JSON' + [ + { + "$project": { + "bitsNeeded": { + "$floor": { + "$add": [ + { + "$numberInt": "1" + }, + { + "$log": [ + "$int", + { + "$numberInt": "2" + } + ] + } + ] + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/log10/#example + */ + case Log10Example = <<<'JSON' + [ + { + "$project": { + "pH": { + "$multiply": [ + { + "$numberInt": "-1" + }, + { + "$log10": "$H3O" + } + ] + } + } + } + ] + JSON; + /** * Example * @@ -3017,6 +3201,26 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/mod/#example + */ + case ModExample = <<<'JSON' + [ + { + "$project": { + "remainder": { + "$mod": [ + "$hours", + "$tasks" + ] + } + } + } + ] + JSON; + /** * Example * @@ -3036,6 +3240,32 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/multiply/#example + */ + case MultiplyExample = <<<'JSON' + [ + { + "$project": { + "date": { + "$numberInt": "1" + }, + "item": { + "$numberInt": "1" + }, + "total": { + "$multiply": [ + "$price", + "$quantity" + ] + } + } + } + ] + JSON; + /** * Example * @@ -3221,6 +3451,32 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/pow/#example + */ + case PowExample = <<<'JSON' + [ + { + "$project": { + "variance": { + "$pow": [ + { + "$stdDevPop": [ + "$scores.score" + ] + }, + { + "$numberInt": "2" + } + ] + } + } + } + ] + JSON; + /** * Example * @@ -3937,6 +4193,28 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/#example + */ + case RoundExample = <<<'JSON' + [ + { + "$project": { + "roundedValue": { + "$round": [ + "$value", + { + "$numberInt": "1" + } + ] + } + } + } + ] + JSON; + /** * Example * @@ -4643,6 +4921,52 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sqrt/#example + */ + case SqrtExample = <<<'JSON' + [ + { + "$project": { + "distance": { + "$sqrt": { + "$add": [ + { + "$pow": [ + { + "$subtract": [ + "$p2.y", + "$p1.y" + ] + }, + { + "$numberInt": "2" + } + ] + }, + { + "$pow": [ + { + "$subtract": [ + "$p2.x", + "$p1.x" + ] + }, + { + "$numberInt": "2" + } + ] + } + ] + } + } + } + } + ] + JSON; + /** * Use in $project Stage * @@ -5435,6 +5759,28 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/trunc/#example + */ + case TruncExample = <<<'JSON' + [ + { + "$project": { + "truncatedValue": { + "$trunc": [ + "$value", + { + "$numberInt": "1" + } + ] + } + } + } + ] + JSON; + /** * Obtain the Incrementing Ordinal from a Timestamp Field * diff --git a/tests/Builder/Expression/PowOperatorTest.php b/tests/Builder/Expression/PowOperatorTest.php new file mode 100644 index 000000000..4af88159e --- /dev/null +++ b/tests/Builder/Expression/PowOperatorTest.php @@ -0,0 +1,32 @@ +assertSamePipeline(Pipelines::PowExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/RoundOperatorTest.php b/tests/Builder/Expression/RoundOperatorTest.php new file mode 100644 index 000000000..accae5074 --- /dev/null +++ b/tests/Builder/Expression/RoundOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::RoundExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/SqrtOperatorTest.php b/tests/Builder/Expression/SqrtOperatorTest.php new file mode 100644 index 000000000..de33969f5 --- /dev/null +++ b/tests/Builder/Expression/SqrtOperatorTest.php @@ -0,0 +1,44 @@ +assertSamePipeline(Pipelines::SqrtExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/TruncOperatorTest.php b/tests/Builder/Expression/TruncOperatorTest.php new file mode 100644 index 000000000..45f1826be --- /dev/null +++ b/tests/Builder/Expression/TruncOperatorTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::TruncExample, $pipeline); + } +} From c0daa64a75590465d6c052ed78b60b31814bff72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 29 Jan 2024 10:46:13 +0100 Subject: [PATCH 52/95] PHPLIB-1341 Add tests on Miscellaneous Query Operators (#53) --- generator/config/query/comment.yaml | 18 +++++ generator/config/query/rand.yaml | 18 +++++ generator/js2yaml.html | 2 +- tests/Builder/Query/CommentOperatorTest.php | 39 +++++++++++ tests/Builder/Query/Pipelines.php | 75 +++++++++++++++++++++ tests/Builder/Query/RandOperatorTest.php | 39 +++++++++++ 6 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 tests/Builder/Query/CommentOperatorTest.php create mode 100644 tests/Builder/Query/RandOperatorTest.php diff --git a/generator/config/query/comment.yaml b/generator/config/query/comment.yaml index b5b0fb25c..13a344613 100644 --- a/generator/config/query/comment.yaml +++ b/generator/config/query/comment.yaml @@ -11,3 +11,21 @@ arguments: name: comment type: - string +tests: + - + name: 'Attach a Comment to an Aggregation Expression' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/comment/#attach-a-comment-to-an-aggregation-expression' + pipeline: + - + $match: + x: + $gt: 0 + $comment: 'Don''t allow negative inputs.' + - + $group: + _id: + $mod: + - '$x' + - 2 + total: + $sum: '$x' diff --git a/generator/config/query/rand.yaml b/generator/config/query/rand.yaml index 809cb6b65..6773ae0d5 100644 --- a/generator/config/query/rand.yaml +++ b/generator/config/query/rand.yaml @@ -6,3 +6,21 @@ type: encode: object description: | Generates a random float between 0 and 1. +tests: + - + name: 'Select Random Items From a Collection' + link: 'https://www.mongodb.com/docs/manual/reference/operator/query/rand/#select-random-items-from-a-collection' + pipeline: + - + $match: + district: 3 + $expr: + $lt: + - 0.5 + - + $rand: {} + - + $project: + _id: 0 + name: 1 + registered: 1 diff --git a/generator/js2yaml.html b/generator/js2yaml.html index 89e0dd594..eeaa72329 100644 --- a/generator/js2yaml.html +++ b/generator/js2yaml.html @@ -124,7 +124,7 @@

Convert JS examples into Yaml

case 'boolean': return object ? ' true' : ' false'; case 'string': - return " '" + object.replace(/'/g, "\\'") + "'"; + return " '" + object.replace(/'/g, "''") + "'"; case 'number': return ' ' + object.toString(); case 'object': diff --git a/tests/Builder/Query/CommentOperatorTest.php b/tests/Builder/Query/CommentOperatorTest.php new file mode 100644 index 000000000..4b94bb56b --- /dev/null +++ b/tests/Builder/Query/CommentOperatorTest.php @@ -0,0 +1,39 @@ +assertSamePipeline(Pipelines::CommentAttachACommentToAnAggregationExpression, $pipeline); + } +} diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 0de857ec2..d72636a54 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -403,6 +403,41 @@ enum Pipelines: string ] JSON; + /** + * Attach a Comment to an Aggregation Expression + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/comment/#attach-a-comment-to-an-aggregation-expression + */ + case CommentAttachACommentToAnAggregationExpression = <<<'JSON' + [ + { + "$match": { + "x": { + "$gt": { + "$numberInt": "0" + } + }, + "$comment": "Don't allow negative inputs." + } + }, + { + "$group": { + "_id": { + "$mod": [ + "$x", + { + "$numberInt": "2" + } + ] + }, + "total": { + "$sum": "$x" + } + } + } + ] + JSON; + /** * Element Match * @@ -1578,6 +1613,46 @@ enum Pipelines: string ] JSON; + /** + * Select Random Items From a Collection + * + * @see https://www.mongodb.com/docs/manual/reference/operator/query/rand/#select-random-items-from-a-collection + */ + case RandSelectRandomItemsFromACollection = <<<'JSON' + [ + { + "$match": { + "district": { + "$numberInt": "3" + }, + "$expr": { + "$lt": [ + { + "$numberDouble": "0.5" + }, + { + "$rand": {} + } + ] + } + } + }, + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "name": { + "$numberInt": "1" + }, + "registered": { + "$numberInt": "1" + } + } + } + ] + JSON; + /** * Perform a LIKE Match * diff --git a/tests/Builder/Query/RandOperatorTest.php b/tests/Builder/Query/RandOperatorTest.php new file mode 100644 index 000000000..9363bd206 --- /dev/null +++ b/tests/Builder/Query/RandOperatorTest.php @@ -0,0 +1,39 @@ +assertSamePipeline(Pipelines::RandSelectRandomItemsFromACollection, $pipeline); + } +} From edd6a1cec57d51000dea8e40b84ecdf4bc327fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 31 Jan 2024 11:45:54 +0100 Subject: [PATCH 53/95] PHPLIB-1340 Remove Projection Operators (#54) They can't be used in $project stage as I was expecting. They are dedicated to the find command. The $slice must be used as an expression (with the array as 1st parameter) and $elemMatch is replaced by $filter. --- generator/config/definitions.php | 12 ---- generator/config/expression/slice.yaml | 1 - generator/config/expressions.php | 4 -- generator/config/projection/elemMatch.yaml | 13 ---- generator/config/projection/filter.yaml | 34 ---------- generator/config/projection/slice.yaml | 17 ----- generator/config/query/natural.yaml | 8 --- generator/config/schema.json | 2 - generator/config/stage/project.yaml | 1 - src/Builder/Expression/SliceOperator.php | 3 +- src/Builder/Projection/FactoryTrait.php | 38 ----------- src/Builder/Projection/FilterOperator.php | 76 ---------------------- src/Builder/Projection/SliceOperator.php | 44 ------------- src/Builder/Query/FactoryTrait.php | 10 --- src/Builder/Query/NaturalOperator.php | 32 --------- src/Builder/Stage/FactoryTrait.php | 5 +- src/Builder/Stage/ProjectStage.php | 12 ++-- tests/Builder/BuilderEncoderTest.php | 3 +- tests/Builder/Projection/Pipelines.php | 13 ---- 19 files changed, 8 insertions(+), 320 deletions(-) delete mode 100644 generator/config/projection/elemMatch.yaml delete mode 100644 generator/config/projection/filter.yaml delete mode 100644 generator/config/projection/slice.yaml delete mode 100644 generator/config/query/natural.yaml delete mode 100644 src/Builder/Projection/FilterOperator.php delete mode 100644 src/Builder/Projection/SliceOperator.php delete mode 100644 src/Builder/Query/NaturalOperator.php delete mode 100644 tests/Builder/Projection/Pipelines.php diff --git a/generator/config/definitions.php b/generator/config/definitions.php index baf101e62..ca3854c2f 100644 --- a/generator/config/definitions.php +++ b/generator/config/definitions.php @@ -56,16 +56,4 @@ OperatorTestGenerator::class, ], ], - - // Projection Operators - [ - 'configFiles' => __DIR__ . '/projection', - 'namespace' => 'MongoDB\\Builder\\Projection', - 'classNameSuffix' => 'Operator', - 'generators' => [ - OperatorClassGenerator::class, - OperatorFactoryGenerator::class, - OperatorTestGenerator::class, - ], - ], ]; diff --git a/generator/config/expression/slice.yaml b/generator/config/expression/slice.yaml index d0abbbbed..22cc287e1 100644 --- a/generator/config/expression/slice.yaml +++ b/generator/config/expression/slice.yaml @@ -3,7 +3,6 @@ name: $slice link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/slice/' type: - resolvesToArray - - projection encode: array description: | Returns a subset of an array. diff --git a/generator/config/expressions.php b/generator/config/expressions.php index 41cc3ecf9..76e54fead 100644 --- a/generator/config/expressions.php +++ b/generator/config/expressions.php @@ -89,10 +89,6 @@ 'returnType' => Type\QueryInterface::class, 'acceptedTypes' => [Type\QueryInterface::class, 'array'], ], - 'projection' => [ - 'returnType' => Type\ProjectionInterface::class, - 'acceptedTypes' => [Type\ProjectionInterface::class, ...$bsonTypes['object']], - ], 'accumulator' => [ 'returnType' => Type\AccumulatorInterface::class, 'acceptedTypes' => [Type\AccumulatorInterface::class, ...$bsonTypes['object']], diff --git a/generator/config/projection/elemMatch.yaml b/generator/config/projection/elemMatch.yaml deleted file mode 100644 index 32f6ccd71..000000000 --- a/generator/config/projection/elemMatch.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# $schema: ../schema.json -name: $elemMatch -link: 'https://www.mongodb.com/docs/manual/reference/operator/projection/elemMatch/' -type: - - projection -encode: single -description: | - Projects the first element in an array that matches the specified $elemMatch condition. -arguments: - - - name: query - type: - - query diff --git a/generator/config/projection/filter.yaml b/generator/config/projection/filter.yaml deleted file mode 100644 index 50cfaaedf..000000000 --- a/generator/config/projection/filter.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# $schema: ../schema.json -name: $filter -link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/' -type: - - projection -encode: object -description: | - Selects a subset of the array to return an array with only the elements that match the filter condition. -arguments: - - - name: input - type: - - resolvesToArray - - - name: cond - type: - - resolvesToBool - description: | - An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. - - - name: as - type: - - string - optional: true - description: | - A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. - - - name: limit - type: - - resolvesToInt - optional: true - description: | - A number expression that restricts the number of matching array elements that $filter returns. You cannot specify a limit less than 1. The matching array elements are returned in the order they appear in the input array. - If the specified limit is greater than the number of matching array elements, $filter returns all matching array elements. If the limit is null, $filter returns all matching array elements. diff --git a/generator/config/projection/slice.yaml b/generator/config/projection/slice.yaml deleted file mode 100644 index cfab8f4a7..000000000 --- a/generator/config/projection/slice.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# $schema: ../schema.json -name: $slice -link: 'https://www.mongodb.com/docs/manual/reference/operator/projection/slice/' -type: - - projection -encode: array -description: | - Limits the number of elements projected from an array. Supports skip and limit slices. -arguments: - - - name: limit - type: - - int - - - name: skip - type: - - int diff --git a/generator/config/query/natural.yaml b/generator/config/query/natural.yaml deleted file mode 100644 index 0d81e9063..000000000 --- a/generator/config/query/natural.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# $schema: ../schema.json -name: $natural -link: 'https://www.mongodb.com/docs/manual/reference/operator/meta/natural/' -type: - - projection # @todo: used in sort -encode: object -description: | - A special hint that can be provided via the sort() or hint() methods that can be used to force either a forward or reverse collection scan. diff --git a/generator/config/schema.json b/generator/config/schema.json index 11ccc1e32..b48b65a98 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -23,7 +23,6 @@ "type": "string", "enum": [ "accumulator", - "projection", "stage", "query", "fieldQuery", @@ -113,7 +112,6 @@ "window", "expression", "geometry", - "projection", "fieldPath", "any", "resolvesToNumber", "numberFieldPath", "number", diff --git a/generator/config/stage/project.yaml b/generator/config/stage/project.yaml index ee45a0fa9..2337940b4 100644 --- a/generator/config/stage/project.yaml +++ b/generator/config/stage/project.yaml @@ -11,5 +11,4 @@ arguments: name: specification type: - expression - - projection variadic: object diff --git a/src/Builder/Expression/SliceOperator.php b/src/Builder/Expression/SliceOperator.php index 2f4a70905..df9172133 100644 --- a/src/Builder/Expression/SliceOperator.php +++ b/src/Builder/Expression/SliceOperator.php @@ -12,7 +12,6 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; -use MongoDB\Builder\Type\ProjectionInterface; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; @@ -24,7 +23,7 @@ * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/slice/ */ -class SliceOperator implements ResolvesToArray, ProjectionInterface, OperatorInterface +class SliceOperator implements ResolvesToArray, OperatorInterface { public const ENCODE = Encode::Array; diff --git a/src/Builder/Projection/FactoryTrait.php b/src/Builder/Projection/FactoryTrait.php index d3f1d2382..359926ded 100644 --- a/src/Builder/Projection/FactoryTrait.php +++ b/src/Builder/Projection/FactoryTrait.php @@ -8,13 +8,7 @@ namespace MongoDB\Builder\Projection; -use MongoDB\BSON\PackedArray; -use MongoDB\Builder\Expression\ResolvesToArray; -use MongoDB\Builder\Expression\ResolvesToBool; -use MongoDB\Builder\Expression\ResolvesToInt; -use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\QueryInterface; -use MongoDB\Model\BSONArray; /** * @internal @@ -31,36 +25,4 @@ public static function elemMatch(QueryInterface|array $query): ElemMatchOperator { return new ElemMatchOperator($query); } - - /** - * Selects a subset of the array to return an array with only the elements that match the filter condition. - * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/filter/ - * @param BSONArray|PackedArray|ResolvesToArray|array $input - * @param ResolvesToBool|bool $cond An expression that resolves to a boolean value used to determine if an element should be included in the output array. The expression references each element of the input array individually with the variable name specified in as. - * @param Optional|string $as A name for the variable that represents each individual element of the input array. If no name is specified, the variable name defaults to this. - * @param Optional|ResolvesToInt|int $limit A number expression that restricts the number of matching array elements that $filter returns. You cannot specify a limit less than 1. The matching array elements are returned in the order they appear in the input array. - * If the specified limit is greater than the number of matching array elements, $filter returns all matching array elements. If the limit is null, $filter returns all matching array elements. - */ - public static function filter( - PackedArray|ResolvesToArray|BSONArray|array $input, - ResolvesToBool|bool $cond, - Optional|string $as = Optional::Undefined, - Optional|ResolvesToInt|int $limit = Optional::Undefined, - ): FilterOperator - { - return new FilterOperator($input, $cond, $as, $limit); - } - - /** - * Limits the number of elements projected from an array. Supports skip and limit slices. - * - * @see https://www.mongodb.com/docs/manual/reference/operator/projection/slice/ - * @param int $limit - * @param int $skip - */ - public static function slice(int $limit, int $skip): SliceOperator - { - return new SliceOperator($limit, $skip); - } } diff --git a/src/Builder/Projection/FilterOperator.php b/src/Builder/Projection/FilterOperator.php deleted file mode 100644 index 3b29e287d..000000000 --- a/src/Builder/Projection/FilterOperator.php +++ /dev/null @@ -1,76 +0,0 @@ -input = $input; - $this->cond = $cond; - $this->as = $as; - $this->limit = $limit; - } - - public function getOperator(): string - { - return '$filter'; - } -} diff --git a/src/Builder/Projection/SliceOperator.php b/src/Builder/Projection/SliceOperator.php deleted file mode 100644 index 5f62076f3..000000000 --- a/src/Builder/Projection/SliceOperator.php +++ /dev/null @@ -1,44 +0,0 @@ -limit = $limit; - $this->skip = $skip; - } - - public function getOperator(): string - { - return '$slice'; - } -} diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index cc480de67..fc8cb80af 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -339,16 +339,6 @@ public static function mod( return new ModOperator($divisor, $remainder); } - /** - * A special hint that can be provided via the sort() or hint() methods that can be used to force either a forward or reverse collection scan. - * - * @see https://www.mongodb.com/docs/manual/reference/operator/meta/natural/ - */ - public static function natural(): NaturalOperator - { - return new NaturalOperator(); - } - /** * Matches all values that are not equal to a specified value. * diff --git a/src/Builder/Query/NaturalOperator.php b/src/Builder/Query/NaturalOperator.php deleted file mode 100644 index 409920903..000000000 --- a/src/Builder/Query/NaturalOperator.php +++ /dev/null @@ -1,32 +0,0 @@ - $specification */ + /** @var stdClass $specification */ public readonly stdClass $specification; /** - * @param Document|ExpressionInterface|ProjectionInterface|Serializable|Type|array|bool|float|int|null|stdClass|string ...$specification + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$specification */ - public function __construct( - Document|Serializable|Type|ExpressionInterface|ProjectionInterface|stdClass|array|bool|float|int|null|string ...$specification, - ) { + public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$specification) + { if (\count($specification) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $specification, got %d.', 1, \count($specification))); } diff --git a/tests/Builder/BuilderEncoderTest.php b/tests/Builder/BuilderEncoderTest.php index b28078599..32ecce738 100644 --- a/tests/Builder/BuilderEncoderTest.php +++ b/tests/Builder/BuilderEncoderTest.php @@ -10,7 +10,6 @@ use MongoDB\Builder\BuilderEncoder; use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; -use MongoDB\Builder\Projection; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; use MongoDB\Builder\Variable; @@ -157,7 +156,7 @@ public function testExpressionFilter(array $limit, array $expectedLimit): void { $pipeline = new Pipeline( Stage::project( - items: Projection::filter( + items: Expression::filter( ...$limit, input: Expression::arrayFieldPath('items'), cond: Expression::gte(Expression::variable('item.price'), 100), diff --git a/tests/Builder/Projection/Pipelines.php b/tests/Builder/Projection/Pipelines.php deleted file mode 100644 index 251d9e69a..000000000 --- a/tests/Builder/Projection/Pipelines.php +++ /dev/null @@ -1,13 +0,0 @@ - Date: Wed, 31 Jan 2024 17:06:47 +0100 Subject: [PATCH 54/95] PHPLIB-1374 Add tests on all stages (#56) --- .../config/accumulator/covariancePop.yaml | 1 + .../config/accumulator/covarianceSamp.yaml | 1 + generator/config/accumulator/push.yaml | 2 + generator/config/accumulator/sum.yaml | 2 + .../config/expression/arrayToObject.yaml | 2 +- generator/config/expression/avg.yaml | 1 + generator/config/expression/dateAdd.yaml | 1 + .../config/expression/dateFromString.yaml | 1 - generator/config/expression/dateSubtract.yaml | 2 + generator/config/expression/dayOfMonth.yaml | 1 + generator/config/expression/dayOfWeek.yaml | 1 + generator/config/expression/dayOfYear.yaml | 1 + generator/config/expression/getField.yaml | 4 +- generator/config/expression/hour.yaml | 1 + generator/config/expression/isArray.yaml | 3 +- generator/config/expression/isoDayOfWeek.yaml | 1 + generator/config/expression/isoWeek.yaml | 1 + generator/config/expression/isoWeekYear.yaml | 1 + generator/config/expression/map.yaml | 2 +- generator/config/expression/max.yaml | 1 + generator/config/expression/millisecond.yaml | 1 + generator/config/expression/min.yaml | 1 + generator/config/expression/minute.yaml | 1 + generator/config/expression/month.yaml | 1 + .../config/expression/objectToArray.yaml | 3 +- generator/config/expression/pow.yaml | 2 +- generator/config/expression/rand.yaml | 1 + generator/config/expression/second.yaml | 1 + generator/config/expression/setField.yaml | 2 + generator/config/expression/size.yaml | 3 +- generator/config/expression/sortArray.yaml | 3 +- generator/config/expression/stdDevPop.yaml | 1 + generator/config/expression/sum.yaml | 2 + generator/config/expression/unsetField.yaml | 1 + generator/config/expression/week.yaml | 1 + generator/config/expression/year.yaml | 1 + generator/config/query/type.yaml | 16 +- generator/config/stage/addFields.yaml | 27 +- generator/config/stage/bucket.yaml | 59 + generator/config/stage/bucketAuto.yaml | 9 + generator/config/stage/changeStream.yaml | 7 + .../stage/changeStreamSplitLargeEvent.yaml | 7 + generator/config/stage/collStats.yaml | 50 +- generator/config/stage/count.yaml | 11 + generator/config/stage/currentOp.yaml | 51 + generator/config/stage/densify.yaml | 26 + generator/config/stage/documents.yaml | 34 + generator/config/stage/facet.yaml | 37 + generator/config/stage/fill.yaml | 68 + generator/config/stage/geoNear.yaml | 84 + generator/config/stage/graphLookup.yaml | 46 + generator/config/stage/group.yaml | 100 + generator/config/stage/indexStats.yaml | 7 + generator/config/stage/limit.yaml | 7 + generator/config/stage/listLocalSessions.yaml | 24 + .../config/stage/listSampledQueries.yaml | 15 + generator/config/stage/listSearchIndexes.yaml | 21 + generator/config/stage/listSessions.yaml | 25 + generator/config/stage/lookup.yaml | 111 + generator/config/stage/match.yaml | 27 + generator/config/stage/merge.yaml | 136 +- generator/config/stage/out.yaml | 42 +- generator/config/stage/planCacheStats.yaml | 16 + generator/config/stage/project.yaml | 110 + generator/config/stage/redact.yaml | 39 + generator/config/stage/replaceRoot.yaml | 58 + generator/config/stage/replaceWith.yaml | 60 + generator/config/stage/sample.yaml | 8 + generator/config/stage/set.yaml | 58 + generator/config/stage/setWindowFields.yaml | 114 + .../config/stage/shardedDataDistribution.yaml | 7 + generator/config/stage/skip.yaml | 7 + generator/config/stage/sort.yaml | 22 + generator/config/stage/sortByCount.yaml | 14 +- generator/config/stage/unionWith.yaml | 59 + generator/config/stage/unset.yaml | 27 + generator/config/stage/unwind.yaml | 64 + generator/js2yaml.html | 4 + generator/src/OperatorTestGenerator.php | 4 +- src/Builder/Expression/FactoryTrait.php | 6 +- src/Builder/Expression/SumOperator.php | 11 +- src/Builder/Stage/CollStatsStage.php | 34 +- src/Builder/Stage/CurrentOpStage.php | 37 +- src/Builder/Stage/FactoryTrait.php | 53 +- src/Builder/Stage/GeoNearStage.php | 9 +- src/Builder/Stage/GroupStage.php | 4 - src/Builder/Stage/MergeStage.php | 21 +- src/Builder/Stage/OutStage.php | 26 +- src/Builder/Stage/SortByCountStage.php | 2 +- tests/Builder/Expression/Pipelines.php | 2 +- .../Expression/SortArrayOperatorTest.php | 3 +- tests/Builder/Stage/AddFieldsStageTest.php | 30 +- tests/Builder/Stage/BucketAutoStageTest.php | 28 + tests/Builder/Stage/BucketStageTest.php | 94 + .../ChangeStreamSplitLargeEventStageTest.php | 24 + tests/Builder/Stage/ChangeStreamStageTest.php | 24 + tests/Builder/Stage/CollStatsStageTest.php | 63 + tests/Builder/Stage/CountStageTest.php | 28 + tests/Builder/Stage/CurrentOpStageTest.php | 47 + tests/Builder/Stage/DensifyStageTest.php | 54 + tests/Builder/Stage/DocumentsStageTest.php | 56 + tests/Builder/Stage/FacetStageTest.php | 62 + tests/Builder/Stage/FillStageTest.php | 110 + tests/Builder/Stage/GeoNearStageTest.php | 123 + tests/Builder/Stage/GraphLookupStageTest.php | 77 + tests/Builder/Stage/GroupStageTest.php | 151 + tests/Builder/Stage/IndexStatsStageTest.php | 24 + tests/Builder/Stage/LimitStageTest.php | 24 + .../Stage/ListLocalSessionsStageTest.php | 50 + .../Stage/ListSampledQueriesStageTest.php | 35 + .../Stage/ListSearchIndexesStageTest.php | 46 + tests/Builder/Stage/ListSessionsStageTest.php | 50 + tests/Builder/Stage/LookupStageTest.php | 161 + tests/Builder/Stage/MatchStageTest.php | 53 + tests/Builder/Stage/MergeStageTest.php | 188 + tests/Builder/Stage/OutStageTest.php | 54 + tests/Builder/Stage/Pipelines.php | 3227 ++++++++++++++++- .../Builder/Stage/PlanCacheStatsStageTest.php | 36 + tests/Builder/Stage/ProjectStageTest.php | 164 + tests/Builder/Stage/RedactStageTest.php | 63 + tests/Builder/Stage/ReplaceRootStageTest.php | 77 + tests/Builder/Stage/ReplaceWithStageTest.php | 83 + tests/Builder/Stage/SampleStageTest.php | 24 + tests/Builder/Stage/SetStageTest.php | 89 + .../Stage/SetWindowFieldsStageTest.php | 173 + .../ShardedDataDistributionStageTest.php | 24 + tests/Builder/Stage/SkipStageTest.php | 24 + tests/Builder/Stage/SortByCountStageTest.php | 30 + tests/Builder/Stage/SortStageTest.php | 49 + tests/Builder/Stage/UnionWithStageTest.php | 83 + tests/Builder/Stage/UnsetStageTest.php | 49 + tests/Builder/Stage/UnwindStageTest.php | 94 + 132 files changed, 7804 insertions(+), 122 deletions(-) create mode 100644 tests/Builder/Stage/BucketAutoStageTest.php create mode 100644 tests/Builder/Stage/BucketStageTest.php create mode 100644 tests/Builder/Stage/ChangeStreamSplitLargeEventStageTest.php create mode 100644 tests/Builder/Stage/ChangeStreamStageTest.php create mode 100644 tests/Builder/Stage/CollStatsStageTest.php create mode 100644 tests/Builder/Stage/CountStageTest.php create mode 100644 tests/Builder/Stage/CurrentOpStageTest.php create mode 100644 tests/Builder/Stage/DensifyStageTest.php create mode 100644 tests/Builder/Stage/DocumentsStageTest.php create mode 100644 tests/Builder/Stage/FacetStageTest.php create mode 100644 tests/Builder/Stage/FillStageTest.php create mode 100644 tests/Builder/Stage/GeoNearStageTest.php create mode 100644 tests/Builder/Stage/GraphLookupStageTest.php create mode 100644 tests/Builder/Stage/GroupStageTest.php create mode 100644 tests/Builder/Stage/IndexStatsStageTest.php create mode 100644 tests/Builder/Stage/LimitStageTest.php create mode 100644 tests/Builder/Stage/ListLocalSessionsStageTest.php create mode 100644 tests/Builder/Stage/ListSampledQueriesStageTest.php create mode 100644 tests/Builder/Stage/ListSearchIndexesStageTest.php create mode 100644 tests/Builder/Stage/ListSessionsStageTest.php create mode 100644 tests/Builder/Stage/LookupStageTest.php create mode 100644 tests/Builder/Stage/MatchStageTest.php create mode 100644 tests/Builder/Stage/MergeStageTest.php create mode 100644 tests/Builder/Stage/OutStageTest.php create mode 100644 tests/Builder/Stage/PlanCacheStatsStageTest.php create mode 100644 tests/Builder/Stage/ProjectStageTest.php create mode 100644 tests/Builder/Stage/RedactStageTest.php create mode 100644 tests/Builder/Stage/ReplaceRootStageTest.php create mode 100644 tests/Builder/Stage/ReplaceWithStageTest.php create mode 100644 tests/Builder/Stage/SampleStageTest.php create mode 100644 tests/Builder/Stage/SetStageTest.php create mode 100644 tests/Builder/Stage/SetWindowFieldsStageTest.php create mode 100644 tests/Builder/Stage/ShardedDataDistributionStageTest.php create mode 100644 tests/Builder/Stage/SkipStageTest.php create mode 100644 tests/Builder/Stage/SortByCountStageTest.php create mode 100644 tests/Builder/Stage/SortStageTest.php create mode 100644 tests/Builder/Stage/UnionWithStageTest.php create mode 100644 tests/Builder/Stage/UnsetStageTest.php create mode 100644 tests/Builder/Stage/UnwindStageTest.php diff --git a/generator/config/accumulator/covariancePop.yaml b/generator/config/accumulator/covariancePop.yaml index 5b41e6a3d..b43a24022 100644 --- a/generator/config/accumulator/covariancePop.yaml +++ b/generator/config/accumulator/covariancePop.yaml @@ -30,6 +30,7 @@ tests: covariancePopForState: $covariancePop: - + # Example uses the short form, the builder always generates the verbose form # $year: '$orderDate' $year: date: '$orderDate' diff --git a/generator/config/accumulator/covarianceSamp.yaml b/generator/config/accumulator/covarianceSamp.yaml index 0c3bb8637..b6cc529af 100644 --- a/generator/config/accumulator/covarianceSamp.yaml +++ b/generator/config/accumulator/covarianceSamp.yaml @@ -30,6 +30,7 @@ tests: covarianceSampForState: $covarianceSamp: - + # Example uses the short form, the builder always generates the verbose form # $year: '$orderDate' $year: date: '$orderDate' diff --git a/generator/config/accumulator/push.yaml b/generator/config/accumulator/push.yaml index 1e705ea1a..3fc367c59 100644 --- a/generator/config/accumulator/push.yaml +++ b/generator/config/accumulator/push.yaml @@ -26,10 +26,12 @@ tests: $group: _id: day: + # Example uses the short form, the builder always generates the verbose form # $dayOfYear: '$date' $dayOfYear: date: '$date' year: + # Example uses the short form, the builder always generates the verbose form # $year: '$date' $year: date: '$date' diff --git a/generator/config/accumulator/sum.yaml b/generator/config/accumulator/sum.yaml index fb4a03ef3..c40417ef4 100644 --- a/generator/config/accumulator/sum.yaml +++ b/generator/config/accumulator/sum.yaml @@ -22,10 +22,12 @@ tests: $group: _id: day: + # Example uses the short form, the builder always generates the verbose form # $dayOfYear: '$date' $dayOfYear: date: '$date' year: + # Example uses the short form, the builder always generates the verbose form # $year: '$date' $year: date: '$date' diff --git a/generator/config/expression/arrayToObject.yaml b/generator/config/expression/arrayToObject.yaml index 76baeb7b0..87026f7a7 100644 --- a/generator/config/expression/arrayToObject.yaml +++ b/generator/config/expression/arrayToObject.yaml @@ -20,7 +20,7 @@ tests: $project: item: 1 dimensions: - # The example renders a single value, but the builder generates an array for consistency + # Example uses the short form, the builder always generates the verbose form # $arrayToObject: '$dimensions' $arrayToObject: - '$dimensions' diff --git a/generator/config/expression/avg.yaml b/generator/config/expression/avg.yaml index c647e083a..3bb771936 100644 --- a/generator/config/expression/avg.yaml +++ b/generator/config/expression/avg.yaml @@ -21,6 +21,7 @@ tests: - $project: quizAvg: + # Example uses the short form, the builder always generates the verbose form # $avg: '$quizzes' $avg: - '$quizzes' diff --git a/generator/config/expression/dateAdd.yaml b/generator/config/expression/dateAdd.yaml index d908f69f9..19ef3ce51 100644 --- a/generator/config/expression/dateAdd.yaml +++ b/generator/config/expression/dateAdd.yaml @@ -46,6 +46,7 @@ tests: unit: 'day' amount: 3 - + # Example uses the short form, the builder always generates the verbose form # $merge: 'shipping' $merge: into: 'shipping' diff --git a/generator/config/expression/dateFromString.yaml b/generator/config/expression/dateFromString.yaml index 5499d7149..713200f5d 100644 --- a/generator/config/expression/dateFromString.yaml +++ b/generator/config/expression/dateFromString.yaml @@ -76,6 +76,5 @@ tests: $dateFromString: dateString: '$date' timezone: '$timezone' - # onNull: new Date(0) onNull: !bson_utcdatetime 0 diff --git a/generator/config/expression/dateSubtract.yaml b/generator/config/expression/dateSubtract.yaml index b2b33bede..1fd3cd144 100644 --- a/generator/config/expression/dateSubtract.yaml +++ b/generator/config/expression/dateSubtract.yaml @@ -43,6 +43,7 @@ tests: $expr: $eq: - + # Example uses the short form, the builder always generates the verbose form # $month: '$logout' $month: date: '$logout' @@ -55,6 +56,7 @@ tests: unit: 'hour' amount: 3 - + # Example uses the short form, the builder always generates the verbose form # $merge: 'connectionTime' $merge: into: 'connectionTime' diff --git a/generator/config/expression/dayOfMonth.yaml b/generator/config/expression/dayOfMonth.yaml index 032c61c98..46a4de0b7 100644 --- a/generator/config/expression/dayOfMonth.yaml +++ b/generator/config/expression/dayOfMonth.yaml @@ -30,6 +30,7 @@ tests: - $project: day: + # Example uses the short form, the builder always generates the verbose form # $dayOfMonth: '$date' $dayOfMonth: date: '$date' diff --git a/generator/config/expression/dayOfWeek.yaml b/generator/config/expression/dayOfWeek.yaml index daa452d75..27a6a809d 100644 --- a/generator/config/expression/dayOfWeek.yaml +++ b/generator/config/expression/dayOfWeek.yaml @@ -30,6 +30,7 @@ tests: - $project: dayOfWeek: + # Example uses the short form, the builder always generates the verbose form # $dayOfWeek: '$date' $dayOfWeek: date: '$date' diff --git a/generator/config/expression/dayOfYear.yaml b/generator/config/expression/dayOfYear.yaml index 978291db9..8caa0374d 100644 --- a/generator/config/expression/dayOfYear.yaml +++ b/generator/config/expression/dayOfYear.yaml @@ -30,6 +30,7 @@ tests: - $project: dayOfYear: + # Example uses the short form, the builder always generates the verbose form # $dayOfYear: '$date' $dayOfYear: date: '$date' diff --git a/generator/config/expression/getField.yaml b/generator/config/expression/getField.yaml index 2a032478e..04b5d4ace 100644 --- a/generator/config/expression/getField.yaml +++ b/generator/config/expression/getField.yaml @@ -33,7 +33,7 @@ tests: $expr: $gt: - - # the builder uses the verbose form with parameter names + # Example uses the short form, the builder always generates the verbose form # $getField: 'price.usd' $getField: field: 'price.usd' @@ -48,7 +48,7 @@ tests: $gt: - $getField: - # the builder uses the verbose form with parameter names + # Example uses the short form, the builder always generates the verbose form # $literal: '$price' field: $literal: '$price' diff --git a/generator/config/expression/hour.yaml b/generator/config/expression/hour.yaml index abc6db305..ebd62c3a0 100644 --- a/generator/config/expression/hour.yaml +++ b/generator/config/expression/hour.yaml @@ -30,6 +30,7 @@ tests: - $project: hour: + # Example uses the short form, the builder always generates the verbose form # $hour: '$date' $hour: date: '$date' diff --git a/generator/config/expression/isArray.yaml b/generator/config/expression/isArray.yaml index 9379d3ca8..b9a5d5cb5 100644 --- a/generator/config/expression/isArray.yaml +++ b/generator/config/expression/isArray.yaml @@ -22,8 +22,7 @@ tests: $cond: if: $and: - # The example in the docs uses the short syntax for $isArray, - # but the aggregation builder always uses the more verbose syntax. + # Example uses the short form, the builder always generates the verbose form - $isArray: - '$instock' diff --git a/generator/config/expression/isoDayOfWeek.yaml b/generator/config/expression/isoDayOfWeek.yaml index 4511a6fa4..1956ff5c4 100644 --- a/generator/config/expression/isoDayOfWeek.yaml +++ b/generator/config/expression/isoDayOfWeek.yaml @@ -32,6 +32,7 @@ tests: _id: 0 name: '$name' dayOfWeek: + # Example uses the short form, the builder always generates the verbose form # $isoDayOfWeek: '$birthday' $isoDayOfWeek: date: '$birthday' diff --git a/generator/config/expression/isoWeek.yaml b/generator/config/expression/isoWeek.yaml index 07598de58..2958a20c3 100644 --- a/generator/config/expression/isoWeek.yaml +++ b/generator/config/expression/isoWeek.yaml @@ -32,6 +32,7 @@ tests: _id: 0 city: '$city' weekNumber: + # Example uses the short form, the builder always generates the verbose form # $isoWeek: '$date' $isoWeek: date: '$date' diff --git a/generator/config/expression/isoWeekYear.yaml b/generator/config/expression/isoWeekYear.yaml index af5457ba7..5dcefa7dd 100644 --- a/generator/config/expression/isoWeekYear.yaml +++ b/generator/config/expression/isoWeekYear.yaml @@ -30,6 +30,7 @@ tests: - $project: yearNumber: + # Example uses the short form, the builder always generates the verbose form # $isoWeekYear: '$date' $isoWeekYear: date: '$date' diff --git a/generator/config/expression/map.yaml b/generator/config/expression/map.yaml index 1019a2cec..11db40c24 100644 --- a/generator/config/expression/map.yaml +++ b/generator/config/expression/map.yaml @@ -53,7 +53,7 @@ tests: input: '$distances' as: 'decimalValue' in: - # The example renders a single value, but the builder generates an array for consistency + # Example uses the short form, the builder always generates the verbose form # $trunc: '$$decimalValue' $trunc: - '$$decimalValue' diff --git a/generator/config/expression/max.yaml b/generator/config/expression/max.yaml index 4f6588c96..b413679c5 100644 --- a/generator/config/expression/max.yaml +++ b/generator/config/expression/max.yaml @@ -21,6 +21,7 @@ tests: - $project: quizMax: + # Example uses the short form, the builder always generates the verbose form # $max: '$quizzes' $max: - '$quizzes' diff --git a/generator/config/expression/millisecond.yaml b/generator/config/expression/millisecond.yaml index 48b2bfe3b..af1d26e75 100644 --- a/generator/config/expression/millisecond.yaml +++ b/generator/config/expression/millisecond.yaml @@ -30,6 +30,7 @@ tests: - $project: milliseconds: + # Example uses the short form, the builder always generates the verbose form # $millisecond: '$date' $millisecond: date: '$date' diff --git a/generator/config/expression/min.yaml b/generator/config/expression/min.yaml index f7d3df725..1212fd4f7 100644 --- a/generator/config/expression/min.yaml +++ b/generator/config/expression/min.yaml @@ -21,6 +21,7 @@ tests: - $project: quizMin: + # Example uses the short form, the builder always generates the verbose form # $min: '$quizzes' $min: - '$quizzes' diff --git a/generator/config/expression/minute.yaml b/generator/config/expression/minute.yaml index 4a896618c..109e87f6b 100644 --- a/generator/config/expression/minute.yaml +++ b/generator/config/expression/minute.yaml @@ -30,6 +30,7 @@ tests: - $project: minutes: + # Example uses the short form, the builder always generates the verbose form # $minute: '$date' $minute: date: '$date' diff --git a/generator/config/expression/month.yaml b/generator/config/expression/month.yaml index 6187a78f7..7bd383be9 100644 --- a/generator/config/expression/month.yaml +++ b/generator/config/expression/month.yaml @@ -30,6 +30,7 @@ tests: - $project: month: + # Example uses the short form, the builder always generates the verbose form # $month: '$date' $month: date: '$date' diff --git a/generator/config/expression/objectToArray.yaml b/generator/config/expression/objectToArray.yaml index bcad8ca11..460977f33 100644 --- a/generator/config/expression/objectToArray.yaml +++ b/generator/config/expression/objectToArray.yaml @@ -33,8 +33,7 @@ tests: warehouses: $objectToArray: '$instock' - - # The example in the docs uses the short syntax for $unwind, - # but the aggregation builder always uses the more verbose syntax. + # Example uses the short form, the builder always generates the verbose form # $unwind: '$warehouses' $unwind: path: '$warehouses' diff --git a/generator/config/expression/pow.yaml b/generator/config/expression/pow.yaml index 71d3be2b3..afab7f875 100644 --- a/generator/config/expression/pow.yaml +++ b/generator/config/expression/pow.yaml @@ -25,7 +25,7 @@ tests: variance: $pow: - - # The builder renders $stdDevPop with the array form, even with a single value + # Example uses the short form, the builder always generates the verbose form # $stdDevPop: '$scores.score' $stdDevPop: ['$scores.score'] - 2 diff --git a/generator/config/expression/rand.yaml b/generator/config/expression/rand.yaml index b1a9d8157..a19f3163e 100644 --- a/generator/config/expression/rand.yaml +++ b/generator/config/expression/rand.yaml @@ -23,6 +23,7 @@ tests: amount: $floor: '$amount' - + # Example uses the short form, the builder always generates the verbose form # $merge: 'donors' $merge: into: 'donors' diff --git a/generator/config/expression/second.yaml b/generator/config/expression/second.yaml index 48ceba31d..83e7fd370 100644 --- a/generator/config/expression/second.yaml +++ b/generator/config/expression/second.yaml @@ -30,6 +30,7 @@ tests: - $project: seconds: + # Example uses the short form, the builder always generates the verbose form # $second: '$date' $second: date: '$date' diff --git a/generator/config/expression/setField.yaml b/generator/config/expression/setField.yaml index 8ff3dbf7c..b53cf6584 100644 --- a/generator/config/expression/setField.yaml +++ b/generator/config/expression/setField.yaml @@ -39,6 +39,7 @@ tests: input: '$$ROOT' value: '$price' - + # Example uses the short form, the builder always generates the verbose form # $unset: 'price' $unset: - 'price' @@ -54,6 +55,7 @@ tests: input: '$$ROOT' value: '$price' - + # Example uses the short form, the builder always generates the verbose form # $unset: 'price' $unset: - 'price' diff --git a/generator/config/expression/size.yaml b/generator/config/expression/size.yaml index 40944af00..ce4fe775e 100644 --- a/generator/config/expression/size.yaml +++ b/generator/config/expression/size.yaml @@ -24,8 +24,7 @@ tests: numberOfColors: $cond: if: - # The example in the docs uses the short syntax for $isArray, - # but the aggregation builder always uses the more verbose syntax. + # Example uses the short form, the builder always generates the verbose form # $isArray: '$colors' $isArray: - '$colors' diff --git a/generator/config/expression/sortArray.yaml b/generator/config/expression/sortArray.yaml index ee320c77e..febd73807 100644 --- a/generator/config/expression/sortArray.yaml +++ b/generator/config/expression/sortArray.yaml @@ -101,8 +101,7 @@ tests: a: sale: true price: 19 - # Decimal128( "10.23" ) - - 10.23 + - !bson_decimal128 '10.23' - a: 'On sale' sortBy: 1 diff --git a/generator/config/expression/stdDevPop.yaml b/generator/config/expression/stdDevPop.yaml index f5d4d91a6..46641ebe8 100644 --- a/generator/config/expression/stdDevPop.yaml +++ b/generator/config/expression/stdDevPop.yaml @@ -22,5 +22,6 @@ tests: - $project: stdDev: + # Example uses the short form, the builder always generates the verbose form # $stdDevPop: '$scores.score' $stdDevPop: ['$scores.score'] diff --git a/generator/config/expression/sum.yaml b/generator/config/expression/sum.yaml index 430f82c4b..25b323ab1 100644 --- a/generator/config/expression/sum.yaml +++ b/generator/config/expression/sum.yaml @@ -12,6 +12,7 @@ arguments: name: expression type: - resolvesToNumber + - resolvesToArray variadic: array tests: - @@ -21,6 +22,7 @@ tests: - $project: quizTotal: + # Example uses the short form, the builder always generates the verbose form # $sum: '$quizzes' $sum: - '$quizzes' diff --git a/generator/config/expression/unsetField.yaml b/generator/config/expression/unsetField.yaml index 98d121687..a4365a646 100644 --- a/generator/config/expression/unsetField.yaml +++ b/generator/config/expression/unsetField.yaml @@ -53,6 +53,7 @@ tests: $unsetField: field: 'euro' input: + # Example uses the short form, the builder always generates the verbose form # $getField: 'price' $getField: field: 'price' diff --git a/generator/config/expression/week.yaml b/generator/config/expression/week.yaml index 839bdae20..6086f57ee 100644 --- a/generator/config/expression/week.yaml +++ b/generator/config/expression/week.yaml @@ -30,6 +30,7 @@ tests: - $project: week: + # Example uses the short form, the builder always generates the verbose form # $week: '$date' $week: date: '$date' diff --git a/generator/config/expression/year.yaml b/generator/config/expression/year.yaml index ee1a689b6..3326e3495 100644 --- a/generator/config/expression/year.yaml +++ b/generator/config/expression/year.yaml @@ -30,6 +30,7 @@ tests: - $project: year: + # Example uses the short form, the builder always generates the verbose form # $year: '$date' $year: date: '$date' diff --git a/generator/config/query/type.yaml b/generator/config/query/type.yaml index bedec7719..d8cd7bc86 100644 --- a/generator/config/query/type.yaml +++ b/generator/config/query/type.yaml @@ -21,31 +21,31 @@ tests: - $match: zipCode: - # Example uses the short form, the builder always generated the verbose form + # Example uses the short form, the builder always generates the verbose form # $type: 2 $type: [2] - $match: zipCode: - # Example uses the short form, the builder always generated the verbose form + # Example uses the short form, the builder always generates the verbose form # $type: 'string' $type: ['string'] - $match: zipCode: - # Example uses the short form, the builder always generated the verbose form + # Example uses the short form, the builder always generates the verbose form # $type: 1 $type: [1] - $match: zipCode: - # Example uses the short form, the builder always generated the verbose form + # Example uses the short form, the builder always generates the verbose form # $type: 'double' $type: ['double'] - $match: zipCode: - # Example uses the short form, the builder always generated the verbose form + # Example uses the short form, the builder always generates the verbose form # $type: 'number' $type: ['number'] - @@ -67,13 +67,13 @@ tests: - $match: zipCode: - # Example uses the short form, the builder always generated the verbose form + # Example uses the short form, the builder always generates the verbose form # $type: 'minKey' $type: ['minKey'] - $match: zipCode: - # Example uses the short form, the builder always generated the verbose form + # Example uses the short form, the builder always generates the verbose form # $type: 'maxKey' $type: ['maxKey'] - @@ -83,6 +83,6 @@ tests: - $match: zipCode: - # Example uses the short form, the builder always generated the verbose form + # Example uses the short form, the builder always generates the verbose form # $type: 'array' $type: ['array'] diff --git a/generator/config/stage/addFields.yaml b/generator/config/stage/addFields.yaml index 9562cd646..e98f5de18 100644 --- a/generator/config/stage/addFields.yaml +++ b/generator/config/stage/addFields.yaml @@ -22,9 +22,12 @@ tests: - $addFields: totalHomework: - $sum: '$homework' + # The example renders a single value, but the builder generates an array for consistency + # $sum: '$homework' + $sum: ['$homework'] totalQuiz: - $sum: '$quiz' + # $sum: '$quiz' + $sum: ['$quiz'] - $addFields: totalScore: @@ -39,3 +42,23 @@ tests: - $addFields: specs.fuel_type: 'unleaded' + - + name: 'Overwriting an existing field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#overwriting-an-existing-field' + pipeline: + - + $addFields: + cats: 20 + - + name: 'Add Element to an Array' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#add-element-to-an-array' + pipeline: + - + $match: + _id: 1 + - + $addFields: + homework: + $concatArrays: + - '$homework' + - [7] diff --git a/generator/config/stage/bucket.yaml b/generator/config/stage/bucket.yaml index f0e5a9e6e..0cd65feac 100644 --- a/generator/config/stage/bucket.yaml +++ b/generator/config/stage/bucket.yaml @@ -40,3 +40,62 @@ arguments: A document that specifies the fields to include in the output documents in addition to the _id field. To specify the field to include, you must use accumulator expressions. If you do not specify an output document, the operation returns a count field containing the number of documents in each bucket. If you specify an output document, only the fields specified in the document are returned; i.e. the count field is not returned unless it is explicitly included in the output document. +tests: + - + name: 'Bucket by Year and Filter by Bucket Results' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucket/#bucket-by-year-and-filter-by-bucket-results' + pipeline: + - + $bucket: + groupBy: '$year_born' + boundaries: [1840, 1850, 1860, 1870, 1880] + default: 'Other' + output: + count: + $sum: 1 + artists: + $push: + name: + $concat: + - '$first_name' + - ' ' + - '$last_name' + year_born: '$year_born' + - + $match: + count: + $gt: 3 + - + name: 'Use $bucket with $facet to Bucket by Multiple Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucket/#use--bucket-with--facet-to-bucket-by-multiple-fields' + pipeline: + - + $facet: + price: + - + $bucket: + groupBy: '$price' + boundaries: [0, 200, 400] + default: 'Other' + output: + count: + $sum: 1 + artwork: + $push: + title: '$title' + price: '$price' + averagePrice: + $avg: '$price' + year: + - + $bucket: + groupBy: '$year' + boundaries: [1890, 1910, 1920, 1940] + default: 'Unknown' + output: + count: + $sum: 1 + artwork: + $push: + title: '$title' + year: '$year' diff --git a/generator/config/stage/bucketAuto.yaml b/generator/config/stage/bucketAuto.yaml index a1c5736cf..48c102e11 100644 --- a/generator/config/stage/bucketAuto.yaml +++ b/generator/config/stage/bucketAuto.yaml @@ -35,3 +35,12 @@ arguments: description: | A string that specifies the preferred number series to use to ensure that the calculated boundary edges end on preferred round numbers or their powers of 10. Available only if the all groupBy values are numeric and none of them are NaN. +tests: + - + name: 'Single Facet Aggregation' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucketAuto/#single-facet-aggregation' + pipeline: + - + $bucketAuto: + groupBy: '$price' + buckets: 4 diff --git a/generator/config/stage/changeStream.yaml b/generator/config/stage/changeStream.yaml index 6567af2b4..44fdb3c5c 100644 --- a/generator/config/stage/changeStream.yaml +++ b/generator/config/stage/changeStream.yaml @@ -57,3 +57,10 @@ arguments: optional: true description: | Specifies a time as the logical starting point for the change stream. Cannot be used with resumeAfter or startAfter fields. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/changeStream/#examples' + pipeline: + - + $changeStream: {} diff --git a/generator/config/stage/changeStreamSplitLargeEvent.yaml b/generator/config/stage/changeStreamSplitLargeEvent.yaml index 331c0dd2d..208129346 100644 --- a/generator/config/stage/changeStreamSplitLargeEvent.yaml +++ b/generator/config/stage/changeStreamSplitLargeEvent.yaml @@ -7,3 +7,10 @@ encode: object description: | Splits large change stream events that exceed 16 MB into smaller fragments returned in a change stream cursor. You can only use $changeStreamSplitLargeEvent in a $changeStream pipeline and it must be the final stage in the pipeline. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/changeStreamSplitLargeEvent/#example' + pipeline: + - + $changeStreamSplitLargeEvent: {} diff --git a/generator/config/stage/collStats.yaml b/generator/config/stage/collStats.yaml index 0e7bd8d04..26cbbc470 100644 --- a/generator/config/stage/collStats.yaml +++ b/generator/config/stage/collStats.yaml @@ -3,11 +3,57 @@ name: $collStats link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/' type: - stage -encode: single +encode: object description: | Returns statistics regarding a collection or view. arguments: - - name: config + name: latencyStats type: - object + optional: true + - + name: storageStats + type: + - object + optional: true + - + name: count + type: + - object # empty object + optional: true + - + name: queryExecStats + type: + - object # empty object + optional: true +tests: + - + name: 'latencyStats Document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/#latencystats-document' + pipeline: + - + $collStats: + latencyStats: + histograms: true + - + name: 'storageStats Document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/#storagestats-document' + pipeline: + - + $collStats: + storageStats: {} + - + name: 'count Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/#count-field' + pipeline: + - + $collStats: + count: {} + - + name: 'queryExecStats Document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/#queryexecstats-document' + pipeline: + - + $collStats: + queryExecStats: {} diff --git a/generator/config/stage/count.yaml b/generator/config/stage/count.yaml index 37ed506c4..a0fa3ba57 100644 --- a/generator/config/stage/count.yaml +++ b/generator/config/stage/count.yaml @@ -14,3 +14,14 @@ arguments: - string description: | Name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/count/#example' + pipeline: + - + $match: + score: + $gt: 80 + - + $count: 'passing_scores' diff --git a/generator/config/stage/currentOp.yaml b/generator/config/stage/currentOp.yaml index a07df0c48..024c71318 100644 --- a/generator/config/stage/currentOp.yaml +++ b/generator/config/stage/currentOp.yaml @@ -6,3 +6,54 @@ type: encode: object description: | Returns information on active and/or dormant operations for the MongoDB deployment. To run, use the db.aggregate() method. +arguments: + - + name: allUsers + type: + - bool + optional: true + - + name: idleConnections + type: + - bool + optional: true + - + name: idleCursors + type: + - bool + optional: true + - + name: idleSessions + type: + - bool + optional: true + - + name: localOps + type: + - bool + optional: true +tests: + - + name: 'Inactive Sessions' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/currentOp/#inactive-sessions' + pipeline: + - + $currentOp: + allUsers: true + idleSessions: true + - + $match: + active: false + transaction: + $exists: true + - + name: 'Sampled Queries' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/currentOp/#sampled-queries' + pipeline: + - + $currentOp: + allUsers: true + localOps: true + - + $match: + desc: 'query analyzer' diff --git a/generator/config/stage/densify.yaml b/generator/config/stage/densify.yaml index 84977f34d..a66cf18dd 100644 --- a/generator/config/stage/densify.yaml +++ b/generator/config/stage/densify.yaml @@ -28,3 +28,29 @@ arguments: - object # Range description: | Specification for range based densification. +tests: + - + name: 'Densify Time Series Data' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/densify/#densify-time-series-data' + pipeline: + - + $densify: + field: 'timestamp' + range: + step: 1 + unit: 'hour' + bounds: + - !bson_utcdatetime '2021-05-18T00:00:00.000Z' + - !bson_utcdatetime '2021-05-18T08:00:00.000Z' + - + name: 'Densifiction with Partitions' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/densify/#densifiction-with-partitions' + pipeline: + - + $densify: + field: 'altitude' + partitionByFields: + - 'variety' + range: + bounds: 'full' + step: 200 diff --git a/generator/config/stage/documents.yaml b/generator/config/stage/documents.yaml index acfdfaa9e..666468da8 100644 --- a/generator/config/stage/documents.yaml +++ b/generator/config/stage/documents.yaml @@ -17,3 +17,37 @@ arguments: - $let expressions - variables in scope from $lookup expressions Expressions that do not resolve to a current document, like $myField or $$ROOT, will result in an error. +tests: + - + name: 'Test a Pipeline Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/documents/#test-a-pipeline-stage' + pipeline: + - + $documents: + - { x: 10 } + - { x: 2 } + - { x: 5 } + - + $bucketAuto: + groupBy: '$x' + buckets: 4 + - + name: 'Use a $documents Stage in a $lookup Stage' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/documents/#use-a--documents-stage-in-a--lookup-stage' + pipeline: + - + $match: {} + - + $lookup: + localField: 'zip' + foreignField: 'zip_id' + as: 'city_state' + pipeline: + - + $documents: + - + zip_id: 94301 + name: 'Palo Alto, CA' + - + zip_id: 10019 + name: 'New York, NY' diff --git a/generator/config/stage/facet.yaml b/generator/config/stage/facet.yaml index f82922793..013163c2a 100644 --- a/generator/config/stage/facet.yaml +++ b/generator/config/stage/facet.yaml @@ -12,3 +12,40 @@ arguments: type: - pipeline variadic: object +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/facet/#example' + pipeline: + - + $facet: + categorizedByTags: + - + # The builder uses the verbose form of the $unwind operator + # $unwind: '$tags' + $unwind: + path: '$tags' + - + $sortByCount: '$tags' + categorizedByPrice: + - + $match: + price: + # The example uses an int, but the builder requires a bool + # $exists: 1 + $exists: true + - + $bucket: + groupBy: '$price' + boundaries: [0, 150, 200, 300, 400] + default: 'Other' + output: + count: + $sum: 1 + titles: + $push: '$title' + categorizedByYears(Auto): + - + $bucketAuto: + groupBy: '$year' + buckets: 4 diff --git a/generator/config/stage/fill.yaml b/generator/config/stage/fill.yaml index 5ad0ed993..d3a9ec390 100644 --- a/generator/config/stage/fill.yaml +++ b/generator/config/stage/fill.yaml @@ -40,3 +40,71 @@ arguments: description: | Specifies an object containing each field for which to fill missing values. You can specify multiple fields in the output object. The object name is the name of the field to fill. The object value specifies how the field is filled. +tests: + - + name: 'Fill Missing Field Values with a Constant Value' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/#fill-missing-field-values-with-a-constant-value' + pipeline: + - + $fill: + output: + bootsSold: + value: 0 + sandalsSold: + value: 0 + sneakersSold: + value: 0 + - + name: 'Fill Missing Field Values with Linear Interpolation' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/#fill-missing-field-values-with-linear-interpolation' + pipeline: + - + $fill: + sortBy: + time: 1 + output: + price: + method: 'linear' + - + name: 'Fill Missing Field Values Based on the Last Observed Value' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/#fill-missing-field-values-based-on-the-last-observed-value' + pipeline: + - + $fill: + sortBy: + date: 1 + output: + score: + method: 'locf' + - + name: 'Fill Data for Distinct Partitions' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/#fill-data-for-distinct-partitions' + pipeline: + - + $fill: + sortBy: + date: 1 + partitionBy: + restaurant: '$restaurant' + output: + score: + method: 'locf' + - + name: 'Indicate if a Field was Populated Using $fill' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/#indicate-if-a-field-was-populated-using--fill' + pipeline: + - + $set: + valueExisted: + $ifNull: + - + $toBool: + $toString: '$score' + - false + - + $fill: + sortBy: + date: 1 + output: + score: + method: 'locf' diff --git a/generator/config/stage/geoNear.yaml b/generator/config/stage/geoNear.yaml index 95d7070ca..c9968509d 100644 --- a/generator/config/stage/geoNear.yaml +++ b/generator/config/stage/geoNear.yaml @@ -54,6 +54,7 @@ arguments: name: near type: - object # GeoPoint + - resolvesToObject description: | The point for which to find the closest documents. - @@ -74,3 +75,86 @@ arguments: - When true, MongoDB uses $nearSphere semantics and calculates distances using spherical geometry. - When false, MongoDB uses $near semantics: spherical geometry for 2dsphere indexes and planar geometry for 2d indexes. Default: false. +tests: + - + name: 'Maximum Distance' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/#maximum-distance' + pipeline: + - + $geoNear: + near: + type: 'Point' + coordinates: + - -73.99279 + - 40.719296 + distanceField: 'dist.calculated' + maxDistance: 2 + query: + category: 'Parks' + includeLocs: 'dist.location' + spherical: true + - + name: 'Minimum Distance' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/#minimum-distance' + pipeline: + - + $geoNear: + near: + type: 'Point' + coordinates: + - -73.99279 + - 40.719296 + distanceField: 'dist.calculated' + minDistance: 2 + query: + category: 'Parks' + includeLocs: 'dist.location' + spherical: true + - + name: 'with the let option' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/#-geonear-with-the-let-option' + pipeline: + - + $geoNear: + near: '$$pt' + distanceField: 'distance' + maxDistance: 2 + query: + category: 'Parks' + includeLocs: 'dist.location' + spherical: true + - + name: 'with Bound let Option' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/#-geonear-with-bound-let-option' + pipeline: + - + $lookup: + from: 'places' + let: + pt: '$location' + pipeline: + - + $geoNear: + near: '$$pt' + distanceField: 'distance' + as: 'joinedField' + - + $match: + name: 'Sara D. Roosevelt Park' + - + name: 'Specify Which Geospatial Index to Use' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/#specify-which-geospatial-index-to-use' + pipeline: + - + $geoNear: + near: + type: 'Point' + coordinates: + - -73.98142 + - 40.71782 + key: 'location' + distanceField: 'dist.calculated' + query: + category: 'Parks' + - + $limit: 5 diff --git a/generator/config/stage/graphLookup.yaml b/generator/config/stage/graphLookup.yaml index 48d62e780..ae220620b 100644 --- a/generator/config/stage/graphLookup.yaml +++ b/generator/config/stage/graphLookup.yaml @@ -60,3 +60,49 @@ arguments: optional: true description: | A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. +tests: + - + name: 'Within a Single Collection' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/#within-a-single-collection' + pipeline: + - + $graphLookup: + from: 'employees' + startWith: '$reportsTo' + connectFromField: 'reportsTo' + connectToField: 'name' + as: 'reportingHierarchy' + - + name: 'Across Multiple Collections' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/#across-multiple-collections' + pipeline: + - + $graphLookup: + from: 'airports' + startWith: '$nearestAirport' + connectFromField: 'connects' + connectToField: 'airport' + maxDepth: 2 + depthField: 'numConnections' + as: 'destinations' + - + name: 'With a Query Filter' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/#with-a-query-filter' + pipeline: + - + $match: + name: 'Tanya Jordan' + - + $graphLookup: + from: 'people' + startWith: '$friends' + connectFromField: 'friends' + connectToField: 'name' + as: 'golfers' + restrictSearchWithMatch: + hobbies: 'golf' + - + $project: + name: 1 + friends: 1 + connections who play golf: '$golfers.name' diff --git a/generator/config/stage/group.yaml b/generator/config/stage/group.yaml index d5599661a..3e93588e9 100644 --- a/generator/config/stage/group.yaml +++ b/generator/config/stage/group.yaml @@ -18,5 +18,105 @@ arguments: type: - accumulator variadic: object + variadicMin: 0 description: | Computed using the accumulator operators. +tests: + - + name: 'Count the Number of Documents in a Collection' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#count-the-number-of-documents-in-a-collection' + pipeline: + - + $group: + _id: ~ + count: + $count: {} + - + name: 'Retrieve Distinct Values' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#retrieve-distinct-values' + pipeline: + - + $group: + _id: '$item' + - + name: 'Group by Item Having' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#group-by-item-having' + pipeline: + - + $group: + _id: '$item' + totalSaleAmount: + $sum: + $multiply: + - '$price' + - '$quantity' + - + $match: + totalSaleAmount: + $gte: 100 + - + name: 'Calculate Count Sum and Average' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#calculate-count--sum--and-average' + pipeline: + - + $match: + date: + $gte: !bson_utcdatetime '2014-01-01' + $lt: !bson_utcdatetime '2015-01-01' + - + $group: + _id: + $dateToString: + format: '%Y-%m-%d' + date: '$date' + totalSaleAmount: + $sum: + $multiply: + - '$price' + - '$quantity' + averageQuantity: + $avg: '$quantity' + count: + $sum: 1 + - + $sort: + totalSaleAmount: -1 + - + name: 'Group by null' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#group-by-null' + pipeline: + - + $group: + _id: ~ + totalSaleAmount: + $sum: + $multiply: + - '$price' + - '$quantity' + averageQuantity: + $avg: '$quantity' + count: + $sum: 1 + - + name: 'Pivot Data' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#pivot-data' + pipeline: + - + $group: + _id: '$author' + books: + $push: '$title' + - + name: 'Group Documents by author' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#group-documents-by-author' + pipeline: + - + $group: + _id: '$author' + books: + $push: '$$ROOT' + - + $addFields: + totalCopies: + # $sum: '$books.copies' + $sum: ['$books.copies'] diff --git a/generator/config/stage/indexStats.yaml b/generator/config/stage/indexStats.yaml index fa3ea2f34..178b209d8 100644 --- a/generator/config/stage/indexStats.yaml +++ b/generator/config/stage/indexStats.yaml @@ -6,3 +6,10 @@ type: encode: object description: | Returns statistics regarding the use of each index for the collection. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexStats/#example' + pipeline: + - + $indexStats: {} diff --git a/generator/config/stage/limit.yaml b/generator/config/stage/limit.yaml index bceb24d47..fff391a01 100644 --- a/generator/config/stage/limit.yaml +++ b/generator/config/stage/limit.yaml @@ -11,3 +11,10 @@ arguments: name: limit type: - int +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/limit/#example' + pipeline: + - + $limit: 5 diff --git a/generator/config/stage/listLocalSessions.yaml b/generator/config/stage/listLocalSessions.yaml index 314dff092..50dccc30e 100644 --- a/generator/config/stage/listLocalSessions.yaml +++ b/generator/config/stage/listLocalSessions.yaml @@ -21,3 +21,27 @@ arguments: optional: true description: | Returns all sessions for all users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster. +tests: + - + name: 'List All Local Sessions' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listLocalSessions/#list-all-local-sessions' + pipeline: + - + $listLocalSessions: + allUsers: true + - + name: 'List All Local Sessions for the Specified Users' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listLocalSessions/#list-all-local-sessions-for-the-specified-users' + pipeline: + - + $listLocalSessions: + users: + - + user: 'myAppReader' + db: 'test' + - + name: 'List All Local Sessions for the Current User' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listLocalSessions/#list-all-local-sessions-for-the-current-user' + pipeline: + - + $listLocalSessions: {} diff --git a/generator/config/stage/listSampledQueries.yaml b/generator/config/stage/listSampledQueries.yaml index c09e9c318..f767f0d04 100644 --- a/generator/config/stage/listSampledQueries.yaml +++ b/generator/config/stage/listSampledQueries.yaml @@ -12,3 +12,18 @@ arguments: type: - string optional: true +tests: + - + name: 'List Sampled Queries for All Collections' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSampledQueries/#list-sampled-queries-for-all-collections' + pipeline: + - + $listSampledQueries: {} + + - + name: 'List Sampled Queries for A Specific Collection' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSampledQueries/#list-sampled-queries-for-a-specific-collection' + pipeline: + - + $listSampledQueries: + namespace: 'social.post' diff --git a/generator/config/stage/listSearchIndexes.yaml b/generator/config/stage/listSearchIndexes.yaml index 5439b3247..afc4f6d05 100644 --- a/generator/config/stage/listSearchIndexes.yaml +++ b/generator/config/stage/listSearchIndexes.yaml @@ -21,3 +21,24 @@ arguments: optional: true description: | The name of the index to return information about. +tests: + - + name: 'Return All Search Indexes' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/#return-all-search-indexes' + pipeline: + - + $listSearchIndexes: {} + - + name: 'Return a Single Search Index by Name' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/#return-a-single-search-index-by-name' + pipeline: + - + $listSearchIndexes: + name: 'synonym-mappings' + - + name: 'Return a Single Search Index by id' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/#return-a-single-search-index-by-id' + pipeline: + - + $listSearchIndexes: + id: '6524096020da840844a4c4a7' diff --git a/generator/config/stage/listSessions.yaml b/generator/config/stage/listSessions.yaml index eaf3b47c0..efb56de05 100644 --- a/generator/config/stage/listSessions.yaml +++ b/generator/config/stage/listSessions.yaml @@ -21,3 +21,28 @@ arguments: optional: true description: | Returns all sessions for all users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster. +tests: + - + name: 'List All Sessions' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSessions/#list-all-sessions' + pipeline: + - + $listSessions: + allUsers: true + - + name: 'List All Sessions for the Specified Users' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSessions/#list-all-sessions-for-the-specified-users' + pipeline: + - + $listSessions: + users: + - + user: 'myAppReader' + db: 'test' + - + name: 'List All Sessions for the Current User' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSessions/#list-all-sessions-for-the-current-user' + pipeline: + - + $listSessions: {} + diff --git a/generator/config/stage/lookup.yaml b/generator/config/stage/lookup.yaml index c1e591d6d..b73770e47 100644 --- a/generator/config/stage/lookup.yaml +++ b/generator/config/stage/lookup.yaml @@ -52,3 +52,114 @@ arguments: - string description: | Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten. +tests: + - + name: 'Perform a Single Equality Join with $lookup' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#perform-a-single-equality-join-with--lookup' + pipeline: + - + $lookup: + from: 'inventory' + localField: 'item' + foreignField: 'sku' + as: 'inventory_docs' + - + name: 'Use $lookup with an Array' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#use--lookup-with-an-array' + pipeline: + - + $lookup: + from: 'members' + localField: 'enrollmentlist' + foreignField: 'name' + as: 'enrollee_info' + - + name: 'Use $lookup with $mergeObjects' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#use--lookup-with--mergeobjects' + pipeline: + - + $lookup: + from: 'items' + localField: 'item' + foreignField: 'item' + as: 'fromItems' + - + $replaceRoot: + newRoot: + $mergeObjects: + - + $arrayElemAt: + - '$fromItems' + - 0 + - '$$ROOT' + - + $project: + fromItems: 0 + - + name: 'Perform Multiple Joins and a Correlated Subquery with $lookup' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#perform-multiple-joins-and-a-correlated-subquery-with--lookup' + pipeline: + - + $lookup: + from: 'warehouses' + let: + order_item: '$item' + order_qty: '$ordered' + pipeline: + - + $match: + $expr: + $and: + - + $eq: + - '$stock_item' + - '$$order_item' + - + $gte: + - '$instock' + - '$$order_qty' + - + $project: + stock_item: 0 + _id: 0 + as: 'stockdata' + - + name: 'Perform an Uncorrelated Subquery with $lookup' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#perform-an-uncorrelated-subquery-with--lookup' + pipeline: + - + $lookup: + from: 'holidays' + pipeline: + - + $match: + year: 2018 + - + $project: + _id: 0 + date: + name: '$name' + date: '$date' + - + $replaceRoot: + newRoot: '$date' + as: 'holidays' + - + name: 'Perform a Concise Correlated Subquery with $lookup' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#perform-a-concise-correlated-subquery-with--lookup' + pipeline: + - + $lookup: + from: 'restaurants' + localField: 'restaurant_name' + foreignField: 'name' + let: + orders_drink: '$drink' + pipeline: + - + $match: + $expr: + $in: + - '$$orders_drink' + - '$beverages' + as: 'matches' diff --git a/generator/config/stage/match.yaml b/generator/config/stage/match.yaml index a5196e65c..ab0081fd0 100644 --- a/generator/config/stage/match.yaml +++ b/generator/config/stage/match.yaml @@ -11,3 +11,30 @@ arguments: name: query type: - query +tests: + - + name: 'Equality Match' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/#equality-match' + pipeline: + - + $match: + author: 'dave' + - + name: 'Perform a Count' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/#perform-a-count' + pipeline: + - + $match: + $or: + - + score: + $gt: 70 + $lt: 90 + - + views: + $gte: 1000 + - + $group: + _id: ~ + count: + $sum: 1 diff --git a/generator/config/stage/merge.yaml b/generator/config/stage/merge.yaml index 486f873de..2e24ec74e 100644 --- a/generator/config/stage/merge.yaml +++ b/generator/config/stage/merge.yaml @@ -12,7 +12,7 @@ arguments: name: into type: - string - #- OutCollection + - object # OutCollection description: | The output collection. - @@ -34,6 +34,7 @@ arguments: name: whenMatched type: - string # WhenMatched + - pipeline optional: true description: | The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). @@ -44,3 +45,136 @@ arguments: optional: true description: | The behavior of $merge if a result document does not match an existing document in the out collection. +tests: + - + name: 'On-Demand Materialized View Initial Creation' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#on-demand-materialized-view--initial-creation' + pipeline: + - + $group: + _id: + fiscal_year: '$fiscal_year' + dept: '$dept' + salaries: + $sum: '$salary' + - + $merge: + into: + db: 'reporting' + coll: 'budgets' + on: '_id' + whenMatched: 'replace' + whenNotMatched: 'insert' + - + name: 'On-Demand Materialized View Update Replace Data' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#on-demand-materialized-view--update-replace-data' + pipeline: + - + $match: + fiscal_year: + $gte: 2019 + - + $group: + _id: + fiscal_year: '$fiscal_year' + dept: '$dept' + salaries: + $sum: '$salary' + - + $merge: + into: + db: 'reporting' + coll: 'budgets' + on: '_id' + whenMatched: 'replace' + whenNotMatched: 'insert' + - + name: 'Only Insert New Data' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#only-insert-new-data' + pipeline: + - + $match: + fiscal_year: 2019 + - + $group: + _id: + fiscal_year: '$fiscal_year' + dept: '$dept' + employees: + $push: '$employee' + - + $project: + _id: 0 + dept: '$_id.dept' + fiscal_year: '$_id.fiscal_year' + employees: 1 + - + $merge: + into: + db: 'reporting' + coll: 'orgArchive' + on: + - 'dept' + - 'fiscal_year' + whenMatched: 'fail' + - + name: 'Merge Results from Multiple Collections' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#merge-results-from-multiple-collections' + pipeline: + - + $group: + _id: '$quarter' + purchased: + $sum: '$qty' + - + $merge: + into: 'quarterlyreport' + on: '_id' + whenMatched: 'merge' + whenNotMatched: 'insert' + - + name: 'Use the Pipeline to Customize the Merge' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#use-the-pipeline-to-customize-the-merge' + pipeline: + - + $match: + date: + $gte: !bson_utcdatetime '2019-05-07' + $lt: !bson_utcdatetime '2019-05-08' + - + $project: + _id: + $dateToString: + format: '%Y-%m' + date: '$date' + thumbsup: 1 + thumbsdown: 1 + - + $merge: + into: 'monthlytotals' + on: '_id' + whenMatched: + - + $addFields: + thumbsup: + $add: + - '$thumbsup' + - '$$new.thumbsup' + thumbsdown: + $add: + - '$thumbsdown' + - '$$new.thumbsdown' + whenNotMatched: 'insert' + - + name: 'Use Variables to Customize the Merge' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#use-variables-to-customize-the-merge' + pipeline: + - + $merge: + into: 'cakeSales' + let: + year: '2020' + whenMatched: + - + $addFields: + salesYear: '$$year' diff --git a/generator/config/stage/out.yaml b/generator/config/stage/out.yaml index 5675a4b65..c4cc7948d 100644 --- a/generator/config/stage/out.yaml +++ b/generator/config/stage/out.yaml @@ -3,26 +3,38 @@ name: $out link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/' type: - stage -encode: object +encode: single description: | Writes the resulting documents of the aggregation pipeline to a collection. To use the $out stage, it must be the last stage in the pipeline. arguments: - - - name: db - type: - - string - description: | - Target collection name to write documents from $out to. - - - name: coll + - name: coll type: - string + - object # OutCollection description: | Target database name to write documents from $out to. +tests: - - name: timeseries - type: - - object - optional: true - description: | - If set, the aggregation stage will use these options to create or replace a time-series collection in the given namespace. + name: 'Output to Same Database' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/#output-to-same-database' + pipeline: + - + $group: + _id: '$author' + books: + $push: '$title' + - + $out: 'authors' + - + name: 'Output to a Different Database' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/#output-to-a-different-database' + pipeline: + - + $group: + _id: '$author' + books: + $push: '$title' + - + $out: + db: 'reporting' + coll: 'authors' diff --git a/generator/config/stage/planCacheStats.yaml b/generator/config/stage/planCacheStats.yaml index b5edf8e58..995caa74e 100644 --- a/generator/config/stage/planCacheStats.yaml +++ b/generator/config/stage/planCacheStats.yaml @@ -6,3 +6,19 @@ type: encode: object description: | Returns plan cache information for a collection. +tests: + - + name: 'Return Information for All Entries in the Query Cache' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/planCacheStats/#return-information-for-all-entries-in-the-query-cache' + pipeline: + - + $planCacheStats: {} + - + name: 'Find Cache Entry Details for a Query Hash' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/planCacheStats/#find-cache-entry-details-for-a-query-hash' + pipeline: + - + $planCacheStats: {} + - + $match: + planCacheKey: 'B1435201' diff --git a/generator/config/stage/project.yaml b/generator/config/stage/project.yaml index 2337940b4..c7b0f7d59 100644 --- a/generator/config/stage/project.yaml +++ b/generator/config/stage/project.yaml @@ -12,3 +12,113 @@ arguments: type: - expression variadic: object +tests: + - + name: 'Include Specific Fields in Output Documents' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#include-specific-fields-in-output-documents' + pipeline: + - + $project: + title: 1 + author: 1 + - + name: 'Suppress id Field in the Output Documents' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#suppress-_id-field-in-the-output-documents' + pipeline: + - + $project: + _id: 0 + title: 1 + author: 1 + - + name: 'Exclude Fields from Output Documents' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#exclude-fields-from-output-documents' + pipeline: + - + $project: + lastModified: 0 + - + name: 'Exclude Fields from Embedded Documents' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#exclude-fields-from-embedded-documents' + pipeline: + - + $project: + author.first: 0 + lastModified: 0 + - + $project: + author: + first: 0 + lastModified: 0 + - + name: 'Conditionally Exclude Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#conditionally-exclude-fields' + pipeline: + - + $project: + title: 1 + author.first: 1 + author.last: 1 + author.middle: + $cond: + if: + $eq: + - '' + - '$author.middle' + then: '$$REMOVE' + else: '$author.middle' + - + name: 'Include Specific Fields from Embedded Documents' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#include-specific-fields-from-embedded-documents' + pipeline: + - + $project: + stop.title: 1 + - + $project: + stop: + title: 1 + - + name: 'Include Computed Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#include-computed-fields' + pipeline: + - + $project: + title: 1 + isbn: + prefix: + $substr: + - '$isbn' + - 0 + - 3 + group: + $substr: + - '$isbn' + - 3 + - 2 + publisher: + $substr: + - '$isbn' + - 5 + - 4 + title: + $substr: + - '$isbn' + - 9 + - 3 + checkDigit: + $substr: + - '$isbn' + - 12 + - 1 + lastName: '$author.last' + copiesSold: '$copies' + - + name: 'Project New Array Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#project-new-array-fields' + pipeline: + - + $project: + myArray: + - '$x' + - '$y' diff --git a/generator/config/stage/redact.yaml b/generator/config/stage/redact.yaml index 8d541ae2f..07698119c 100644 --- a/generator/config/stage/redact.yaml +++ b/generator/config/stage/redact.yaml @@ -11,3 +11,42 @@ arguments: name: expression type: - expression +tests: + - + name: 'Evaluate Access at Every Document Level' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/#evaluate-access-at-every-document-level' + pipeline: + - + $match: + year: 2014 + - + $redact: + $cond: + if: + $gt: + - + $size: + $setIntersection: + - '$tags' + - + - 'STLW' + - 'G' + - 0 + then: '$$DESCEND' + else: '$$PRUNE' + - + name: 'Exclude All Fields at a Given Level' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/#exclude-all-fields-at-a-given-level' + pipeline: + - + $match: + status: 'A' + - + $redact: + $cond: + if: + $eq: + - '$level' + - 5 + then: '$$PRUNE' + else: '$$DESCEND' diff --git a/generator/config/stage/replaceRoot.yaml b/generator/config/stage/replaceRoot.yaml index 5fa0036f1..4de474e00 100644 --- a/generator/config/stage/replaceRoot.yaml +++ b/generator/config/stage/replaceRoot.yaml @@ -11,3 +11,61 @@ arguments: name: newRoot type: - resolvesToObject +tests: + - + name: 'with an Embedded Document Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/#-replaceroot-with-an-embedded-document-field' + pipeline: + - + $replaceRoot: + newRoot: + $mergeObjects: + - + dogs: 0 + cats: 0 + birds: 0 + fish: 0 + - '$pets' + - + name: 'with a Document Nested in an Array' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/#-replaceroot-with-a-document-nested-in-an-array' + pipeline: + - + # The builder uses the verbose form of the $unwind operator + # $unwind: '$grades' + $unwind: + path: '$grades' + - + $match: + grades.grade: + $gte: 90 + - + $replaceRoot: + newRoot: '$grades' + - + name: 'with a newly created document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/#-replaceroot-with-a-newly-created-document' + pipeline: + - + $replaceRoot: + newRoot: + full_name: + $concat: + - '$first_name' + - ' ' + - '$last_name' + - + name: 'with a New Document Created from $$ROOT and a Default Document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/#-replaceroot-with-a-new-document-created-from---root-and-a-default-document' + pipeline: + - + $replaceRoot: + newRoot: + $mergeObjects: + - + _id: '' + name: '' + email: '' + cell: '' + home: '' + - '$$ROOT' diff --git a/generator/config/stage/replaceWith.yaml b/generator/config/stage/replaceWith.yaml index c87e43b83..10c5fa3a2 100644 --- a/generator/config/stage/replaceWith.yaml +++ b/generator/config/stage/replaceWith.yaml @@ -12,3 +12,63 @@ arguments: name: expression type: - resolvesToObject +tests: + - + name: 'an Embedded Document Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/#-replacewith-an-embedded-document-field' + pipeline: + - + $replaceWith: + $mergeObjects: + - + dogs: 0 + cats: 0 + birds: 0 + fish: 0 + - '$pets' + - + name: 'a Document Nested in an Array' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/#-replacewith-a-document-nested-in-an-array' + pipeline: + - + # The builder uses the verbose form of the $unwind operator + # $unwind: '$grades' + $unwind: + path: '$grades' + - + $match: + grades.grade: + $gte: 90 + - + $replaceWith: '$grades' + - + name: 'a Newly Created Document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/#-replacewith-a-newly-created-document' + pipeline: + - + $match: + status: 'C' + - + $replaceWith: + _id: '$_id' + item: '$item' + amount: + $multiply: + - '$price' + - '$quantity' + status: 'Complete' + asofDate: '$$NOW' + - + name: 'a New Document Created from $$ROOT and a Default Document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/#-replacewith-a-new-document-created-from---root-and-a-default-document' + pipeline: + - + $replaceWith: + $mergeObjects: + - + _id: '' + name: '' + email: '' + cell: '' + home: '' + - '$$ROOT' diff --git a/generator/config/stage/sample.yaml b/generator/config/stage/sample.yaml index c2b613eed..757382aaf 100644 --- a/generator/config/stage/sample.yaml +++ b/generator/config/stage/sample.yaml @@ -13,3 +13,11 @@ arguments: - int description: | The number of documents to randomly select. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/#example' + pipeline: + - + $sample: + size: 3 diff --git a/generator/config/stage/set.yaml b/generator/config/stage/set.yaml index 1955df4ff..a5861aa29 100644 --- a/generator/config/stage/set.yaml +++ b/generator/config/stage/set.yaml @@ -13,3 +13,61 @@ arguments: type: - expression variadic: object +tests: + - + name: 'Using Two $set Stages' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/#using-two--set-stages' + pipeline: + - + $set: + totalHomework: + # The $sum expression is always build as an array, even if the value is an array field name + # $sum: '$homework' + $sum: ['$homework'] + totalQuiz: + # $sum: '$quiz' + $sum: ['$quiz'] + - + $set: + totalScore: + $add: + - '$totalHomework' + - '$totalQuiz' + - '$extraCredit' + - + name: 'Adding Fields to an Embedded Document' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/#adding-fields-to-an-embedded-document' + pipeline: + - + $set: + specs.fuel_type: 'unleaded' + - + name: 'Overwriting an existing field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/#overwriting-an-existing-field' + pipeline: + - + $set: + cats: 20 + - + name: 'Add Element to an Array' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/#add-element-to-an-array' + pipeline: + - + $match: + _id: 1 + - + $set: + homework: + $concatArrays: + - '$homework' + - + - 7 + - + name: 'Creating a New Field with Existing Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/#creating-a-new-field-with-existing-fields' + pipeline: + - + $set: + quizAverage: + # $avg: '$quiz' + $avg: ['$quiz'] diff --git a/generator/config/stage/setWindowFields.yaml b/generator/config/stage/setWindowFields.yaml index 844968608..fba751d03 100644 --- a/generator/config/stage/setWindowFields.yaml +++ b/generator/config/stage/setWindowFields.yaml @@ -28,3 +28,117 @@ arguments: description: | Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. optional: true +tests: + - + name: 'Use Documents Window to Obtain Cumulative Quantity for Each State' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-documents-window-to-obtain-cumulative-quantity-for-each-state' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + cumulativeQuantityForState: + $sum: '$quantity' + window: + documents: ['unbounded', 'current'] + - + name: 'Use Documents Window to Obtain Cumulative Quantity for Each Year' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-documents-window-to-obtain-cumulative-quantity-for-each-year' + pipeline: + - + $setWindowFields: + partitionBy: + # $year: '$orderDate' + $year: + date: '$orderDate' + sortBy: + orderDate: 1 + output: + cumulativeQuantityForYear: + $sum: '$quantity' + window: + documents: ['unbounded', 'current'] + - + name: 'Use Documents Window to Obtain Moving Average Quantity for Each Year' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-documents-window-to-obtain-moving-average-quantity-for-each-year' + pipeline: + - + $setWindowFields: + partitionBy: + # $year: '$orderDate' + $year: + date: '$orderDate' + sortBy: + orderDate: 1 + output: + averageQuantity: + $avg: '$quantity' + window: + documents: [-1, 0] + - + name: 'Use Documents Window to Obtain Cumulative and Maximum Quantity for Each Year' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-documents-window-to-obtain-cumulative-and-maximum-quantity-for-each-year' + pipeline: + - + $setWindowFields: + partitionBy: + # $year: '$orderDate' + $year: + date: '$orderDate' + sortBy: + orderDate: 1 + output: + cumulativeQuantityForYear: + $sum: '$quantity' + window: + documents: ['unbounded', 'current'] + maximumQuantityForYear: + $max: '$quantity' + window: + documents: ['unbounded', 'unbounded'] + - + name: 'Range Window Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#range-window-example' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + price: 1 + output: + quantityFromSimilarOrders: + $sum: '$quantity' + window: + range: [-10, 10] + - + name: 'Use a Time Range Window with a Positive Upper Bound' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-a-time-range-window-with-a-positive-upper-bound' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + recentOrders: + $push: '$orderDate' + window: + range: ['unbounded', 10] + unit: 'month' + - + name: 'Use a Time Range Window with a Negative Upper Bound' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-a-time-range-window-with-a-negative-upper-bound' + pipeline: + - + $setWindowFields: + partitionBy: '$state' + sortBy: + orderDate: 1 + output: + recentOrders: + $push: '$orderDate' + window: + range: ['unbounded', -10] + unit: 'month' diff --git a/generator/config/stage/shardedDataDistribution.yaml b/generator/config/stage/shardedDataDistribution.yaml index 8cf84660e..2f298ca0f 100644 --- a/generator/config/stage/shardedDataDistribution.yaml +++ b/generator/config/stage/shardedDataDistribution.yaml @@ -7,3 +7,10 @@ encode: object description: | Provides data and size distribution information on sharded collections. New in MongoDB 6.0.3. +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/shardedDataDistribution/#examples' + pipeline: + - + $shardedDataDistribution: {} diff --git a/generator/config/stage/skip.yaml b/generator/config/stage/skip.yaml index 8b0118454..2128fe226 100644 --- a/generator/config/stage/skip.yaml +++ b/generator/config/stage/skip.yaml @@ -11,3 +11,10 @@ arguments: name: skip type: - int +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/skip/#example' + pipeline: + - + $skip: 5 diff --git a/generator/config/stage/sort.yaml b/generator/config/stage/sort.yaml index 9c26f0a2f..0cc7e02e0 100644 --- a/generator/config/stage/sort.yaml +++ b/generator/config/stage/sort.yaml @@ -11,3 +11,25 @@ arguments: name: sort type: - object # SortSpec +tests: + - + name: 'Ascending Descending Sort' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/#ascending-descending-sort' + pipeline: + - + $sort: + age: -1 + posts: 1 + - + name: 'Text Score Metadata Sort' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/#text-score-metadata-sort' + pipeline: + - + $match: + $text: + $search: 'operating' + - + $sort: + score: + $meta: 'textScore' + posts: -1 diff --git a/generator/config/stage/sortByCount.yaml b/generator/config/stage/sortByCount.yaml index c102323f6..a32d7aff4 100644 --- a/generator/config/stage/sortByCount.yaml +++ b/generator/config/stage/sortByCount.yaml @@ -3,7 +3,7 @@ name: $sortByCount link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortByCount/' type: - stage -encode: object +encode: single description: | Groups incoming documents based on the value of a specified expression, then computes the count of documents in each distinct group. arguments: @@ -11,3 +11,15 @@ arguments: name: expression type: - expression +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortByCount/#example' + pipeline: + - + # The builder uses the verbose form of the $unwind operator + # $unwind: '$tags' + $unwind: + path: '$tags' + - + $sortByCount: '$tags' diff --git a/generator/config/stage/unionWith.yaml b/generator/config/stage/unionWith.yaml index ebf8f903f..eafa44110 100644 --- a/generator/config/stage/unionWith.yaml +++ b/generator/config/stage/unionWith.yaml @@ -22,3 +22,62 @@ arguments: description: | An aggregation pipeline to apply to the specified coll. The pipeline cannot include the $out and $merge stages. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. +tests: + - + name: 'Report 1 All Sales by Year and Stores and Items' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unionWith/#report-1--all-sales-by-year-and-stores-and-items' + pipeline: + - + $set: + _id: '2017' + - + $unionWith: + coll: 'sales_2018' + pipeline: + - + $set: + _id: '2018' + - + $unionWith: + coll: 'sales_2019' + pipeline: + - + $set: + _id: '2019' + - + $unionWith: + coll: 'sales_2020' + pipeline: + - + $set: + _id: '2020' + - + $sort: + _id: 1 + store: 1 + item: 1 + - + name: 'Report 2 Aggregated Sales by Items' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unionWith/#report-2--aggregated-sales-by-items' + pipeline: + - + # Example uses the short form, the builder always generates the verbose form + # $unionWith: 'sales_2018' + $unionWith: + coll: 'sales_2018' + - + # $unionWith: 'sales_2019' + $unionWith: + coll: 'sales_2019' + - + # $unionWith: 'sales_2020' + $unionWith: + coll: 'sales_2020' + - + $group: + _id: '$item' + total: + $sum: '$quantity' + - + $sort: + total: -1 diff --git a/generator/config/stage/unset.yaml b/generator/config/stage/unset.yaml index 848eed3ca..cef9cdd6d 100644 --- a/generator/config/stage/unset.yaml +++ b/generator/config/stage/unset.yaml @@ -13,3 +13,30 @@ arguments: type: - fieldPath variadic: array +tests: + - + name: 'Remove a Single Field' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/#remove-a-single-field' + pipeline: + - + # The example in the docs uses the short syntax whereas + # the aggregation builder always uses the equivalent array syntax. + # $unset: 'copies' + $unset: ['copies'] + - + name: 'Remove Top-Level Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/#remove-top-level-fields' + pipeline: + - + $unset: + - 'isbn' + - 'copies' + - + name: 'Remove Embedded Fields' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/#remove-embedded-fields' + pipeline: + - + $unset: + - 'isbn' + - 'author.first' + - 'copies.warehouse' diff --git a/generator/config/stage/unwind.yaml b/generator/config/stage/unwind.yaml index 8fd255971..a1f93edbc 100644 --- a/generator/config/stage/unwind.yaml +++ b/generator/config/stage/unwind.yaml @@ -29,3 +29,67 @@ arguments: If true, if the path is null, missing, or an empty array, $unwind outputs the document. If false, if path is null, missing, or an empty array, $unwind does not output a document. The default value is false. +tests: + - + name: 'Unwind Array' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#unwind-array' + pipeline: + - + # Example uses the short form, the builder always generates the verbose form + # $unwind: '$sizes' + $unwind: + path: '$sizes' + - + name: 'preserveNullAndEmptyArrays' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#preservenullandemptyarrays' + pipeline: + - + $unwind: + path: '$sizes' + preserveNullAndEmptyArrays: true + - + name: 'includeArrayIndex' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#includearrayindex' + pipeline: + - + $unwind: + path: '$sizes' + includeArrayIndex: 'arrayIndex' + - + name: 'Group by Unwound Values' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#group-by-unwound-values' + pipeline: + - + $unwind: + path: '$sizes' + preserveNullAndEmptyArrays: true + - + $group: + _id: '$sizes' + averagePrice: + $avg: '$price' + - + $sort: + averagePrice: -1 + - + name: 'Unwind Embedded Arrays' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#unwind-embedded-arrays' + pipeline: + - + # Example uses the short form, the builder always generates the verbose form + # $unwind: '$items' + $unwind: + path: '$items' + - + # Example uses the short form, the builder always generates the verbose form + # $unwind: '$items.tags' + $unwind: + path: '$items.tags' + - + $group: + _id: '$items.tags' + totalSalesAmount: + $sum: + $multiply: + - '$items.price' + - '$items.quantity' diff --git a/generator/js2yaml.html b/generator/js2yaml.html index eeaa72329..a0f8654af 100644 --- a/generator/js2yaml.html +++ b/generator/js2yaml.html @@ -74,6 +74,10 @@

Convert JS examples into Yaml

return new TaggedValue('bson_binary', value); } + function ISODate(value) { + return new TaggedValue('bson_utcdatetime', value); + } + function Decimal128(value) { return new TaggedValue('bson_decimal128', value) } diff --git a/generator/src/OperatorTestGenerator.php b/generator/src/OperatorTestGenerator.php index 73617664f..a8e6e91cc 100644 --- a/generator/src/OperatorTestGenerator.php +++ b/generator/src/OperatorTestGenerator.php @@ -4,6 +4,7 @@ namespace MongoDB\CodeGenerator; +use DateTimeImmutable; use InvalidArgumentException; use MongoDB\BSON\Binary; use MongoDB\BSON\Decimal128; @@ -27,6 +28,7 @@ use function basename; use function get_object_vars; use function is_array; +use function is_numeric; use function is_object; use function json_decode; use function json_encode; @@ -141,7 +143,7 @@ private function convertYamlTaggedValues(mixed $object): mixed 'bson_regex' => new Regex(...(array) $value), 'bson_int128' => new Int64($value), 'bson_decimal128' => new Decimal128($value), - 'bson_utcdatetime' => new UTCDateTime($value), + 'bson_utcdatetime' => new UTCDateTime(is_numeric($value) ? $value : new DateTimeImmutable($value)), 'bson_binary' => new Binary(base64_decode($value)), default => throw new InvalidArgumentException(sprintf('Yaml tag "%s" is not supported.', $object->getTag())), }; diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 0cc96a96d..dbc0a59c0 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -1969,9 +1969,11 @@ public static function subtract( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sum/ * @no-named-arguments - * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression + * @param BSONArray|Decimal128|Int64|PackedArray|ResolvesToArray|ResolvesToNumber|array|float|int ...$expression */ - public static function sum(Decimal128|Int64|ResolvesToNumber|float|int ...$expression): SumOperator + public static function sum( + Decimal128|Int64|PackedArray|ResolvesToArray|ResolvesToNumber|BSONArray|array|float|int ...$expression, + ): SumOperator { return new SumOperator(...$expression); } diff --git a/src/Builder/Expression/SumOperator.php b/src/Builder/Expression/SumOperator.php index aae0fdd7a..c74232d67 100644 --- a/src/Builder/Expression/SumOperator.php +++ b/src/Builder/Expression/SumOperator.php @@ -10,9 +10,11 @@ use MongoDB\BSON\Decimal128; use MongoDB\BSON\Int64; +use MongoDB\BSON\PackedArray; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Exception\InvalidArgumentException; +use MongoDB\Model\BSONArray; use function array_is_list; @@ -26,15 +28,16 @@ class SumOperator implements ResolvesToNumber, OperatorInterface { public const ENCODE = Encode::Single; - /** @var list $expression */ + /** @var list $expression */ public readonly array $expression; /** - * @param Decimal128|Int64|ResolvesToNumber|float|int ...$expression + * @param BSONArray|Decimal128|Int64|PackedArray|ResolvesToArray|ResolvesToNumber|array|float|int ...$expression * @no-named-arguments */ - public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expression) - { + public function __construct( + Decimal128|Int64|PackedArray|ResolvesToArray|ResolvesToNumber|BSONArray|array|float|int ...$expression, + ) { if (\count($expression) < 1) { throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } diff --git a/src/Builder/Stage/CollStatsStage.php b/src/Builder/Stage/CollStatsStage.php index 18ea6ecfe..96f048f46 100644 --- a/src/Builder/Stage/CollStatsStage.php +++ b/src/Builder/Stage/CollStatsStage.php @@ -12,6 +12,7 @@ use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\StageInterface; use stdClass; @@ -22,17 +23,36 @@ */ class CollStatsStage implements StageInterface, OperatorInterface { - public const ENCODE = Encode::Single; + public const ENCODE = Encode::Object; - /** @var Document|Serializable|array|stdClass $config */ - public readonly Document|Serializable|stdClass|array $config; + /** @var Optional|Document|Serializable|array|stdClass $latencyStats */ + public readonly Optional|Document|Serializable|stdClass|array $latencyStats; + + /** @var Optional|Document|Serializable|array|stdClass $storageStats */ + public readonly Optional|Document|Serializable|stdClass|array $storageStats; + + /** @var Optional|Document|Serializable|array|stdClass $count */ + public readonly Optional|Document|Serializable|stdClass|array $count; + + /** @var Optional|Document|Serializable|array|stdClass $queryExecStats */ + public readonly Optional|Document|Serializable|stdClass|array $queryExecStats; /** - * @param Document|Serializable|array|stdClass $config + * @param Optional|Document|Serializable|array|stdClass $latencyStats + * @param Optional|Document|Serializable|array|stdClass $storageStats + * @param Optional|Document|Serializable|array|stdClass $count + * @param Optional|Document|Serializable|array|stdClass $queryExecStats */ - public function __construct(Document|Serializable|stdClass|array $config) - { - $this->config = $config; + public function __construct( + Optional|Document|Serializable|stdClass|array $latencyStats = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $storageStats = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $count = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $queryExecStats = Optional::Undefined, + ) { + $this->latencyStats = $latencyStats; + $this->storageStats = $storageStats; + $this->count = $count; + $this->queryExecStats = $queryExecStats; } public function getOperator(): string diff --git a/src/Builder/Stage/CurrentOpStage.php b/src/Builder/Stage/CurrentOpStage.php index b3cbe81b0..3b5930d32 100644 --- a/src/Builder/Stage/CurrentOpStage.php +++ b/src/Builder/Stage/CurrentOpStage.php @@ -10,6 +10,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\StageInterface; /** @@ -21,8 +22,40 @@ class CurrentOpStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - public function __construct() - { + /** @var Optional|bool $allUsers */ + public readonly Optional|bool $allUsers; + + /** @var Optional|bool $idleConnections */ + public readonly Optional|bool $idleConnections; + + /** @var Optional|bool $idleCursors */ + public readonly Optional|bool $idleCursors; + + /** @var Optional|bool $idleSessions */ + public readonly Optional|bool $idleSessions; + + /** @var Optional|bool $localOps */ + public readonly Optional|bool $localOps; + + /** + * @param Optional|bool $allUsers + * @param Optional|bool $idleConnections + * @param Optional|bool $idleCursors + * @param Optional|bool $idleSessions + * @param Optional|bool $localOps + */ + public function __construct( + Optional|bool $allUsers = Optional::Undefined, + Optional|bool $idleConnections = Optional::Undefined, + Optional|bool $idleCursors = Optional::Undefined, + Optional|bool $idleSessions = Optional::Undefined, + Optional|bool $localOps = Optional::Undefined, + ) { + $this->allUsers = $allUsers; + $this->idleConnections = $idleConnections; + $this->idleCursors = $idleCursors; + $this->idleSessions = $idleSessions; + $this->localOps = $localOps; } public function getOperator(): string diff --git a/src/Builder/Stage/FactoryTrait.php b/src/Builder/Stage/FactoryTrait.php index aecec8227..cce28140b 100644 --- a/src/Builder/Stage/FactoryTrait.php +++ b/src/Builder/Stage/FactoryTrait.php @@ -133,11 +133,19 @@ public static function changeStreamSplitLargeEvent(): ChangeStreamSplitLargeEven * Returns statistics regarding a collection or view. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/ - * @param Document|Serializable|array|stdClass $config + * @param Optional|Document|Serializable|array|stdClass $latencyStats + * @param Optional|Document|Serializable|array|stdClass $storageStats + * @param Optional|Document|Serializable|array|stdClass $count + * @param Optional|Document|Serializable|array|stdClass $queryExecStats */ - public static function collStats(Document|Serializable|stdClass|array $config): CollStatsStage + public static function collStats( + Optional|Document|Serializable|stdClass|array $latencyStats = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $storageStats = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $count = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $queryExecStats = Optional::Undefined, + ): CollStatsStage { - return new CollStatsStage($config); + return new CollStatsStage($latencyStats, $storageStats, $count, $queryExecStats); } /** @@ -156,10 +164,21 @@ public static function count(string $field): CountStage * Returns information on active and/or dormant operations for the MongoDB deployment. To run, use the db.aggregate() method. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/currentOp/ + * @param Optional|bool $allUsers + * @param Optional|bool $idleConnections + * @param Optional|bool $idleCursors + * @param Optional|bool $idleSessions + * @param Optional|bool $localOps */ - public static function currentOp(): CurrentOpStage + public static function currentOp( + Optional|bool $allUsers = Optional::Undefined, + Optional|bool $idleConnections = Optional::Undefined, + Optional|bool $idleCursors = Optional::Undefined, + Optional|bool $idleSessions = Optional::Undefined, + Optional|bool $localOps = Optional::Undefined, + ): CurrentOpStage { - return new CurrentOpStage(); + return new CurrentOpStage($allUsers, $idleConnections, $idleCursors, $idleSessions, $localOps); } /** @@ -236,7 +255,7 @@ public static function fill( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/ * @param string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. - * @param Document|Serializable|array|stdClass $near The point for which to find the closest documents. + * @param Document|ResolvesToObject|Serializable|array|stdClass $near The point for which to find the closest documents. * @param Optional|Decimal128|Int64|float|int $distanceMultiplier The factor to multiply all distances returned by the query. For example, use the distanceMultiplier to convert radians, as returned by a spherical query, to kilometers by multiplying by the radius of the Earth. * @param Optional|string $includeLocs This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. * @param Optional|string $key Specify the geospatial indexed field to use when calculating the distance. @@ -253,7 +272,7 @@ public static function fill( */ public static function geoNear( string $distanceField, - Document|Serializable|stdClass|array $near, + Document|Serializable|ResolvesToObject|stdClass|array $near, Optional|Decimal128|Int64|float|int $distanceMultiplier = Optional::Undefined, Optional|string $includeLocs = Optional::Undefined, Optional|string $key = Optional::Undefined, @@ -431,17 +450,17 @@ public static function match(QueryInterface|array $query): MatchStage * New in MongoDB 4.2. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/ - * @param string $into The output collection. + * @param Document|Serializable|array|stdClass|string $into The output collection. * @param Optional|BSONArray|PackedArray|array|string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. * @param Optional|Document|Serializable|array|stdClass $let Specifies variables for use in the whenMatched pipeline. - * @param Optional|string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). + * @param Optional|BSONArray|PackedArray|Pipeline|array|string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). * @param Optional|string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. */ public static function merge( - string $into, + Document|Serializable|stdClass|array|string $into, Optional|PackedArray|BSONArray|array|string $on = Optional::Undefined, Optional|Document|Serializable|stdClass|array $let = Optional::Undefined, - Optional|string $whenMatched = Optional::Undefined, + Optional|PackedArray|Pipeline|BSONArray|array|string $whenMatched = Optional::Undefined, Optional|string $whenNotMatched = Optional::Undefined, ): MergeStage { @@ -452,17 +471,11 @@ public static function merge( * Writes the resulting documents of the aggregation pipeline to a collection. To use the $out stage, it must be the last stage in the pipeline. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/ - * @param string $db Target collection name to write documents from $out to. - * @param string $coll Target database name to write documents from $out to. - * @param Optional|Document|Serializable|array|stdClass $timeseries If set, the aggregation stage will use these options to create or replace a time-series collection in the given namespace. + * @param Document|Serializable|array|stdClass|string $coll Target database name to write documents from $out to. */ - public static function out( - string $db, - string $coll, - Optional|Document|Serializable|stdClass|array $timeseries = Optional::Undefined, - ): OutStage + public static function out(Document|Serializable|stdClass|array|string $coll): OutStage { - return new OutStage($db, $coll, $timeseries); + return new OutStage($coll); } /** diff --git a/src/Builder/Stage/GeoNearStage.php b/src/Builder/Stage/GeoNearStage.php index 65996e343..d39766dc5 100644 --- a/src/Builder/Stage/GeoNearStage.php +++ b/src/Builder/Stage/GeoNearStage.php @@ -12,6 +12,7 @@ use MongoDB\BSON\Document; use MongoDB\BSON\Int64; use MongoDB\BSON\Serializable; +use MongoDB\Builder\Expression\ResolvesToObject; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; @@ -34,8 +35,8 @@ class GeoNearStage implements StageInterface, OperatorInterface /** @var string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. */ public readonly string $distanceField; - /** @var Document|Serializable|array|stdClass $near The point for which to find the closest documents. */ - public readonly Document|Serializable|stdClass|array $near; + /** @var Document|ResolvesToObject|Serializable|array|stdClass $near The point for which to find the closest documents. */ + public readonly Document|Serializable|ResolvesToObject|stdClass|array $near; /** @var Optional|Decimal128|Int64|float|int $distanceMultiplier The factor to multiply all distances returned by the query. For example, use the distanceMultiplier to convert radians, as returned by a spherical query, to kilometers by multiplying by the radius of the Earth. */ public readonly Optional|Decimal128|Int64|float|int $distanceMultiplier; @@ -74,7 +75,7 @@ class GeoNearStage implements StageInterface, OperatorInterface /** * @param string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. - * @param Document|Serializable|array|stdClass $near The point for which to find the closest documents. + * @param Document|ResolvesToObject|Serializable|array|stdClass $near The point for which to find the closest documents. * @param Optional|Decimal128|Int64|float|int $distanceMultiplier The factor to multiply all distances returned by the query. For example, use the distanceMultiplier to convert radians, as returned by a spherical query, to kilometers by multiplying by the radius of the Earth. * @param Optional|string $includeLocs This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. * @param Optional|string $key Specify the geospatial indexed field to use when calculating the distance. @@ -91,7 +92,7 @@ class GeoNearStage implements StageInterface, OperatorInterface */ public function __construct( string $distanceField, - Document|Serializable|stdClass|array $near, + Document|Serializable|ResolvesToObject|stdClass|array $near, Optional|Decimal128|Int64|float|int $distanceMultiplier = Optional::Undefined, Optional|string $includeLocs = Optional::Undefined, Optional|string $key = Optional::Undefined, diff --git a/src/Builder/Stage/GroupStage.php b/src/Builder/Stage/GroupStage.php index bc1466205..a16038007 100644 --- a/src/Builder/Stage/GroupStage.php +++ b/src/Builder/Stage/GroupStage.php @@ -45,10 +45,6 @@ public function __construct( Document|Serializable|AccumulatorInterface|stdClass|array ...$field, ) { $this->_id = $_id; - if (\count($field) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); - } - foreach($field as $key => $value) { if (! is_string($key)) { throw new InvalidArgumentException('Expected $field arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); diff --git a/src/Builder/Stage/MergeStage.php b/src/Builder/Stage/MergeStage.php index 6af1e28d0..271c012ac 100644 --- a/src/Builder/Stage/MergeStage.php +++ b/src/Builder/Stage/MergeStage.php @@ -11,6 +11,7 @@ use MongoDB\BSON\Document; use MongoDB\BSON\PackedArray; use MongoDB\BSON\Serializable; +use MongoDB\Builder\Pipeline; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; @@ -32,8 +33,8 @@ class MergeStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Object; - /** @var string $into The output collection. */ - public readonly string $into; + /** @var Document|Serializable|array|stdClass|string $into The output collection. */ + public readonly Document|Serializable|stdClass|array|string $into; /** @var Optional|BSONArray|PackedArray|array|string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. */ public readonly Optional|PackedArray|BSONArray|array|string $on; @@ -41,24 +42,24 @@ class MergeStage implements StageInterface, OperatorInterface /** @var Optional|Document|Serializable|array|stdClass $let Specifies variables for use in the whenMatched pipeline. */ public readonly Optional|Document|Serializable|stdClass|array $let; - /** @var Optional|string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). */ - public readonly Optional|string $whenMatched; + /** @var Optional|BSONArray|PackedArray|Pipeline|array|string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). */ + public readonly Optional|PackedArray|Pipeline|BSONArray|array|string $whenMatched; /** @var Optional|string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. */ public readonly Optional|string $whenNotMatched; /** - * @param string $into The output collection. + * @param Document|Serializable|array|stdClass|string $into The output collection. * @param Optional|BSONArray|PackedArray|array|string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. * @param Optional|Document|Serializable|array|stdClass $let Specifies variables for use in the whenMatched pipeline. - * @param Optional|string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). + * @param Optional|BSONArray|PackedArray|Pipeline|array|string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). * @param Optional|string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. */ public function __construct( - string $into, + Document|Serializable|stdClass|array|string $into, Optional|PackedArray|BSONArray|array|string $on = Optional::Undefined, Optional|Document|Serializable|stdClass|array $let = Optional::Undefined, - Optional|string $whenMatched = Optional::Undefined, + Optional|PackedArray|Pipeline|BSONArray|array|string $whenMatched = Optional::Undefined, Optional|string $whenNotMatched = Optional::Undefined, ) { $this->into = $into; @@ -68,6 +69,10 @@ public function __construct( $this->on = $on; $this->let = $let; + if (is_array($whenMatched) && ! array_is_list($whenMatched)) { + throw new InvalidArgumentException('Expected $whenMatched argument to be a list, got an associative array.'); + } + $this->whenMatched = $whenMatched; $this->whenNotMatched = $whenNotMatched; } diff --git a/src/Builder/Stage/OutStage.php b/src/Builder/Stage/OutStage.php index e87a3f896..5e99d578a 100644 --- a/src/Builder/Stage/OutStage.php +++ b/src/Builder/Stage/OutStage.php @@ -12,7 +12,6 @@ use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; -use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\StageInterface; use stdClass; @@ -23,30 +22,17 @@ */ class OutStage implements StageInterface, OperatorInterface { - public const ENCODE = Encode::Object; + public const ENCODE = Encode::Single; - /** @var string $db Target collection name to write documents from $out to. */ - public readonly string $db; - - /** @var string $coll Target database name to write documents from $out to. */ - public readonly string $coll; - - /** @var Optional|Document|Serializable|array|stdClass $timeseries If set, the aggregation stage will use these options to create or replace a time-series collection in the given namespace. */ - public readonly Optional|Document|Serializable|stdClass|array $timeseries; + /** @var Document|Serializable|array|stdClass|string $coll Target database name to write documents from $out to. */ + public readonly Document|Serializable|stdClass|array|string $coll; /** - * @param string $db Target collection name to write documents from $out to. - * @param string $coll Target database name to write documents from $out to. - * @param Optional|Document|Serializable|array|stdClass $timeseries If set, the aggregation stage will use these options to create or replace a time-series collection in the given namespace. + * @param Document|Serializable|array|stdClass|string $coll Target database name to write documents from $out to. */ - public function __construct( - string $db, - string $coll, - Optional|Document|Serializable|stdClass|array $timeseries = Optional::Undefined, - ) { - $this->db = $db; + public function __construct(Document|Serializable|stdClass|array|string $coll) + { $this->coll = $coll; - $this->timeseries = $timeseries; } public function getOperator(): string diff --git a/src/Builder/Stage/SortByCountStage.php b/src/Builder/Stage/SortByCountStage.php index 8fda2cbee..9c7160b8c 100644 --- a/src/Builder/Stage/SortByCountStage.php +++ b/src/Builder/Stage/SortByCountStage.php @@ -22,7 +22,7 @@ */ class SortByCountStage implements StageInterface, OperatorInterface { - public const ENCODE = Encode::Object; + public const ENCODE = Encode::Single; /** @var ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression */ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression; diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 868978d15..8bfde10a6 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -4850,7 +4850,7 @@ enum Pipelines: string } }, { - "$numberDouble": "10.230000000000000426" + "$numberDecimal": "10.23" }, { "a": "On sale" diff --git a/tests/Builder/Expression/SortArrayOperatorTest.php b/tests/Builder/Expression/SortArrayOperatorTest.php index a43887dca..3b2b2ef3e 100644 --- a/tests/Builder/Expression/SortArrayOperatorTest.php +++ b/tests/Builder/Expression/SortArrayOperatorTest.php @@ -4,6 +4,7 @@ namespace MongoDB\Tests\Builder\Expression; +use MongoDB\BSON\Decimal128; use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; @@ -83,7 +84,7 @@ public function testSortOnMixedTypeFields(): void 'Gratis', ['a' => null], object(a: object(sale: true, price: 19)), - 10.23, + new Decimal128('10.23'), ['a' => 'On sale'], ], sortBy: 1, diff --git a/tests/Builder/Stage/AddFieldsStageTest.php b/tests/Builder/Stage/AddFieldsStageTest.php index 9e8863139..970471ba2 100644 --- a/tests/Builder/Stage/AddFieldsStageTest.php +++ b/tests/Builder/Stage/AddFieldsStageTest.php @@ -14,6 +14,23 @@ */ class AddFieldsStageTest extends PipelineTestCase { + public function testAddElementToAnArray(): void + { + $pipeline = new Pipeline( + Stage::match( + _id: 1, + ), + Stage::addFields( + homework: Expression::concatArrays( + Expression::arrayFieldPath('homework'), + [7], + ), + ), + ); + + $this->assertSamePipeline(Pipelines::AddFieldsAddElementToAnArray, $pipeline); + } + public function testAddingFieldsToAnEmbeddedDocument(): void { $pipeline = new Pipeline( @@ -25,10 +42,19 @@ public function testAddingFieldsToAnEmbeddedDocument(): void $this->assertSamePipeline(Pipelines::AddFieldsAddingFieldsToAnEmbeddedDocument, $pipeline); } - public function testUsingTwoAddFieldsStages(): void + public function testOverwritingAnExistingField(): void { - $this->markTestSkipped('$sum must accept arrayFieldPath and render it as a single value: https://jira.mongodb.org/browse/PHPLIB-1287'); + $pipeline = new Pipeline( + Stage::addFields( + cats: 20, + ), + ); + $this->assertSamePipeline(Pipelines::AddFieldsOverwritingAnExistingField, $pipeline); + } + + public function testUsingTwoAddFieldsStages(): void + { $pipeline = new Pipeline( Stage::addFields( totalHomework: Expression::sum(Expression::fieldPath('homework')), diff --git a/tests/Builder/Stage/BucketAutoStageTest.php b/tests/Builder/Stage/BucketAutoStageTest.php new file mode 100644 index 000000000..ecae48f20 --- /dev/null +++ b/tests/Builder/Stage/BucketAutoStageTest.php @@ -0,0 +1,28 @@ +assertSamePipeline(Pipelines::BucketAutoSingleFacetAggregation, $pipeline); + } +} diff --git a/tests/Builder/Stage/BucketStageTest.php b/tests/Builder/Stage/BucketStageTest.php new file mode 100644 index 000000000..95119a5e3 --- /dev/null +++ b/tests/Builder/Stage/BucketStageTest.php @@ -0,0 +1,94 @@ +assertSamePipeline(Pipelines::BucketBucketByYearAndFilterByBucketResults, $pipeline); + } + + public function testUseBucketWithFacetToBucketByMultipleFields(): void + { + $pipeline = new Pipeline( + Stage::facet( + price: new Pipeline( + Stage::bucket( + groupBy: Expression::numberFieldPath('price'), + boundaries: [0, 200, 400], + default: 'Other', + output: object( + count: Accumulator::sum(1), + artwork: Accumulator::push( + object( + title: Expression::stringFieldPath('title'), + price: Expression::stringFieldPath('price'), + ), + ), + averagePrice: Accumulator::avg( + Expression::numberFieldPath('price'), + ), + ), + ), + ), + year: new Pipeline( + Stage::bucket( + groupBy: Expression::stringFieldPath('year'), + boundaries: [1890, 1910, 1920, 1940], + default: 'Unknown', + output: object( + count: Accumulator::sum(1), + artwork: Accumulator::push( + object( + title: Expression::stringFieldPath('title'), + year: Expression::stringFieldPath('year'), + ), + ), + ), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::BucketUseBucketWithFacetToBucketByMultipleFields, $pipeline); + } +} diff --git a/tests/Builder/Stage/ChangeStreamSplitLargeEventStageTest.php b/tests/Builder/Stage/ChangeStreamSplitLargeEventStageTest.php new file mode 100644 index 000000000..c7652e2de --- /dev/null +++ b/tests/Builder/Stage/ChangeStreamSplitLargeEventStageTest.php @@ -0,0 +1,24 @@ +assertSamePipeline(Pipelines::ChangeStreamSplitLargeEventExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/ChangeStreamStageTest.php b/tests/Builder/Stage/ChangeStreamStageTest.php new file mode 100644 index 000000000..03a5bf523 --- /dev/null +++ b/tests/Builder/Stage/ChangeStreamStageTest.php @@ -0,0 +1,24 @@ +assertSamePipeline(Pipelines::ChangeStreamExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/CollStatsStageTest.php b/tests/Builder/Stage/CollStatsStageTest.php new file mode 100644 index 000000000..5b9277d27 --- /dev/null +++ b/tests/Builder/Stage/CollStatsStageTest.php @@ -0,0 +1,63 @@ +assertSamePipeline(Pipelines::CollStatsCountField, $pipeline); + } + + public function testLatencyStatsDocument(): void + { + $pipeline = new Pipeline( + Stage::collStats( + latencyStats: object( + histograms: true, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::CollStatsLatencyStatsDocument, $pipeline); + } + + public function testQueryExecStatsDocument(): void + { + $pipeline = new Pipeline( + Stage::collStats( + queryExecStats: object(), + ), + ); + + $this->assertSamePipeline(Pipelines::CollStatsQueryExecStatsDocument, $pipeline); + } + + public function testStorageStatsDocument(): void + { + $pipeline = new Pipeline( + Stage::collStats( + storageStats: object(), + ), + ); + + $this->assertSamePipeline(Pipelines::CollStatsStorageStatsDocument, $pipeline); + } +} diff --git a/tests/Builder/Stage/CountStageTest.php b/tests/Builder/Stage/CountStageTest.php new file mode 100644 index 000000000..68c45c52e --- /dev/null +++ b/tests/Builder/Stage/CountStageTest.php @@ -0,0 +1,28 @@ +assertSamePipeline(Pipelines::CountExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/CurrentOpStageTest.php b/tests/Builder/Stage/CurrentOpStageTest.php new file mode 100644 index 000000000..e1370a23e --- /dev/null +++ b/tests/Builder/Stage/CurrentOpStageTest.php @@ -0,0 +1,47 @@ +assertSamePipeline(Pipelines::CurrentOpInactiveSessions, $pipeline); + } + + public function testSampledQueries(): void + { + $pipeline = new Pipeline( + Stage::currentOp( + allUsers: true, + localOps: true, + ), + Stage::match( + desc: 'query analyzer', + ), + ); + + $this->assertSamePipeline(Pipelines::CurrentOpSampledQueries, $pipeline); + } +} diff --git a/tests/Builder/Stage/DensifyStageTest.php b/tests/Builder/Stage/DensifyStageTest.php new file mode 100644 index 000000000..18630ed5d --- /dev/null +++ b/tests/Builder/Stage/DensifyStageTest.php @@ -0,0 +1,54 @@ +assertSamePipeline(Pipelines::DensifyDensifictionWithPartitions, $pipeline); + } + + public function testDensifyTimeSeriesData(): void + { + $pipeline = new Pipeline( + Stage::densify( + field: 'timestamp', + range: object( + step: 1, + unit: 'hour', + bounds: [ + new UTCDateTime(new DateTimeImmutable('2021-05-18T00:00:00.000Z')), + new UTCDateTime(new DateTimeImmutable('2021-05-18T08:00:00.000Z')), + ], + ), + ), + ); + + $this->assertSamePipeline(Pipelines::DensifyDensifyTimeSeriesData, $pipeline); + } +} diff --git a/tests/Builder/Stage/DocumentsStageTest.php b/tests/Builder/Stage/DocumentsStageTest.php new file mode 100644 index 000000000..e0b2e4ce0 --- /dev/null +++ b/tests/Builder/Stage/DocumentsStageTest.php @@ -0,0 +1,56 @@ +assertSamePipeline(Pipelines::DocumentsTestAPipelineStage, $pipeline); + } + + public function testUseADocumentsStageInALookupStage(): void + { + $pipeline = new Pipeline( + Stage::match(), + Stage::lookup( + localField: 'zip', + foreignField: 'zip_id', + as: 'city_state', + pipeline: new Pipeline( + Stage::documents([ + Document::fromPHP(object(zip_id: 94301, name: 'Palo Alto, CA')), + Document::fromPHP(object(zip_id: 10019, name: 'New York, NY')), + ]), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::DocumentsUseADocumentsStageInALookupStage, $pipeline); + } +} diff --git a/tests/Builder/Stage/FacetStageTest.php b/tests/Builder/Stage/FacetStageTest.php new file mode 100644 index 000000000..52ad2c428 --- /dev/null +++ b/tests/Builder/Stage/FacetStageTest.php @@ -0,0 +1,62 @@ + new Pipeline( + Stage::bucketAuto( + groupBy: Expression::stringFieldPath('year'), + buckets: 4, + ), + ), + ], + categorizedByTags: new Pipeline( + Stage::unwind( + Expression::arrayFieldPath('tags'), + ), + Stage::sortByCount( + Expression::arrayFieldPath('tags'), + ), + ), + categorizedByPrice: new Pipeline( + Stage::match( + price: Query::exists(true), + ), + Stage::bucket( + groupBy: Expression::numberFieldPath('price'), + boundaries: [0, 150, 200, 300, 400], + default: 'Other', + output: object( + count: Accumulator::sum(1), + titles: Accumulator::push( + Expression::stringFieldPath('title'), + ), + ), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FacetExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/FillStageTest.php b/tests/Builder/Stage/FillStageTest.php new file mode 100644 index 000000000..6d11796e1 --- /dev/null +++ b/tests/Builder/Stage/FillStageTest.php @@ -0,0 +1,110 @@ +assertSamePipeline(Pipelines::FillFillDataForDistinctPartitions, $pipeline); + } + + public function testFillMissingFieldValuesBasedOnTheLastObservedValue(): void + { + $pipeline = new Pipeline( + Stage::fill( + sortBy: object( + date: 1, + ), + output: object( + score: object(method: 'locf'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FillFillMissingFieldValuesBasedOnTheLastObservedValue, $pipeline); + } + + public function testFillMissingFieldValuesWithAConstantValue(): void + { + $pipeline = new Pipeline( + Stage::fill( + output: object( + bootsSold: object(value: 0), + sandalsSold: object(value: 0), + sneakersSold: object(value: 0), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FillFillMissingFieldValuesWithAConstantValue, $pipeline); + } + + public function testFillMissingFieldValuesWithLinearInterpolation(): void + { + $pipeline = new Pipeline( + Stage::fill( + sortBy: object( + time: 1, + ), + output: object( + price: object(method: 'linear'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FillFillMissingFieldValuesWithLinearInterpolation, $pipeline); + } + + public function testIndicateIfAFieldWasPopulatedUsingFill(): void + { + $pipeline = new Pipeline( + Stage::set( + valueExisted: Expression::ifNull( + Expression::toBool( + Expression::toString( + Expression::fieldPath('score'), + ), + ), + false, + ), + ), + Stage::fill( + sortBy: object( + date: 1, + ), + output: object( + score: object(method: 'locf'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::FillIndicateIfAFieldWasPopulatedUsingFill, $pipeline); + } +} diff --git a/tests/Builder/Stage/GeoNearStageTest.php b/tests/Builder/Stage/GeoNearStageTest.php new file mode 100644 index 000000000..93acfc277 --- /dev/null +++ b/tests/Builder/Stage/GeoNearStageTest.php @@ -0,0 +1,123 @@ +assertSamePipeline(Pipelines::GeoNearMaximumDistance, $pipeline); + } + + public function testMinimumDistance(): void + { + $pipeline = new Pipeline( + Stage::geoNear( + near: object( + type: 'Point', + coordinates: [-73.99279, 40.719296], + ), + distanceField: 'dist.calculated', + minDistance: 2, + query: Query::query( + category: 'Parks', + ), + includeLocs: 'dist.location', + spherical: true, + ), + ); + + $this->assertSamePipeline(Pipelines::GeoNearMinimumDistance, $pipeline); + } + + public function testSpecifyWhichGeospatialIndexToUse(): void + { + $pipeline = new Pipeline( + Stage::geoNear( + near: object( + type: 'Point', + coordinates: [-73.98142, 40.71782], + ), + key: 'location', + distanceField: 'dist.calculated', + query: Query::query( + category: 'Parks', + ), + ), + Stage::limit(5), + ); + + $this->assertSamePipeline(Pipelines::GeoNearSpecifyWhichGeospatialIndexToUse, $pipeline); + } + + public function testWithBoundLetOption(): void + { + $pipeline = new Pipeline( + Stage::lookup( + from: 'places', + let: object( + pt: Expression::stringFieldPath('location'), + ), + pipeline: new Pipeline( + Stage::geoNear( + near: Expression::variable('pt'), + distanceField: 'distance', + ), + ), + as: 'joinedField', + ), + Stage::match( + name: 'Sara D. Roosevelt Park', + ), + ); + + $this->assertSamePipeline(Pipelines::GeoNearWithBoundLetOption, $pipeline); + } + + public function testWithTheLetOption(): void + { + $pipeline = new Pipeline( + Stage::geoNear( + near: Expression::variable('pt'), + distanceField: 'distance', + maxDistance: 2, + query: Query::query( + category: 'Parks', + ), + includeLocs: 'dist.location', + spherical: true, + ), + ); + + $this->assertSamePipeline(Pipelines::GeoNearWithTheLetOption, $pipeline); + } +} diff --git a/tests/Builder/Stage/GraphLookupStageTest.php b/tests/Builder/Stage/GraphLookupStageTest.php new file mode 100644 index 000000000..fd4a97b4c --- /dev/null +++ b/tests/Builder/Stage/GraphLookupStageTest.php @@ -0,0 +1,77 @@ +assertSamePipeline(Pipelines::GraphLookupAcrossMultipleCollections, $pipeline); + } + + public function testWithAQueryFilter(): void + { + $pipeline = new Pipeline( + Stage::match( + name: 'Tanya Jordan', + ), + Stage::graphLookup( + from: 'people', + startWith: Expression::stringFieldPath('friends'), + connectFromField: 'friends', + connectToField: 'name', + as: 'golfers', + restrictSearchWithMatch: Query::query( + hobbies: 'golf', + ), + ), + Stage::project( + ...[ + 'connections who play golf' => Expression::stringFieldPath('golfers.name'), + ], + name: 1, + friends: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::GraphLookupWithAQueryFilter, $pipeline); + } + + public function testWithinASingleCollection(): void + { + $pipeline = new Pipeline( + Stage::graphLookup( + from: 'employees', + startWith: Expression::stringFieldPath('reportsTo'), + connectFromField: 'reportsTo', + connectToField: 'name', + as: 'reportingHierarchy', + ), + ); + + $this->assertSamePipeline(Pipelines::GraphLookupWithinASingleCollection, $pipeline); + } +} diff --git a/tests/Builder/Stage/GroupStageTest.php b/tests/Builder/Stage/GroupStageTest.php new file mode 100644 index 000000000..74bfe0265 --- /dev/null +++ b/tests/Builder/Stage/GroupStageTest.php @@ -0,0 +1,151 @@ +assertSamePipeline(Pipelines::GroupCalculateCountSumAndAverage, $pipeline); + } + + public function testCountTheNumberOfDocumentsInACollection(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: null, + count: Accumulator::count(), + ), + ); + + $this->assertSamePipeline(Pipelines::GroupCountTheNumberOfDocumentsInACollection, $pipeline); + } + + public function testGroupByItemHaving(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('item'), + totalSaleAmount: Accumulator::sum( + Expression::multiply( + Expression::numberFieldPath('price'), + Expression::numberFieldPath('quantity'), + ), + ), + ), + Stage::match( + totalSaleAmount: Query::gte(100), + ), + ); + + $this->assertSamePipeline(Pipelines::GroupGroupByItemHaving, $pipeline); + } + + public function testGroupByNull(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: null, + totalSaleAmount: Accumulator::sum( + Expression::multiply( + Expression::numberFieldPath('price'), + Expression::numberFieldPath('quantity'), + ), + ), + averageQuantity: Accumulator::avg( + Expression::numberFieldPath('quantity'), + ), + count: Accumulator::sum(1), + ), + ); + + $this->assertSamePipeline(Pipelines::GroupGroupByNull, $pipeline); + } + + public function testGroupDocumentsByAuthor(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('author'), + books: Accumulator::push( + Expression::variable('ROOT'), + ), + ), + Stage::addFields( + totalCopies: Expression::sum( + Expression::numberFieldPath('books.copies'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::GroupGroupDocumentsByAuthor, $pipeline); + } + + public function testPivotData(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::fieldPath('author'), + books: Accumulator::push( + Expression::stringFieldPath('title'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::GroupPivotData, $pipeline); + } + + public function testRetrieveDistinctValues(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::stringFieldPath('item'), + ), + ); + + $this->assertSamePipeline(Pipelines::GroupRetrieveDistinctValues, $pipeline); + } +} diff --git a/tests/Builder/Stage/IndexStatsStageTest.php b/tests/Builder/Stage/IndexStatsStageTest.php new file mode 100644 index 000000000..9b121417b --- /dev/null +++ b/tests/Builder/Stage/IndexStatsStageTest.php @@ -0,0 +1,24 @@ +assertSamePipeline(Pipelines::IndexStatsExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/LimitStageTest.php b/tests/Builder/Stage/LimitStageTest.php new file mode 100644 index 000000000..04c9c6bbd --- /dev/null +++ b/tests/Builder/Stage/LimitStageTest.php @@ -0,0 +1,24 @@ +assertSamePipeline(Pipelines::LimitExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/ListLocalSessionsStageTest.php b/tests/Builder/Stage/ListLocalSessionsStageTest.php new file mode 100644 index 000000000..57d1bf06d --- /dev/null +++ b/tests/Builder/Stage/ListLocalSessionsStageTest.php @@ -0,0 +1,50 @@ +assertSamePipeline(Pipelines::ListLocalSessionsListAllLocalSessions, $pipeline); + } + + public function testListAllLocalSessionsForTheCurrentUser(): void + { + $pipeline = new Pipeline( + Stage::listLocalSessions(), + ); + + $this->assertSamePipeline(Pipelines::ListLocalSessionsListAllLocalSessionsForTheCurrentUser, $pipeline); + } + + public function testListAllLocalSessionsForTheSpecifiedUsers(): void + { + $pipeline = new Pipeline( + Stage::listLocalSessions( + users: [ + object(user: 'myAppReader', db: 'test'), + ], + ), + ); + + $this->assertSamePipeline(Pipelines::ListLocalSessionsListAllLocalSessionsForTheSpecifiedUsers, $pipeline); + } +} diff --git a/tests/Builder/Stage/ListSampledQueriesStageTest.php b/tests/Builder/Stage/ListSampledQueriesStageTest.php new file mode 100644 index 000000000..3d1fccf29 --- /dev/null +++ b/tests/Builder/Stage/ListSampledQueriesStageTest.php @@ -0,0 +1,35 @@ +assertSamePipeline(Pipelines::ListSampledQueriesListSampledQueriesForASpecificCollection, $pipeline); + } + + public function testListSampledQueriesForAllCollections(): void + { + $pipeline = new Pipeline( + Stage::listSampledQueries(), + ); + + $this->assertSamePipeline(Pipelines::ListSampledQueriesListSampledQueriesForAllCollections, $pipeline); + } +} diff --git a/tests/Builder/Stage/ListSearchIndexesStageTest.php b/tests/Builder/Stage/ListSearchIndexesStageTest.php new file mode 100644 index 000000000..e5bdbfd18 --- /dev/null +++ b/tests/Builder/Stage/ListSearchIndexesStageTest.php @@ -0,0 +1,46 @@ +assertSamePipeline(Pipelines::ListSearchIndexesReturnASingleSearchIndexById, $pipeline); + } + + public function testReturnASingleSearchIndexByName(): void + { + $pipeline = new Pipeline( + Stage::listSearchIndexes( + name: 'synonym-mappings', + ), + ); + + $this->assertSamePipeline(Pipelines::ListSearchIndexesReturnASingleSearchIndexByName, $pipeline); + } + + public function testReturnAllSearchIndexes(): void + { + $pipeline = new Pipeline( + Stage::listSearchIndexes(), + ); + + $this->assertSamePipeline(Pipelines::ListSearchIndexesReturnAllSearchIndexes, $pipeline); + } +} diff --git a/tests/Builder/Stage/ListSessionsStageTest.php b/tests/Builder/Stage/ListSessionsStageTest.php new file mode 100644 index 000000000..a6d68e3b1 --- /dev/null +++ b/tests/Builder/Stage/ListSessionsStageTest.php @@ -0,0 +1,50 @@ +assertSamePipeline(Pipelines::ListSessionsListAllSessions, $pipeline); + } + + public function testListAllSessionsForTheCurrentUser(): void + { + $pipeline = new Pipeline( + Stage::listSessions(), + ); + + $this->assertSamePipeline(Pipelines::ListSessionsListAllSessionsForTheCurrentUser, $pipeline); + } + + public function testListAllSessionsForTheSpecifiedUsers(): void + { + $pipeline = new Pipeline( + Stage::listSessions( + users: [ + object(user: 'myAppReader', db: 'test'), + ], + ), + ); + + $this->assertSamePipeline(Pipelines::ListSessionsListAllSessionsForTheSpecifiedUsers, $pipeline); + } +} diff --git a/tests/Builder/Stage/LookupStageTest.php b/tests/Builder/Stage/LookupStageTest.php new file mode 100644 index 000000000..3b541ebb4 --- /dev/null +++ b/tests/Builder/Stage/LookupStageTest.php @@ -0,0 +1,161 @@ +assertSamePipeline(Pipelines::LookupPerformAConciseCorrelatedSubqueryWithLookup, $pipeline); + } + + public function testPerformASingleEqualityJoinWithLookup(): void + { + $pipeline = new Pipeline( + Stage::lookup( + from: 'inventory', + localField: 'item', + foreignField: 'sku', + as: 'inventory_docs', + ), + ); + + $this->assertSamePipeline(Pipelines::LookupPerformASingleEqualityJoinWithLookup, $pipeline); + } + + public function testPerformAnUncorrelatedSubqueryWithLookup(): void + { + $pipeline = new Pipeline( + Stage::lookup( + from: 'holidays', + pipeline: new Pipeline( + Stage::match( + year: 2018, + ), + Stage::project( + _id: 0, + date: object( + name: Expression::stringFieldPath('name'), + date: Expression::dateFieldPath('date'), + ), + ), + Stage::replaceRoot(Expression::objectFieldPath('date')), + ), + as: 'holidays', + ), + ); + + $this->assertSamePipeline(Pipelines::LookupPerformAnUncorrelatedSubqueryWithLookup, $pipeline); + } + + public function testPerformMultipleJoinsAndACorrelatedSubqueryWithLookup(): void + { + $pipeline = new Pipeline( + Stage::lookup( + from: 'warehouses', + let: object( + order_item: Expression::fieldPath('item'), + order_qty: Expression::intFieldPath('ordered'), + ), + pipeline: new Pipeline( + Stage::match( + Query::expr( + Expression::and( + Expression::eq( + Expression::stringFieldPath('stock_item'), + Expression::variable('order_item'), + ), + Expression::gte( + Expression::intFieldPath('instock'), + Expression::variable('order_qty'), + ), + ), + ), + ), + Stage::project( + stock_item: 0, + _id: 0, + ), + ), + as: 'stockdata', + ), + ); + + $this->assertSamePipeline(Pipelines::LookupPerformMultipleJoinsAndACorrelatedSubqueryWithLookup, $pipeline); + } + + public function testUseLookupWithAnArray(): void + { + $pipeline = new Pipeline( + Stage::lookup( + from: 'members', + localField: 'enrollmentlist', + foreignField: 'name', + as: 'enrollee_info', + ), + ); + + $this->assertSamePipeline(Pipelines::LookupUseLookupWithAnArray, $pipeline); + } + + public function testUseLookupWithMergeObjects(): void + { + $pipeline = new Pipeline( + Stage::lookup( + from: 'items', + localField: 'item', + foreignField: 'item', + as: 'fromItems', + ), + Stage::replaceRoot( + Expression::mergeObjects( + Expression::arrayElemAt( + Expression::arrayFieldPath('fromItems'), + 0, + ), + Expression::variable('ROOT'), + ), + ), + Stage::project( + fromItems: 0, + ), + ); + + $this->assertSamePipeline(Pipelines::LookupUseLookupWithMergeObjects, $pipeline); + } +} diff --git a/tests/Builder/Stage/MatchStageTest.php b/tests/Builder/Stage/MatchStageTest.php new file mode 100644 index 000000000..31c2d9554 --- /dev/null +++ b/tests/Builder/Stage/MatchStageTest.php @@ -0,0 +1,53 @@ +assertSamePipeline(Pipelines::MatchEqualityMatch, $pipeline); + } + + public function testPerformACount(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::or( + Query::query( + score: [ + Query::gt(70), + Query::lt(90), + ], + ), + Query::query( + views: Query::gte(1000), + ), + ), + ), + Stage::group( + _id: null, + count: Accumulator::sum(1), + ), + ); + + $this->assertSamePipeline(Pipelines::MatchPerformACount, $pipeline); + } +} diff --git a/tests/Builder/Stage/MergeStageTest.php b/tests/Builder/Stage/MergeStageTest.php new file mode 100644 index 000000000..7c145fc29 --- /dev/null +++ b/tests/Builder/Stage/MergeStageTest.php @@ -0,0 +1,188 @@ +assertSamePipeline(Pipelines::MergeMergeResultsFromMultipleCollections, $pipeline); + } + + public function testOnDemandMaterializedViewInitialCreation(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: object( + fiscal_year: Expression::stringFieldPath('fiscal_year'), + dept: Expression::stringFieldPath('dept'), + ), + salaries: Accumulator::sum( + Expression::numberFieldPath('salary'), + ), + ), + Stage::merge( + into: object( + db: 'reporting', + coll: 'budgets', + ), + on: '_id', + whenMatched: 'replace', + whenNotMatched: 'insert', + ), + ); + + $this->assertSamePipeline(Pipelines::MergeOnDemandMaterializedViewInitialCreation, $pipeline); + } + + public function testOnDemandMaterializedViewUpdateReplaceData(): void + { + $pipeline = new Pipeline( + Stage::match( + fiscal_year: Query::gte(2019), + ), + Stage::group( + _id: object( + fiscal_year: Expression::stringFieldPath('fiscal_year'), + dept: Expression::stringFieldPath('dept'), + ), + salaries: Accumulator::sum( + Expression::numberFieldPath('salary'), + ), + ), + Stage::merge( + into: object( + db: 'reporting', + coll: 'budgets', + ), + on: '_id', + whenMatched: 'replace', + whenNotMatched: 'insert', + ), + ); + + $this->assertSamePipeline(Pipelines::MergeOnDemandMaterializedViewUpdateReplaceData, $pipeline); + } + + public function testOnlyInsertNewData(): void + { + $pipeline = new Pipeline( + Stage::match( + fiscal_year: 2019, + ), + Stage::group( + _id: object( + fiscal_year: Expression::stringFieldPath('fiscal_year'), + dept: Expression::stringFieldPath('dept'), + ), + employees: Accumulator::push( + Expression::numberFieldPath('employee'), + ), + ), + Stage::project( + _id: 0, + dept: Expression::fieldPath('_id.dept'), + fiscal_year: Expression::fieldPath('_id.fiscal_year'), + employees: 1, + ), + Stage::merge( + into: object( + db: 'reporting', + coll: 'orgArchive', + ), + on: ['dept', 'fiscal_year'], + whenMatched: 'fail', + ), + ); + + $this->assertSamePipeline(Pipelines::MergeOnlyInsertNewData, $pipeline); + } + + public function testUseThePipelineToCustomizeTheMerge(): void + { + $pipeline = new Pipeline( + Stage::match( + date: [ + Query::gte(new UTCDateTime(1557187200000)), + Query::lt(new UTCDateTime(1557273600000)), + ], + ), + Stage::project( + _id: Expression::dateToString( + format: '%Y-%m', + date: Expression::dateFieldPath('date'), + ), + thumbsup: 1, + thumbsdown: 1, + ), + Stage::merge( + into: 'monthlytotals', + on: '_id', + whenMatched: new Pipeline( + Stage::addFields( + thumbsup: Expression::add( + Expression::numberFieldPath('thumbsup'), + Expression::variable('new.thumbsup'), + ), + thumbsdown: Expression::add( + Expression::numberFieldPath('thumbsdown'), + Expression::variable('new.thumbsdown'), + ), + ), + ), + whenNotMatched: 'insert', + ), + ); + + $this->assertSamePipeline(Pipelines::MergeUseThePipelineToCustomizeTheMerge, $pipeline); + } + + public function testUseVariablesToCustomizeTheMerge(): void + { + $pipeline = new Pipeline( + Stage::merge( + into: 'cakeSales', + let: object( + year: '2020', + ), + whenMatched: new Pipeline( + Stage::addFields( + salesYear: Expression::variable('year'), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::MergeUseVariablesToCustomizeTheMerge, $pipeline); + } +} diff --git a/tests/Builder/Stage/OutStageTest.php b/tests/Builder/Stage/OutStageTest.php new file mode 100644 index 000000000..a8ec9fb77 --- /dev/null +++ b/tests/Builder/Stage/OutStageTest.php @@ -0,0 +1,54 @@ +assertSamePipeline(Pipelines::OutOutputToADifferentDatabase, $pipeline); + } + + public function testOutputToSameDatabase(): void + { + $pipeline = new Pipeline( + Stage::group( + _id: Expression::stringFieldPath('author'), + books: Accumulator::push( + Expression::stringFieldPath('title'), + ), + ), + Stage::out('authors'), + ); + + $this->assertSamePipeline(Pipelines::OutOutputToSameDatabase, $pipeline); + } +} diff --git a/tests/Builder/Stage/Pipelines.php b/tests/Builder/Stage/Pipelines.php index c671f1c19..77d39e330 100644 --- a/tests/Builder/Stage/Pipelines.php +++ b/tests/Builder/Stage/Pipelines.php @@ -20,10 +20,14 @@ enum Pipelines: string { "$addFields": { "totalHomework": { - "$sum": "$homework" + "$sum": [ + "$homework" + ] }, "totalQuiz": { - "$sum": "$quiz" + "$sum": [ + "$quiz" + ] } } }, @@ -55,4 +59,3223 @@ enum Pipelines: string } ] JSON; + + /** + * Overwriting an existing field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#overwriting-an-existing-field + */ + case AddFieldsOverwritingAnExistingField = <<<'JSON' + [ + { + "$addFields": { + "cats": { + "$numberInt": "20" + } + } + } + ] + JSON; + + /** + * Add Element to an Array + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/#add-element-to-an-array + */ + case AddFieldsAddElementToAnArray = <<<'JSON' + [ + { + "$match": { + "_id": { + "$numberInt": "1" + } + } + }, + { + "$addFields": { + "homework": { + "$concatArrays": [ + "$homework", + [ + { + "$numberInt": "7" + } + ] + ] + } + } + } + ] + JSON; + + /** + * Bucket by Year and Filter by Bucket Results + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucket/#bucket-by-year-and-filter-by-bucket-results + */ + case BucketBucketByYearAndFilterByBucketResults = <<<'JSON' + [ + { + "$bucket": { + "groupBy": "$year_born", + "boundaries": [ + { + "$numberInt": "1840" + }, + { + "$numberInt": "1850" + }, + { + "$numberInt": "1860" + }, + { + "$numberInt": "1870" + }, + { + "$numberInt": "1880" + } + ], + "default": "Other", + "output": { + "count": { + "$sum": { + "$numberInt": "1" + } + }, + "artists": { + "$push": { + "name": { + "$concat": [ + "$first_name", + " ", + "$last_name" + ] + }, + "year_born": "$year_born" + } + } + } + } + }, + { + "$match": { + "count": { + "$gt": { + "$numberInt": "3" + } + } + } + } + ] + JSON; + + /** + * Use $bucket with $facet to Bucket by Multiple Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucket/#use--bucket-with--facet-to-bucket-by-multiple-fields + */ + case BucketUseBucketWithFacetToBucketByMultipleFields = <<<'JSON' + [ + { + "$facet": { + "price": [ + { + "$bucket": { + "groupBy": "$price", + "boundaries": [ + { + "$numberInt": "0" + }, + { + "$numberInt": "200" + }, + { + "$numberInt": "400" + } + ], + "default": "Other", + "output": { + "count": { + "$sum": { + "$numberInt": "1" + } + }, + "artwork": { + "$push": { + "title": "$title", + "price": "$price" + } + }, + "averagePrice": { + "$avg": "$price" + } + } + } + } + ], + "year": [ + { + "$bucket": { + "groupBy": "$year", + "boundaries": [ + { + "$numberInt": "1890" + }, + { + "$numberInt": "1910" + }, + { + "$numberInt": "1920" + }, + { + "$numberInt": "1940" + } + ], + "default": "Unknown", + "output": { + "count": { + "$sum": { + "$numberInt": "1" + } + }, + "artwork": { + "$push": { + "title": "$title", + "year": "$year" + } + } + } + } + } + ] + } + } + ] + JSON; + + /** + * Single Facet Aggregation + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucketAuto/#single-facet-aggregation + */ + case BucketAutoSingleFacetAggregation = <<<'JSON' + [ + { + "$bucketAuto": { + "groupBy": "$price", + "buckets": { + "$numberInt": "4" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/changeStream/#examples + */ + case ChangeStreamExample = <<<'JSON' + [ + { + "$changeStream": {} + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/changeStreamSplitLargeEvent/#example + */ + case ChangeStreamSplitLargeEventExample = <<<'JSON' + [ + { + "$changeStreamSplitLargeEvent": {} + } + ] + JSON; + + /** + * latencyStats Document + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/#latencystats-document + */ + case CollStatsLatencyStatsDocument = <<<'JSON' + [ + { + "$collStats": { + "latencyStats": { + "histograms": true + } + } + } + ] + JSON; + + /** + * storageStats Document + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/#storagestats-document + */ + case CollStatsStorageStatsDocument = <<<'JSON' + [ + { + "$collStats": { + "storageStats": {} + } + } + ] + JSON; + + /** + * count Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/#count-field + */ + case CollStatsCountField = <<<'JSON' + [ + { + "$collStats": { + "count": {} + } + } + ] + JSON; + + /** + * queryExecStats Document + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/#queryexecstats-document + */ + case CollStatsQueryExecStatsDocument = <<<'JSON' + [ + { + "$collStats": { + "queryExecStats": {} + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/count/#example + */ + case CountExample = <<<'JSON' + [ + { + "$match": { + "score": { + "$gt": { + "$numberInt": "80" + } + } + } + }, + { + "$count": "passing_scores" + } + ] + JSON; + + /** + * Inactive Sessions + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/currentOp/#inactive-sessions + */ + case CurrentOpInactiveSessions = <<<'JSON' + [ + { + "$currentOp": { + "allUsers": true, + "idleSessions": true + } + }, + { + "$match": { + "active": false, + "transaction": { + "$exists": true + } + } + } + ] + JSON; + + /** + * Sampled Queries + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/currentOp/#sampled-queries + */ + case CurrentOpSampledQueries = <<<'JSON' + [ + { + "$currentOp": { + "allUsers": true, + "localOps": true + } + }, + { + "$match": { + "desc": "query analyzer" + } + } + ] + JSON; + + /** + * Densify Time Series Data + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/densify/#densify-time-series-data + */ + case DensifyDensifyTimeSeriesData = <<<'JSON' + [ + { + "$densify": { + "field": "timestamp", + "range": { + "step": { + "$numberInt": "1" + }, + "unit": "hour", + "bounds": [ + { + "$date": { + "$numberLong": "1621296000000" + } + }, + { + "$date": { + "$numberLong": "1621324800000" + } + } + ] + } + } + } + ] + JSON; + + /** + * Densifiction with Partitions + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/densify/#densifiction-with-partitions + */ + case DensifyDensifictionWithPartitions = <<<'JSON' + [ + { + "$densify": { + "field": "altitude", + "partitionByFields": [ + "variety" + ], + "range": { + "bounds": "full", + "step": { + "$numberInt": "200" + } + } + } + } + ] + JSON; + + /** + * Test a Pipeline Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/documents/#test-a-pipeline-stage + */ + case DocumentsTestAPipelineStage = <<<'JSON' + [ + { + "$documents": [ + { + "x": { + "$numberInt": "10" + } + }, + { + "x": { + "$numberInt": "2" + } + }, + { + "x": { + "$numberInt": "5" + } + } + ] + }, + { + "$bucketAuto": { + "groupBy": "$x", + "buckets": { + "$numberInt": "4" + } + } + } + ] + JSON; + + /** + * Use a $documents Stage in a $lookup Stage + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/documents/#use-a--documents-stage-in-a--lookup-stage + */ + case DocumentsUseADocumentsStageInALookupStage = <<<'JSON' + [ + { + "$match": {} + }, + { + "$lookup": { + "localField": "zip", + "foreignField": "zip_id", + "as": "city_state", + "pipeline": [ + { + "$documents": [ + { + "zip_id": { + "$numberInt": "94301" + }, + "name": "Palo Alto, CA" + }, + { + "zip_id": { + "$numberInt": "10019" + }, + "name": "New York, NY" + } + ] + } + ] + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/facet/#example + */ + case FacetExample = <<<'JSON' + [ + { + "$facet": { + "categorizedByTags": [ + { + "$unwind": { + "path": "$tags" + } + }, + { + "$sortByCount": "$tags" + } + ], + "categorizedByPrice": [ + { + "$match": { + "price": { + "$exists": true + } + } + }, + { + "$bucket": { + "groupBy": "$price", + "boundaries": [ + { + "$numberInt": "0" + }, + { + "$numberInt": "150" + }, + { + "$numberInt": "200" + }, + { + "$numberInt": "300" + }, + { + "$numberInt": "400" + } + ], + "default": "Other", + "output": { + "count": { + "$sum": { + "$numberInt": "1" + } + }, + "titles": { + "$push": "$title" + } + } + } + } + ], + "categorizedByYears(Auto)": [ + { + "$bucketAuto": { + "groupBy": "$year", + "buckets": { + "$numberInt": "4" + } + } + } + ] + } + } + ] + JSON; + + /** + * Fill Missing Field Values with a Constant Value + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/#fill-missing-field-values-with-a-constant-value + */ + case FillFillMissingFieldValuesWithAConstantValue = <<<'JSON' + [ + { + "$fill": { + "output": { + "bootsSold": { + "value": { + "$numberInt": "0" + } + }, + "sandalsSold": { + "value": { + "$numberInt": "0" + } + }, + "sneakersSold": { + "value": { + "$numberInt": "0" + } + } + } + } + } + ] + JSON; + + /** + * Fill Missing Field Values with Linear Interpolation + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/#fill-missing-field-values-with-linear-interpolation + */ + case FillFillMissingFieldValuesWithLinearInterpolation = <<<'JSON' + [ + { + "$fill": { + "sortBy": { + "time": { + "$numberInt": "1" + } + }, + "output": { + "price": { + "method": "linear" + } + } + } + } + ] + JSON; + + /** + * Fill Missing Field Values Based on the Last Observed Value + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/#fill-missing-field-values-based-on-the-last-observed-value + */ + case FillFillMissingFieldValuesBasedOnTheLastObservedValue = <<<'JSON' + [ + { + "$fill": { + "sortBy": { + "date": { + "$numberInt": "1" + } + }, + "output": { + "score": { + "method": "locf" + } + } + } + } + ] + JSON; + + /** + * Fill Data for Distinct Partitions + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/#fill-data-for-distinct-partitions + */ + case FillFillDataForDistinctPartitions = <<<'JSON' + [ + { + "$fill": { + "sortBy": { + "date": { + "$numberInt": "1" + } + }, + "partitionBy": { + "restaurant": "$restaurant" + }, + "output": { + "score": { + "method": "locf" + } + } + } + } + ] + JSON; + + /** + * Indicate if a Field was Populated Using $fill + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/#indicate-if-a-field-was-populated-using--fill + */ + case FillIndicateIfAFieldWasPopulatedUsingFill = <<<'JSON' + [ + { + "$set": { + "valueExisted": { + "$ifNull": [ + { + "$toBool": { + "$toString": "$score" + } + }, + false + ] + } + } + }, + { + "$fill": { + "sortBy": { + "date": { + "$numberInt": "1" + } + }, + "output": { + "score": { + "method": "locf" + } + } + } + } + ] + JSON; + + /** + * Maximum Distance + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/#maximum-distance + */ + case GeoNearMaximumDistance = <<<'JSON' + [ + { + "$geoNear": { + "near": { + "type": "Point", + "coordinates": [ + { + "$numberDouble": "-73.992789999999999395" + }, + { + "$numberDouble": "40.719295999999999935" + } + ] + }, + "distanceField": "dist.calculated", + "maxDistance": { + "$numberInt": "2" + }, + "query": { + "category": "Parks" + }, + "includeLocs": "dist.location", + "spherical": true + } + } + ] + JSON; + + /** + * Minimum Distance + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/#minimum-distance + */ + case GeoNearMinimumDistance = <<<'JSON' + [ + { + "$geoNear": { + "near": { + "type": "Point", + "coordinates": [ + { + "$numberDouble": "-73.992789999999999395" + }, + { + "$numberDouble": "40.719295999999999935" + } + ] + }, + "distanceField": "dist.calculated", + "minDistance": { + "$numberInt": "2" + }, + "query": { + "category": "Parks" + }, + "includeLocs": "dist.location", + "spherical": true + } + } + ] + JSON; + + /** + * with the let option + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/#-geonear-with-the-let-option + */ + case GeoNearWithTheLetOption = <<<'JSON' + [ + { + "$geoNear": { + "near": "$$pt", + "distanceField": "distance", + "maxDistance": { + "$numberInt": "2" + }, + "query": { + "category": "Parks" + }, + "includeLocs": "dist.location", + "spherical": true + } + } + ] + JSON; + + /** + * with Bound let Option + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/#-geonear-with-bound-let-option + */ + case GeoNearWithBoundLetOption = <<<'JSON' + [ + { + "$lookup": { + "from": "places", + "let": { + "pt": "$location" + }, + "pipeline": [ + { + "$geoNear": { + "near": "$$pt", + "distanceField": "distance" + } + } + ], + "as": "joinedField" + } + }, + { + "$match": { + "name": "Sara D. Roosevelt Park" + } + } + ] + JSON; + + /** + * Specify Which Geospatial Index to Use + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/#specify-which-geospatial-index-to-use + */ + case GeoNearSpecifyWhichGeospatialIndexToUse = <<<'JSON' + [ + { + "$geoNear": { + "near": { + "type": "Point", + "coordinates": [ + { + "$numberDouble": "-73.981419999999999959" + }, + { + "$numberDouble": "40.717820000000003233" + } + ] + }, + "key": "location", + "distanceField": "dist.calculated", + "query": { + "category": "Parks" + } + } + }, + { + "$limit": { + "$numberInt": "5" + } + } + ] + JSON; + + /** + * Within a Single Collection + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/#within-a-single-collection + */ + case GraphLookupWithinASingleCollection = <<<'JSON' + [ + { + "$graphLookup": { + "from": "employees", + "startWith": "$reportsTo", + "connectFromField": "reportsTo", + "connectToField": "name", + "as": "reportingHierarchy" + } + } + ] + JSON; + + /** + * Across Multiple Collections + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/#across-multiple-collections + */ + case GraphLookupAcrossMultipleCollections = <<<'JSON' + [ + { + "$graphLookup": { + "from": "airports", + "startWith": "$nearestAirport", + "connectFromField": "connects", + "connectToField": "airport", + "maxDepth": { + "$numberInt": "2" + }, + "depthField": "numConnections", + "as": "destinations" + } + } + ] + JSON; + + /** + * With a Query Filter + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/#with-a-query-filter + */ + case GraphLookupWithAQueryFilter = <<<'JSON' + [ + { + "$match": { + "name": "Tanya Jordan" + } + }, + { + "$graphLookup": { + "from": "people", + "startWith": "$friends", + "connectFromField": "friends", + "connectToField": "name", + "as": "golfers", + "restrictSearchWithMatch": { + "hobbies": "golf" + } + } + }, + { + "$project": { + "name": { + "$numberInt": "1" + }, + "friends": { + "$numberInt": "1" + }, + "connections who play golf": "$golfers.name" + } + } + ] + JSON; + + /** + * Count the Number of Documents in a Collection + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#count-the-number-of-documents-in-a-collection + */ + case GroupCountTheNumberOfDocumentsInACollection = <<<'JSON' + [ + { + "$group": { + "_id": null, + "count": { + "$count": {} + } + } + } + ] + JSON; + + /** + * Retrieve Distinct Values + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#retrieve-distinct-values + */ + case GroupRetrieveDistinctValues = <<<'JSON' + [ + { + "$group": { + "_id": "$item" + } + } + ] + JSON; + + /** + * Group by Item Having + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#group-by-item-having + */ + case GroupGroupByItemHaving = <<<'JSON' + [ + { + "$group": { + "_id": "$item", + "totalSaleAmount": { + "$sum": { + "$multiply": [ + "$price", + "$quantity" + ] + } + } + } + }, + { + "$match": { + "totalSaleAmount": { + "$gte": { + "$numberInt": "100" + } + } + } + } + ] + JSON; + + /** + * Calculate Count Sum and Average + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#calculate-count--sum--and-average + */ + case GroupCalculateCountSumAndAverage = <<<'JSON' + [ + { + "$match": { + "date": { + "$gte": { + "$date": { + "$numberLong": "1388534400000" + } + }, + "$lt": { + "$date": { + "$numberLong": "1420070400000" + } + } + } + } + }, + { + "$group": { + "_id": { + "$dateToString": { + "format": "%Y-%m-%d", + "date": "$date" + } + }, + "totalSaleAmount": { + "$sum": { + "$multiply": [ + "$price", + "$quantity" + ] + } + }, + "averageQuantity": { + "$avg": "$quantity" + }, + "count": { + "$sum": { + "$numberInt": "1" + } + } + } + }, + { + "$sort": { + "totalSaleAmount": { + "$numberInt": "-1" + } + } + } + ] + JSON; + + /** + * Group by null + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#group-by-null + */ + case GroupGroupByNull = <<<'JSON' + [ + { + "$group": { + "_id": null, + "totalSaleAmount": { + "$sum": { + "$multiply": [ + "$price", + "$quantity" + ] + } + }, + "averageQuantity": { + "$avg": "$quantity" + }, + "count": { + "$sum": { + "$numberInt": "1" + } + } + } + } + ] + JSON; + + /** + * Pivot Data + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#pivot-data + */ + case GroupPivotData = <<<'JSON' + [ + { + "$group": { + "_id": "$author", + "books": { + "$push": "$title" + } + } + } + ] + JSON; + + /** + * Group Documents by author + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#group-documents-by-author + */ + case GroupGroupDocumentsByAuthor = <<<'JSON' + [ + { + "$group": { + "_id": "$author", + "books": { + "$push": "$$ROOT" + } + } + }, + { + "$addFields": { + "totalCopies": { + "$sum": [ + "$books.copies" + ] + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexStats/#example + */ + case IndexStatsExample = <<<'JSON' + [ + { + "$indexStats": {} + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/limit/#example + */ + case LimitExample = <<<'JSON' + [ + { + "$limit": { + "$numberInt": "5" + } + } + ] + JSON; + + /** + * List All Local Sessions + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listLocalSessions/#list-all-local-sessions + */ + case ListLocalSessionsListAllLocalSessions = <<<'JSON' + [ + { + "$listLocalSessions": { + "allUsers": true + } + } + ] + JSON; + + /** + * List All Local Sessions for the Specified Users + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listLocalSessions/#list-all-local-sessions-for-the-specified-users + */ + case ListLocalSessionsListAllLocalSessionsForTheSpecifiedUsers = <<<'JSON' + [ + { + "$listLocalSessions": { + "users": [ + { + "user": "myAppReader", + "db": "test" + } + ] + } + } + ] + JSON; + + /** + * List All Local Sessions for the Current User + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listLocalSessions/#list-all-local-sessions-for-the-current-user + */ + case ListLocalSessionsListAllLocalSessionsForTheCurrentUser = <<<'JSON' + [ + { + "$listLocalSessions": {} + } + ] + JSON; + + /** + * List Sampled Queries for All Collections + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSampledQueries/#list-sampled-queries-for-all-collections + */ + case ListSampledQueriesListSampledQueriesForAllCollections = <<<'JSON' + [ + { + "$listSampledQueries": {} + } + ] + JSON; + + /** + * List Sampled Queries for A Specific Collection + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSampledQueries/#list-sampled-queries-for-a-specific-collection + */ + case ListSampledQueriesListSampledQueriesForASpecificCollection = <<<'JSON' + [ + { + "$listSampledQueries": { + "namespace": "social.post" + } + } + ] + JSON; + + /** + * Return All Search Indexes + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/#return-all-search-indexes + */ + case ListSearchIndexesReturnAllSearchIndexes = <<<'JSON' + [ + { + "$listSearchIndexes": {} + } + ] + JSON; + + /** + * Return a Single Search Index by Name + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/#return-a-single-search-index-by-name + */ + case ListSearchIndexesReturnASingleSearchIndexByName = <<<'JSON' + [ + { + "$listSearchIndexes": { + "name": "synonym-mappings" + } + } + ] + JSON; + + /** + * Return a Single Search Index by id + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/#return-a-single-search-index-by-id + */ + case ListSearchIndexesReturnASingleSearchIndexById = <<<'JSON' + [ + { + "$listSearchIndexes": { + "id": "6524096020da840844a4c4a7" + } + } + ] + JSON; + + /** + * List All Sessions + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSessions/#list-all-sessions + */ + case ListSessionsListAllSessions = <<<'JSON' + [ + { + "$listSessions": { + "allUsers": true + } + } + ] + JSON; + + /** + * List All Sessions for the Specified Users + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSessions/#list-all-sessions-for-the-specified-users + */ + case ListSessionsListAllSessionsForTheSpecifiedUsers = <<<'JSON' + [ + { + "$listSessions": { + "users": [ + { + "user": "myAppReader", + "db": "test" + } + ] + } + } + ] + JSON; + + /** + * List All Sessions for the Current User + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSessions/#list-all-sessions-for-the-current-user + */ + case ListSessionsListAllSessionsForTheCurrentUser = <<<'JSON' + [ + { + "$listSessions": {} + } + ] + JSON; + + /** + * Perform a Single Equality Join with $lookup + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#perform-a-single-equality-join-with--lookup + */ + case LookupPerformASingleEqualityJoinWithLookup = <<<'JSON' + [ + { + "$lookup": { + "from": "inventory", + "localField": "item", + "foreignField": "sku", + "as": "inventory_docs" + } + } + ] + JSON; + + /** + * Use $lookup with an Array + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#use--lookup-with-an-array + */ + case LookupUseLookupWithAnArray = <<<'JSON' + [ + { + "$lookup": { + "from": "members", + "localField": "enrollmentlist", + "foreignField": "name", + "as": "enrollee_info" + } + } + ] + JSON; + + /** + * Use $lookup with $mergeObjects + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#use--lookup-with--mergeobjects + */ + case LookupUseLookupWithMergeObjects = <<<'JSON' + [ + { + "$lookup": { + "from": "items", + "localField": "item", + "foreignField": "item", + "as": "fromItems" + } + }, + { + "$replaceRoot": { + "newRoot": { + "$mergeObjects": [ + { + "$arrayElemAt": [ + "$fromItems", + { + "$numberInt": "0" + } + ] + }, + "$$ROOT" + ] + } + } + }, + { + "$project": { + "fromItems": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Perform Multiple Joins and a Correlated Subquery with $lookup + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#perform-multiple-joins-and-a-correlated-subquery-with--lookup + */ + case LookupPerformMultipleJoinsAndACorrelatedSubqueryWithLookup = <<<'JSON' + [ + { + "$lookup": { + "from": "warehouses", + "let": { + "order_item": "$item", + "order_qty": "$ordered" + }, + "pipeline": [ + { + "$match": { + "$expr": { + "$and": [ + { + "$eq": [ + "$stock_item", + "$$order_item" + ] + }, + { + "$gte": [ + "$instock", + "$$order_qty" + ] + } + ] + } + } + }, + { + "$project": { + "stock_item": { + "$numberInt": "0" + }, + "_id": { + "$numberInt": "0" + } + } + } + ], + "as": "stockdata" + } + } + ] + JSON; + + /** + * Perform an Uncorrelated Subquery with $lookup + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#perform-an-uncorrelated-subquery-with--lookup + */ + case LookupPerformAnUncorrelatedSubqueryWithLookup = <<<'JSON' + [ + { + "$lookup": { + "from": "holidays", + "pipeline": [ + { + "$match": { + "year": { + "$numberInt": "2018" + } + } + }, + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "date": { + "name": "$name", + "date": "$date" + } + } + }, + { + "$replaceRoot": { + "newRoot": "$date" + } + } + ], + "as": "holidays" + } + } + ] + JSON; + + /** + * Perform a Concise Correlated Subquery with $lookup + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/#perform-a-concise-correlated-subquery-with--lookup + */ + case LookupPerformAConciseCorrelatedSubqueryWithLookup = <<<'JSON' + [ + { + "$lookup": { + "from": "restaurants", + "localField": "restaurant_name", + "foreignField": "name", + "let": { + "orders_drink": "$drink" + }, + "pipeline": [ + { + "$match": { + "$expr": { + "$in": [ + "$$orders_drink", + "$beverages" + ] + } + } + } + ], + "as": "matches" + } + } + ] + JSON; + + /** + * Equality Match + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/#equality-match + */ + case MatchEqualityMatch = <<<'JSON' + [ + { + "$match": { + "author": "dave" + } + } + ] + JSON; + + /** + * Perform a Count + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/#perform-a-count + */ + case MatchPerformACount = <<<'JSON' + [ + { + "$match": { + "$or": [ + { + "score": { + "$gt": { + "$numberInt": "70" + }, + "$lt": { + "$numberInt": "90" + } + } + }, + { + "views": { + "$gte": { + "$numberInt": "1000" + } + } + } + ] + } + }, + { + "$group": { + "_id": null, + "count": { + "$sum": { + "$numberInt": "1" + } + } + } + } + ] + JSON; + + /** + * On-Demand Materialized View Initial Creation + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#on-demand-materialized-view--initial-creation + */ + case MergeOnDemandMaterializedViewInitialCreation = <<<'JSON' + [ + { + "$group": { + "_id": { + "fiscal_year": "$fiscal_year", + "dept": "$dept" + }, + "salaries": { + "$sum": "$salary" + } + } + }, + { + "$merge": { + "into": { + "db": "reporting", + "coll": "budgets" + }, + "on": "_id", + "whenMatched": "replace", + "whenNotMatched": "insert" + } + } + ] + JSON; + + /** + * On-Demand Materialized View Update Replace Data + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#on-demand-materialized-view--update-replace-data + */ + case MergeOnDemandMaterializedViewUpdateReplaceData = <<<'JSON' + [ + { + "$match": { + "fiscal_year": { + "$gte": { + "$numberInt": "2019" + } + } + } + }, + { + "$group": { + "_id": { + "fiscal_year": "$fiscal_year", + "dept": "$dept" + }, + "salaries": { + "$sum": "$salary" + } + } + }, + { + "$merge": { + "into": { + "db": "reporting", + "coll": "budgets" + }, + "on": "_id", + "whenMatched": "replace", + "whenNotMatched": "insert" + } + } + ] + JSON; + + /** + * Only Insert New Data + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#only-insert-new-data + */ + case MergeOnlyInsertNewData = <<<'JSON' + [ + { + "$match": { + "fiscal_year": { + "$numberInt": "2019" + } + } + }, + { + "$group": { + "_id": { + "fiscal_year": "$fiscal_year", + "dept": "$dept" + }, + "employees": { + "$push": "$employee" + } + } + }, + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "dept": "$_id.dept", + "fiscal_year": "$_id.fiscal_year", + "employees": { + "$numberInt": "1" + } + } + }, + { + "$merge": { + "into": { + "db": "reporting", + "coll": "orgArchive" + }, + "on": [ + "dept", + "fiscal_year" + ], + "whenMatched": "fail" + } + } + ] + JSON; + + /** + * Merge Results from Multiple Collections + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#merge-results-from-multiple-collections + */ + case MergeMergeResultsFromMultipleCollections = <<<'JSON' + [ + { + "$group": { + "_id": "$quarter", + "purchased": { + "$sum": "$qty" + } + } + }, + { + "$merge": { + "into": "quarterlyreport", + "on": "_id", + "whenMatched": "merge", + "whenNotMatched": "insert" + } + } + ] + JSON; + + /** + * Use the Pipeline to Customize the Merge + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#use-the-pipeline-to-customize-the-merge + */ + case MergeUseThePipelineToCustomizeTheMerge = <<<'JSON' + [ + { + "$match": { + "date": { + "$gte": { + "$date": { + "$numberLong": "1557187200000" + } + }, + "$lt": { + "$date": { + "$numberLong": "1557273600000" + } + } + } + } + }, + { + "$project": { + "_id": { + "$dateToString": { + "format": "%Y-%m", + "date": "$date" + } + }, + "thumbsup": { + "$numberInt": "1" + }, + "thumbsdown": { + "$numberInt": "1" + } + } + }, + { + "$merge": { + "into": "monthlytotals", + "on": "_id", + "whenMatched": [ + { + "$addFields": { + "thumbsup": { + "$add": [ + "$thumbsup", + "$$new.thumbsup" + ] + }, + "thumbsdown": { + "$add": [ + "$thumbsdown", + "$$new.thumbsdown" + ] + } + } + } + ], + "whenNotMatched": "insert" + } + } + ] + JSON; + + /** + * Use Variables to Customize the Merge + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/#use-variables-to-customize-the-merge + */ + case MergeUseVariablesToCustomizeTheMerge = <<<'JSON' + [ + { + "$merge": { + "into": "cakeSales", + "let": { + "year": "2020" + }, + "whenMatched": [ + { + "$addFields": { + "salesYear": "$$year" + } + } + ] + } + } + ] + JSON; + + /** + * Output to Same Database + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/#output-to-same-database + */ + case OutOutputToSameDatabase = <<<'JSON' + [ + { + "$group": { + "_id": "$author", + "books": { + "$push": "$title" + } + } + }, + { + "$out": "authors" + } + ] + JSON; + + /** + * Output to a Different Database + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/#output-to-a-different-database + */ + case OutOutputToADifferentDatabase = <<<'JSON' + [ + { + "$group": { + "_id": "$author", + "books": { + "$push": "$title" + } + } + }, + { + "$out": { + "db": "reporting", + "coll": "authors" + } + } + ] + JSON; + + /** + * Return Information for All Entries in the Query Cache + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/planCacheStats/#return-information-for-all-entries-in-the-query-cache + */ + case PlanCacheStatsReturnInformationForAllEntriesInTheQueryCache = <<<'JSON' + [ + { + "$planCacheStats": {} + } + ] + JSON; + + /** + * Find Cache Entry Details for a Query Hash + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/planCacheStats/#find-cache-entry-details-for-a-query-hash + */ + case PlanCacheStatsFindCacheEntryDetailsForAQueryHash = <<<'JSON' + [ + { + "$planCacheStats": {} + }, + { + "$match": { + "planCacheKey": "B1435201" + } + } + ] + JSON; + + /** + * Include Specific Fields in Output Documents + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#include-specific-fields-in-output-documents + */ + case ProjectIncludeSpecificFieldsInOutputDocuments = <<<'JSON' + [ + { + "$project": { + "title": { + "$numberInt": "1" + }, + "author": { + "$numberInt": "1" + } + } + } + ] + JSON; + + /** + * Suppress id Field in the Output Documents + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#suppress-_id-field-in-the-output-documents + */ + case ProjectSuppressIdFieldInTheOutputDocuments = <<<'JSON' + [ + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "title": { + "$numberInt": "1" + }, + "author": { + "$numberInt": "1" + } + } + } + ] + JSON; + + /** + * Exclude Fields from Output Documents + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#exclude-fields-from-output-documents + */ + case ProjectExcludeFieldsFromOutputDocuments = <<<'JSON' + [ + { + "$project": { + "lastModified": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Exclude Fields from Embedded Documents + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#exclude-fields-from-embedded-documents + */ + case ProjectExcludeFieldsFromEmbeddedDocuments = <<<'JSON' + [ + { + "$project": { + "author.first": { + "$numberInt": "0" + }, + "lastModified": { + "$numberInt": "0" + } + } + }, + { + "$project": { + "author": { + "first": { + "$numberInt": "0" + } + }, + "lastModified": { + "$numberInt": "0" + } + } + } + ] + JSON; + + /** + * Conditionally Exclude Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#conditionally-exclude-fields + */ + case ProjectConditionallyExcludeFields = <<<'JSON' + [ + { + "$project": { + "title": { + "$numberInt": "1" + }, + "author.first": { + "$numberInt": "1" + }, + "author.last": { + "$numberInt": "1" + }, + "author.middle": { + "$cond": { + "if": { + "$eq": [ + "", + "$author.middle" + ] + }, + "then": "$$REMOVE", + "else": "$author.middle" + } + } + } + } + ] + JSON; + + /** + * Include Specific Fields from Embedded Documents + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#include-specific-fields-from-embedded-documents + */ + case ProjectIncludeSpecificFieldsFromEmbeddedDocuments = <<<'JSON' + [ + { + "$project": { + "stop.title": { + "$numberInt": "1" + } + } + }, + { + "$project": { + "stop": { + "title": { + "$numberInt": "1" + } + } + } + } + ] + JSON; + + /** + * Include Computed Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#include-computed-fields + */ + case ProjectIncludeComputedFields = <<<'JSON' + [ + { + "$project": { + "title": { + "$numberInt": "1" + }, + "isbn": { + "prefix": { + "$substr": [ + "$isbn", + { + "$numberInt": "0" + }, + { + "$numberInt": "3" + } + ] + }, + "group": { + "$substr": [ + "$isbn", + { + "$numberInt": "3" + }, + { + "$numberInt": "2" + } + ] + }, + "publisher": { + "$substr": [ + "$isbn", + { + "$numberInt": "5" + }, + { + "$numberInt": "4" + } + ] + }, + "title": { + "$substr": [ + "$isbn", + { + "$numberInt": "9" + }, + { + "$numberInt": "3" + } + ] + }, + "checkDigit": { + "$substr": [ + "$isbn", + { + "$numberInt": "12" + }, + { + "$numberInt": "1" + } + ] + } + }, + "lastName": "$author.last", + "copiesSold": "$copies" + } + } + ] + JSON; + + /** + * Project New Array Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#project-new-array-fields + */ + case ProjectProjectNewArrayFields = <<<'JSON' + [ + { + "$project": { + "myArray": [ + "$x", + "$y" + ] + } + } + ] + JSON; + + /** + * Evaluate Access at Every Document Level + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/#evaluate-access-at-every-document-level + */ + case RedactEvaluateAccessAtEveryDocumentLevel = <<<'JSON' + [ + { + "$match": { + "year": { + "$numberInt": "2014" + } + } + }, + { + "$redact": { + "$cond": { + "if": { + "$gt": [ + { + "$size": { + "$setIntersection": [ + "$tags", + [ + "STLW", + "G" + ] + ] + } + }, + { + "$numberInt": "0" + } + ] + }, + "then": "$$DESCEND", + "else": "$$PRUNE" + } + } + } + ] + JSON; + + /** + * Exclude All Fields at a Given Level + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/#exclude-all-fields-at-a-given-level + */ + case RedactExcludeAllFieldsAtAGivenLevel = <<<'JSON' + [ + { + "$match": { + "status": "A" + } + }, + { + "$redact": { + "$cond": { + "if": { + "$eq": [ + "$level", + { + "$numberInt": "5" + } + ] + }, + "then": "$$PRUNE", + "else": "$$DESCEND" + } + } + } + ] + JSON; + + /** + * with an Embedded Document Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/#-replaceroot-with-an-embedded-document-field + */ + case ReplaceRootWithAnEmbeddedDocumentField = <<<'JSON' + [ + { + "$replaceRoot": { + "newRoot": { + "$mergeObjects": [ + { + "dogs": { + "$numberInt": "0" + }, + "cats": { + "$numberInt": "0" + }, + "birds": { + "$numberInt": "0" + }, + "fish": { + "$numberInt": "0" + } + }, + "$pets" + ] + } + } + } + ] + JSON; + + /** + * with a Document Nested in an Array + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/#-replaceroot-with-a-document-nested-in-an-array + */ + case ReplaceRootWithADocumentNestedInAnArray = <<<'JSON' + [ + { + "$unwind": { + "path": "$grades" + } + }, + { + "$match": { + "grades.grade": { + "$gte": { + "$numberInt": "90" + } + } + } + }, + { + "$replaceRoot": { + "newRoot": "$grades" + } + } + ] + JSON; + + /** + * with a newly created document + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/#-replaceroot-with-a-newly-created-document + */ + case ReplaceRootWithANewlyCreatedDocument = <<<'JSON' + [ + { + "$replaceRoot": { + "newRoot": { + "full_name": { + "$concat": [ + "$first_name", + " ", + "$last_name" + ] + } + } + } + } + ] + JSON; + + /** + * with a New Document Created from $$ROOT and a Default Document + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/#-replaceroot-with-a-new-document-created-from---root-and-a-default-document + */ + case ReplaceRootWithANewDocumentCreatedFromROOTAndADefaultDocument = <<<'JSON' + [ + { + "$replaceRoot": { + "newRoot": { + "$mergeObjects": [ + { + "_id": "", + "name": "", + "email": "", + "cell": "", + "home": "" + }, + "$$ROOT" + ] + } + } + } + ] + JSON; + + /** + * an Embedded Document Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/#-replacewith-an-embedded-document-field + */ + case ReplaceWithAnEmbeddedDocumentField = <<<'JSON' + [ + { + "$replaceWith": { + "$mergeObjects": [ + { + "dogs": { + "$numberInt": "0" + }, + "cats": { + "$numberInt": "0" + }, + "birds": { + "$numberInt": "0" + }, + "fish": { + "$numberInt": "0" + } + }, + "$pets" + ] + } + } + ] + JSON; + + /** + * a Document Nested in an Array + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/#-replacewith-a-document-nested-in-an-array + */ + case ReplaceWithADocumentNestedInAnArray = <<<'JSON' + [ + { + "$unwind": { + "path": "$grades" + } + }, + { + "$match": { + "grades.grade": { + "$gte": { + "$numberInt": "90" + } + } + } + }, + { + "$replaceWith": "$grades" + } + ] + JSON; + + /** + * a Newly Created Document + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/#-replacewith-a-newly-created-document + */ + case ReplaceWithANewlyCreatedDocument = <<<'JSON' + [ + { + "$match": { + "status": "C" + } + }, + { + "$replaceWith": { + "_id": "$_id", + "item": "$item", + "amount": { + "$multiply": [ + "$price", + "$quantity" + ] + }, + "status": "Complete", + "asofDate": "$$NOW" + } + } + ] + JSON; + + /** + * a New Document Created from $$ROOT and a Default Document + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/#-replacewith-a-new-document-created-from---root-and-a-default-document + */ + case ReplaceWithANewDocumentCreatedFromROOTAndADefaultDocument = <<<'JSON' + [ + { + "$replaceWith": { + "$mergeObjects": [ + { + "_id": "", + "name": "", + "email": "", + "cell": "", + "home": "" + }, + "$$ROOT" + ] + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/#example + */ + case SampleExample = <<<'JSON' + [ + { + "$sample": { + "size": { + "$numberInt": "3" + } + } + } + ] + JSON; + + /** + * Using Two $set Stages + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/#using-two--set-stages + */ + case SetUsingTwoSetStages = <<<'JSON' + [ + { + "$set": { + "totalHomework": { + "$sum": [ + "$homework" + ] + }, + "totalQuiz": { + "$sum": [ + "$quiz" + ] + } + } + }, + { + "$set": { + "totalScore": { + "$add": [ + "$totalHomework", + "$totalQuiz", + "$extraCredit" + ] + } + } + } + ] + JSON; + + /** + * Adding Fields to an Embedded Document + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/#adding-fields-to-an-embedded-document + */ + case SetAddingFieldsToAnEmbeddedDocument = <<<'JSON' + [ + { + "$set": { + "specs.fuel_type": "unleaded" + } + } + ] + JSON; + + /** + * Overwriting an existing field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/#overwriting-an-existing-field + */ + case SetOverwritingAnExistingField = <<<'JSON' + [ + { + "$set": { + "cats": { + "$numberInt": "20" + } + } + } + ] + JSON; + + /** + * Add Element to an Array + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/#add-element-to-an-array + */ + case SetAddElementToAnArray = <<<'JSON' + [ + { + "$match": { + "_id": { + "$numberInt": "1" + } + } + }, + { + "$set": { + "homework": { + "$concatArrays": [ + "$homework", + [ + { + "$numberInt": "7" + } + ] + ] + } + } + } + ] + JSON; + + /** + * Creating a New Field with Existing Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/#creating-a-new-field-with-existing-fields + */ + case SetCreatingANewFieldWithExistingFields = <<<'JSON' + [ + { + "$set": { + "quizAverage": { + "$avg": [ + "$quiz" + ] + } + } + } + ] + JSON; + + /** + * Use Documents Window to Obtain Cumulative Quantity for Each State + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-documents-window-to-obtain-cumulative-quantity-for-each-state + */ + case SetWindowFieldsUseDocumentsWindowToObtainCumulativeQuantityForEachState = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "cumulativeQuantityForState": { + "$sum": "$quantity", + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } + } + } + } + ] + JSON; + + /** + * Use Documents Window to Obtain Cumulative Quantity for Each Year + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-documents-window-to-obtain-cumulative-quantity-for-each-year + */ + case SetWindowFieldsUseDocumentsWindowToObtainCumulativeQuantityForEachYear = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": { + "$year": { + "date": "$orderDate" + } + }, + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "cumulativeQuantityForYear": { + "$sum": "$quantity", + "window": { + "documents": [ + "unbounded", + "current" + ] + } + } + } + } + } + ] + JSON; + + /** + * Use Documents Window to Obtain Moving Average Quantity for Each Year + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-documents-window-to-obtain-moving-average-quantity-for-each-year + */ + case SetWindowFieldsUseDocumentsWindowToObtainMovingAverageQuantityForEachYear = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": { + "$year": { + "date": "$orderDate" + } + }, + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "averageQuantity": { + "$avg": "$quantity", + "window": { + "documents": [ + { + "$numberInt": "-1" + }, + { + "$numberInt": "0" + } + ] + } + } + } + } + } + ] + JSON; + + /** + * Use Documents Window to Obtain Cumulative and Maximum Quantity for Each Year + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-documents-window-to-obtain-cumulative-and-maximum-quantity-for-each-year + */ + case SetWindowFieldsUseDocumentsWindowToObtainCumulativeAndMaximumQuantityForEachYear = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": { + "$year": { + "date": "$orderDate" + } + }, + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "cumulativeQuantityForYear": { + "$sum": "$quantity", + "window": { + "documents": [ + "unbounded", + "current" + ] + } + }, + "maximumQuantityForYear": { + "$max": "$quantity", + "window": { + "documents": [ + "unbounded", + "unbounded" + ] + } + } + } + } + } + ] + JSON; + + /** + * Range Window Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#range-window-example + */ + case SetWindowFieldsRangeWindowExample = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "price": { + "$numberInt": "1" + } + }, + "output": { + "quantityFromSimilarOrders": { + "$sum": "$quantity", + "window": { + "range": [ + { + "$numberInt": "-10" + }, + { + "$numberInt": "10" + } + ] + } + } + } + } + } + ] + JSON; + + /** + * Use a Time Range Window with a Positive Upper Bound + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-a-time-range-window-with-a-positive-upper-bound + */ + case SetWindowFieldsUseATimeRangeWindowWithAPositiveUpperBound = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "recentOrders": { + "$push": "$orderDate", + "window": { + "range": [ + "unbounded", + { + "$numberInt": "10" + } + ], + "unit": "month" + } + } + } + } + } + ] + JSON; + + /** + * Use a Time Range Window with a Negative Upper Bound + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/#use-a-time-range-window-with-a-negative-upper-bound + */ + case SetWindowFieldsUseATimeRangeWindowWithANegativeUpperBound = <<<'JSON' + [ + { + "$setWindowFields": { + "partitionBy": "$state", + "sortBy": { + "orderDate": { + "$numberInt": "1" + } + }, + "output": { + "recentOrders": { + "$push": "$orderDate", + "window": { + "range": [ + "unbounded", + { + "$numberInt": "-10" + } + ], + "unit": "month" + } + } + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/shardedDataDistribution/#examples + */ + case ShardedDataDistributionExample = <<<'JSON' + [ + { + "$shardedDataDistribution": {} + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/skip/#example + */ + case SkipExample = <<<'JSON' + [ + { + "$skip": { + "$numberInt": "5" + } + } + ] + JSON; + + /** + * Ascending Descending Sort + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/#ascending-descending-sort + */ + case SortAscendingDescendingSort = <<<'JSON' + [ + { + "$sort": { + "age": { + "$numberInt": "-1" + }, + "posts": { + "$numberInt": "1" + } + } + } + ] + JSON; + + /** + * Text Score Metadata Sort + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/#text-score-metadata-sort + */ + case SortTextScoreMetadataSort = <<<'JSON' + [ + { + "$match": { + "$text": { + "$search": "operating" + } + } + }, + { + "$sort": { + "score": { + "$meta": "textScore" + }, + "posts": { + "$numberInt": "-1" + } + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortByCount/#example + */ + case SortByCountExample = <<<'JSON' + [ + { + "$unwind": { + "path": "$tags" + } + }, + { + "$sortByCount": "$tags" + } + ] + JSON; + + /** + * Report 1 All Sales by Year and Stores and Items + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unionWith/#report-1--all-sales-by-year-and-stores-and-items + */ + case UnionWithReport1AllSalesByYearAndStoresAndItems = <<<'JSON' + [ + { + "$set": { + "_id": "2017" + } + }, + { + "$unionWith": { + "coll": "sales_2018", + "pipeline": [ + { + "$set": { + "_id": "2018" + } + } + ] + } + }, + { + "$unionWith": { + "coll": "sales_2019", + "pipeline": [ + { + "$set": { + "_id": "2019" + } + } + ] + } + }, + { + "$unionWith": { + "coll": "sales_2020", + "pipeline": [ + { + "$set": { + "_id": "2020" + } + } + ] + } + }, + { + "$sort": { + "_id": { + "$numberInt": "1" + }, + "store": { + "$numberInt": "1" + }, + "item": { + "$numberInt": "1" + } + } + } + ] + JSON; + + /** + * Report 2 Aggregated Sales by Items + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unionWith/#report-2--aggregated-sales-by-items + */ + case UnionWithReport2AggregatedSalesByItems = <<<'JSON' + [ + { + "$unionWith": { + "coll": "sales_2018" + } + }, + { + "$unionWith": { + "coll": "sales_2019" + } + }, + { + "$unionWith": { + "coll": "sales_2020" + } + }, + { + "$group": { + "_id": "$item", + "total": { + "$sum": "$quantity" + } + } + }, + { + "$sort": { + "total": { + "$numberInt": "-1" + } + } + } + ] + JSON; + + /** + * Remove a Single Field + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/#remove-a-single-field + */ + case UnsetRemoveASingleField = <<<'JSON' + [ + { + "$unset": [ + "copies" + ] + } + ] + JSON; + + /** + * Remove Top-Level Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/#remove-top-level-fields + */ + case UnsetRemoveTopLevelFields = <<<'JSON' + [ + { + "$unset": [ + "isbn", + "copies" + ] + } + ] + JSON; + + /** + * Remove Embedded Fields + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/#remove-embedded-fields + */ + case UnsetRemoveEmbeddedFields = <<<'JSON' + [ + { + "$unset": [ + "isbn", + "author.first", + "copies.warehouse" + ] + } + ] + JSON; + + /** + * Unwind Array + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#unwind-array + */ + case UnwindUnwindArray = <<<'JSON' + [ + { + "$unwind": { + "path": "$sizes" + } + } + ] + JSON; + + /** + * preserveNullAndEmptyArrays + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#preservenullandemptyarrays + */ + case UnwindPreserveNullAndEmptyArrays = <<<'JSON' + [ + { + "$unwind": { + "path": "$sizes", + "preserveNullAndEmptyArrays": true + } + } + ] + JSON; + + /** + * includeArrayIndex + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#includearrayindex + */ + case UnwindIncludeArrayIndex = <<<'JSON' + [ + { + "$unwind": { + "path": "$sizes", + "includeArrayIndex": "arrayIndex" + } + } + ] + JSON; + + /** + * Group by Unwound Values + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#group-by-unwound-values + */ + case UnwindGroupByUnwoundValues = <<<'JSON' + [ + { + "$unwind": { + "path": "$sizes", + "preserveNullAndEmptyArrays": true + } + }, + { + "$group": { + "_id": "$sizes", + "averagePrice": { + "$avg": "$price" + } + } + }, + { + "$sort": { + "averagePrice": { + "$numberInt": "-1" + } + } + } + ] + JSON; + + /** + * Unwind Embedded Arrays + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/#unwind-embedded-arrays + */ + case UnwindUnwindEmbeddedArrays = <<<'JSON' + [ + { + "$unwind": { + "path": "$items" + } + }, + { + "$unwind": { + "path": "$items.tags" + } + }, + { + "$group": { + "_id": "$items.tags", + "totalSalesAmount": { + "$sum": { + "$multiply": [ + "$items.price", + "$items.quantity" + ] + } + } + } + } + ] + JSON; } diff --git a/tests/Builder/Stage/PlanCacheStatsStageTest.php b/tests/Builder/Stage/PlanCacheStatsStageTest.php new file mode 100644 index 000000000..4816eeafd --- /dev/null +++ b/tests/Builder/Stage/PlanCacheStatsStageTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::PlanCacheStatsFindCacheEntryDetailsForAQueryHash, $pipeline); + } + + public function testReturnInformationForAllEntriesInTheQueryCache(): void + { + $pipeline = new Pipeline( + Stage::planCacheStats(), + ); + + $this->assertSamePipeline(Pipelines::PlanCacheStatsReturnInformationForAllEntriesInTheQueryCache, $pipeline); + } +} diff --git a/tests/Builder/Stage/ProjectStageTest.php b/tests/Builder/Stage/ProjectStageTest.php new file mode 100644 index 000000000..9e30d731e --- /dev/null +++ b/tests/Builder/Stage/ProjectStageTest.php @@ -0,0 +1,164 @@ + 1, + 'author.last' => 1, + 'author.middle' => Expression::cond( + if: Expression::eq( + '', + Expression::stringFieldPath('author.middle'), + ), + then: Expression::variable('REMOVE'), + else: Expression::stringFieldPath('author.middle'), + ), + ], + title: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::ProjectConditionallyExcludeFields, $pipeline); + } + + public function testExcludeFieldsFromEmbeddedDocuments(): void + { + $pipeline = new Pipeline( + // Both stages are equivalents + Stage::project( + ...['author.first' => 0], + ...['lastModified' => 0], + ), + Stage::project( + author: object(first: 0), + lastModified: 0, + ), + ); + + $this->assertSamePipeline(Pipelines::ProjectExcludeFieldsFromEmbeddedDocuments, $pipeline); + } + + public function testExcludeFieldsFromOutputDocuments(): void + { + $pipeline = new Pipeline( + Stage::project( + lastModified: 0, + ), + ); + + $this->assertSamePipeline(Pipelines::ProjectExcludeFieldsFromOutputDocuments, $pipeline); + } + + public function testIncludeComputedFields(): void + { + $pipeline = new Pipeline( + Stage::project( + title: 1, + isbn: object( + prefix: Expression::substr( + Expression::stringFieldPath('isbn'), + 0, + 3, + ), + group: Expression::substr( + Expression::stringFieldPath('isbn'), + 3, + 2, + ), + publisher: Expression::substr( + Expression::stringFieldPath('isbn'), + 5, + 4, + ), + title: Expression::substr( + Expression::stringFieldPath('isbn'), + 9, + 3, + ), + checkDigit: Expression::substr( + Expression::stringFieldPath('isbn'), + 12, + 1, + ), + ), + lastName: Expression::stringFieldPath('author.last'), + copiesSold: Expression::intFieldPath('copies'), + ), + ); + + $this->assertSamePipeline(Pipelines::ProjectIncludeComputedFields, $pipeline); + } + + public function testIncludeSpecificFieldsFromEmbeddedDocuments(): void + { + $pipeline = new Pipeline( + Stage::project( + ...['stop.title' => 1], + ), + Stage::project( + stop: object(title: 1), + ), + ); + + $this->assertSamePipeline(Pipelines::ProjectIncludeSpecificFieldsFromEmbeddedDocuments, $pipeline); + } + + public function testIncludeSpecificFieldsInOutputDocuments(): void + { + $pipeline = new Pipeline( + Stage::project( + title: 1, + author: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::ProjectIncludeSpecificFieldsInOutputDocuments, $pipeline); + } + + public function testProjectNewArrayFields(): void + { + $pipeline = new Pipeline( + Stage::project( + myArray: [ + Expression::fieldPath('x'), + Expression::fieldPath('y'), + ], + ), + ); + + $this->assertSamePipeline(Pipelines::ProjectProjectNewArrayFields, $pipeline); + } + + public function testSuppressIdFieldInTheOutputDocuments(): void + { + $pipeline = new Pipeline( + Stage::project( + _id: 0, + title: 1, + author: 1, + ), + ); + + $this->assertSamePipeline(Pipelines::ProjectSuppressIdFieldInTheOutputDocuments, $pipeline); + } +} diff --git a/tests/Builder/Stage/RedactStageTest.php b/tests/Builder/Stage/RedactStageTest.php new file mode 100644 index 000000000..867ca5919 --- /dev/null +++ b/tests/Builder/Stage/RedactStageTest.php @@ -0,0 +1,63 @@ +assertSamePipeline(Pipelines::RedactEvaluateAccessAtEveryDocumentLevel, $pipeline); + } + + public function testExcludeAllFieldsAtAGivenLevel(): void + { + $pipeline = new Pipeline( + Stage::match( + status: 'A', + ), + Stage::redact( + Expression::cond( + if: Expression::eq( + Expression::intFieldPath('level'), + 5, + ), + then: Expression::variable('PRUNE'), + else: Expression::variable('DESCEND'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::RedactExcludeAllFieldsAtAGivenLevel, $pipeline); + } +} diff --git a/tests/Builder/Stage/ReplaceRootStageTest.php b/tests/Builder/Stage/ReplaceRootStageTest.php new file mode 100644 index 000000000..436bde704 --- /dev/null +++ b/tests/Builder/Stage/ReplaceRootStageTest.php @@ -0,0 +1,77 @@ + Query::gte(90)], + ), + Stage::replaceRoot(Expression::objectFieldPath('grades')), + ); + + $this->assertSamePipeline(Pipelines::ReplaceRootWithADocumentNestedInAnArray, $pipeline); + } + + public function testWithANewDocumentCreatedFromROOTAndADefaultDocument(): void + { + $pipeline = new Pipeline( + Stage::replaceRoot( + Expression::mergeObjects( + object(_id: '', name: '', email: '', cell: '', home: ''), + Expression::variable('ROOT'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReplaceRootWithANewDocumentCreatedFromROOTAndADefaultDocument, $pipeline); + } + + public function testWithANewlyCreatedDocument(): void + { + $pipeline = new Pipeline( + Stage::replaceRoot( + object( + full_name: Expression::concat( + Expression::stringFieldPath('first_name'), + ' ', + Expression::stringFieldPath('last_name'), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReplaceRootWithANewlyCreatedDocument, $pipeline); + } + + public function testWithAnEmbeddedDocumentField(): void + { + $pipeline = new Pipeline( + Stage::replaceRoot( + Expression::mergeObjects( + object(dogs: 0, cats: 0, birds: 0, fish: 0), + Expression::objectFieldPath('pets'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReplaceRootWithAnEmbeddedDocumentField, $pipeline); + } +} diff --git a/tests/Builder/Stage/ReplaceWithStageTest.php b/tests/Builder/Stage/ReplaceWithStageTest.php new file mode 100644 index 000000000..ad0edfbc5 --- /dev/null +++ b/tests/Builder/Stage/ReplaceWithStageTest.php @@ -0,0 +1,83 @@ + Query::gte(90)], + ), + Stage::replaceWith(Expression::objectFieldPath('grades')), + ); + + $this->assertSamePipeline(Pipelines::ReplaceWithADocumentNestedInAnArray, $pipeline); + } + + public function testANewDocumentCreatedFromROOTAndADefaultDocument(): void + { + $pipeline = new Pipeline( + Stage::replaceWith( + Expression::mergeObjects( + object(_id: '', name: '', email: '', cell: '', home: ''), + Expression::variable('ROOT'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReplaceWithANewDocumentCreatedFromROOTAndADefaultDocument, $pipeline); + } + + public function testANewlyCreatedDocument(): void + { + $pipeline = new Pipeline( + Stage::match( + status: 'C', + ), + Stage::replaceWith( + object( + _id: Expression::objectFieldPath('_id'), + item: Expression::fieldPath('item'), + amount: Expression::multiply( + Expression::numberFieldPath('price'), + Expression::numberFieldPath('quantity'), + ), + status: 'Complete', + asofDate: Expression::variable('NOW'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReplaceWithANewlyCreatedDocument, $pipeline); + } + + public function testAnEmbeddedDocumentField(): void + { + $pipeline = new Pipeline( + Stage::replaceWith( + Expression::mergeObjects( + object(dogs: 0, cats: 0, birds: 0, fish: 0), + Expression::objectFieldPath('pets'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReplaceWithAnEmbeddedDocumentField, $pipeline); + } +} diff --git a/tests/Builder/Stage/SampleStageTest.php b/tests/Builder/Stage/SampleStageTest.php new file mode 100644 index 000000000..5ac8efd4a --- /dev/null +++ b/tests/Builder/Stage/SampleStageTest.php @@ -0,0 +1,24 @@ +assertSamePipeline(Pipelines::SampleExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/SetStageTest.php b/tests/Builder/Stage/SetStageTest.php new file mode 100644 index 000000000..64193b8b9 --- /dev/null +++ b/tests/Builder/Stage/SetStageTest.php @@ -0,0 +1,89 @@ +assertSamePipeline(Pipelines::SetAddElementToAnArray, $pipeline); + } + + public function testAddingFieldsToAnEmbeddedDocument(): void + { + $pipeline = new Pipeline( + Stage::set( + ...['specs.fuel_type' => 'unleaded'], + ), + ); + + $this->assertSamePipeline(Pipelines::SetAddingFieldsToAnEmbeddedDocument, $pipeline); + } + + public function testCreatingANewFieldWithExistingFields(): void + { + $pipeline = new Pipeline( + Stage::set( + quizAverage: Expression::avg( + Expression::numberFieldPath('quiz'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetCreatingANewFieldWithExistingFields, $pipeline); + } + + public function testOverwritingAnExistingField(): void + { + $pipeline = new Pipeline( + Stage::set(cats: 20), + ); + + $this->assertSamePipeline(Pipelines::SetOverwritingAnExistingField, $pipeline); + } + + public function testUsingTwoSetStages(): void + { + $pipeline = new Pipeline( + Stage::set( + totalHomework: Expression::sum( + Expression::arrayFieldPath('homework'), + ), + totalQuiz: Expression::sum( + Expression::arrayFieldPath('quiz'), + ), + ), + Stage::set( + totalScore: Expression::add( + Expression::numberFieldPath('totalHomework'), + Expression::numberFieldPath('totalQuiz'), + Expression::numberFieldPath('extraCredit'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetUsingTwoSetStages, $pipeline); + } +} diff --git a/tests/Builder/Stage/SetWindowFieldsStageTest.php b/tests/Builder/Stage/SetWindowFieldsStageTest.php new file mode 100644 index 000000000..f14c94992 --- /dev/null +++ b/tests/Builder/Stage/SetWindowFieldsStageTest.php @@ -0,0 +1,173 @@ +assertSamePipeline(Pipelines::SetWindowFieldsRangeWindowExample, $pipeline); + } + + public function testUseATimeRangeWindowWithANegativeUpperBound(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::stringFieldPath('state'), + sortBy: object(orderDate: 1), + output: object( + recentOrders: Accumulator::outputWindow( + Accumulator::push( + Expression::dateFieldPath('orderDate'), + ), + range: ['unbounded', -10], + unit: 'month', + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetWindowFieldsUseATimeRangeWindowWithANegativeUpperBound, $pipeline); + } + + public function testUseATimeRangeWindowWithAPositiveUpperBound(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::stringFieldPath('state'), + sortBy: object(orderDate: 1), + output: object( + recentOrders: Accumulator::outputWindow( + Accumulator::push( + Expression::dateFieldPath('orderDate'), + ), + range: ['unbounded', 10], + unit: 'month', + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetWindowFieldsUseATimeRangeWindowWithAPositiveUpperBound, $pipeline); + } + + public function testUseDocumentsWindowToObtainCumulativeAndMaximumQuantityForEachYear(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::year( + Expression::dateFieldPath('orderDate'), + ), + sortBy: object(orderDate: 1), + output: object( + cumulativeQuantityForYear: Accumulator::outputWindow( + Accumulator::sum( + Expression::numberFieldPath('quantity'), + ), + documents: ['unbounded', 'current'], + ), + maximumQuantityForYear: Accumulator::outputWindow( + Accumulator::max( + Expression::numberFieldPath('quantity'), + ), + documents: ['unbounded', 'unbounded'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetWindowFieldsUseDocumentsWindowToObtainCumulativeAndMaximumQuantityForEachYear, $pipeline); + } + + public function testUseDocumentsWindowToObtainCumulativeQuantityForEachState(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::stringFieldPath('state'), + sortBy: object(orderDate: 1), + output: object( + cumulativeQuantityForState: Accumulator::outputWindow( + Accumulator::sum( + Expression::numberFieldPath('quantity'), + ), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetWindowFieldsUseDocumentsWindowToObtainCumulativeQuantityForEachState, $pipeline); + } + + public function testUseDocumentsWindowToObtainCumulativeQuantityForEachYear(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::year( + Expression::dateFieldPath('orderDate'), + ), + sortBy: object(orderDate: 1), + output: object( + cumulativeQuantityForYear: Accumulator::outputWindow( + Accumulator::sum( + Expression::numberFieldPath('quantity'), + ), + documents: ['unbounded', 'current'], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetWindowFieldsUseDocumentsWindowToObtainCumulativeQuantityForEachYear, $pipeline); + } + + public function testUseDocumentsWindowToObtainMovingAverageQuantityForEachYear(): void + { + $pipeline = new Pipeline( + Stage::setWindowFields( + partitionBy: Expression::year( + Expression::dateFieldPath('orderDate'), + ), + sortBy: object(orderDate: 1), + output: object( + averageQuantity: Accumulator::outputWindow( + Accumulator::avg( + Expression::numberFieldPath('quantity'), + ), + documents: [-1, 0], + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SetWindowFieldsUseDocumentsWindowToObtainMovingAverageQuantityForEachYear, $pipeline); + } +} diff --git a/tests/Builder/Stage/ShardedDataDistributionStageTest.php b/tests/Builder/Stage/ShardedDataDistributionStageTest.php new file mode 100644 index 000000000..f9c0db9bc --- /dev/null +++ b/tests/Builder/Stage/ShardedDataDistributionStageTest.php @@ -0,0 +1,24 @@ +assertSamePipeline(Pipelines::ShardedDataDistributionExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/SkipStageTest.php b/tests/Builder/Stage/SkipStageTest.php new file mode 100644 index 000000000..4a716bea1 --- /dev/null +++ b/tests/Builder/Stage/SkipStageTest.php @@ -0,0 +1,24 @@ +assertSamePipeline(Pipelines::SkipExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/SortByCountStageTest.php b/tests/Builder/Stage/SortByCountStageTest.php new file mode 100644 index 000000000..d1a0393a9 --- /dev/null +++ b/tests/Builder/Stage/SortByCountStageTest.php @@ -0,0 +1,30 @@ +assertSamePipeline(Pipelines::SortByCountExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/SortStageTest.php b/tests/Builder/Stage/SortStageTest.php new file mode 100644 index 000000000..2b9a093d6 --- /dev/null +++ b/tests/Builder/Stage/SortStageTest.php @@ -0,0 +1,49 @@ +assertSamePipeline(Pipelines::SortAscendingDescendingSort, $pipeline); + } + + public function testTextScoreMetadataSort(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::text('operating'), + ), + Stage::sort( + object( + score: ['$meta' => 'textScore'], + posts: -1, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SortTextScoreMetadataSort, $pipeline); + } +} diff --git a/tests/Builder/Stage/UnionWithStageTest.php b/tests/Builder/Stage/UnionWithStageTest.php new file mode 100644 index 000000000..dc6ebcb89 --- /dev/null +++ b/tests/Builder/Stage/UnionWithStageTest.php @@ -0,0 +1,83 @@ +assertSamePipeline(Pipelines::UnionWithReport1AllSalesByYearAndStoresAndItems, $pipeline); + } + + public function testReport2AggregatedSalesByItems(): void + { + $pipeline = new Pipeline( + Stage::unionWith('sales_2018'), + Stage::unionWith('sales_2019'), + Stage::unionWith('sales_2020'), + Stage::group( + _id: Expression::stringFieldPath('item'), + total: Accumulator::sum( + Expression::numberFieldPath('quantity'), + ), + ), + Stage::sort( + object( + total: -1, + ), + ), + ); + + $this->assertSamePipeline(Pipelines::UnionWithReport2AggregatedSalesByItems, $pipeline); + } +} diff --git a/tests/Builder/Stage/UnsetStageTest.php b/tests/Builder/Stage/UnsetStageTest.php new file mode 100644 index 000000000..217172743 --- /dev/null +++ b/tests/Builder/Stage/UnsetStageTest.php @@ -0,0 +1,49 @@ +assertSamePipeline(Pipelines::UnsetRemoveASingleField, $pipeline); + } + + public function testRemoveEmbeddedFields(): void + { + $pipeline = new Pipeline( + Stage::unset( + 'isbn', + 'author.first', + 'copies.warehouse', + ), + ); + + $this->assertSamePipeline(Pipelines::UnsetRemoveEmbeddedFields, $pipeline); + } + + public function testRemoveTopLevelFields(): void + { + $pipeline = new Pipeline( + Stage::unset( + 'isbn', + 'copies', + ), + ); + + $this->assertSamePipeline(Pipelines::UnsetRemoveTopLevelFields, $pipeline); + } +} diff --git a/tests/Builder/Stage/UnwindStageTest.php b/tests/Builder/Stage/UnwindStageTest.php new file mode 100644 index 000000000..c572c0240 --- /dev/null +++ b/tests/Builder/Stage/UnwindStageTest.php @@ -0,0 +1,94 @@ +assertSamePipeline(Pipelines::UnwindGroupByUnwoundValues, $pipeline); + } + + public function testIncludeArrayIndex(): void + { + $pipeline = new Pipeline( + Stage::unwind( + path: Expression::arrayFieldPath('sizes'), + includeArrayIndex: 'arrayIndex', + ), + ); + + $this->assertSamePipeline(Pipelines::UnwindIncludeArrayIndex, $pipeline); + } + + public function testPreserveNullAndEmptyArrays(): void + { + $pipeline = new Pipeline( + Stage::unwind( + path: Expression::arrayFieldPath('sizes'), + preserveNullAndEmptyArrays: true, + ), + ); + + $this->assertSamePipeline(Pipelines::UnwindPreserveNullAndEmptyArrays, $pipeline); + } + + public function testUnwindArray(): void + { + $pipeline = new Pipeline( + Stage::unwind(Expression::arrayFieldPath('sizes')), + ); + + $this->assertSamePipeline(Pipelines::UnwindUnwindArray, $pipeline); + } + + public function testUnwindEmbeddedArrays(): void + { + $pipeline = new Pipeline( + Stage::unwind(Expression::arrayFieldPath('items')), + Stage::unwind(Expression::arrayFieldPath('items.tags')), + Stage::group( + _id: Expression::fieldPath('items.tags'), + totalSalesAmount: Accumulator::sum( + Expression::multiply( + Expression::numberFieldPath('items.price'), + Expression::numberFieldPath('items.quantity'), + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::UnwindUnwindEmbeddedArrays, $pipeline); + } +} From 214baf1c9fb34656029902c2f4291da0c71ee703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 1 Feb 2024 15:40:51 +0100 Subject: [PATCH 55/95] PHPLIB-1355 Add tests to $meta expression (#59) --- generator/config/expression/meta.yaml | 26 ++++++++++ tests/Builder/Expression/MetaOperatorTest.php | 49 ++++++++++++++++++ tests/Builder/Expression/Pipelines.php | 51 +++++++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 tests/Builder/Expression/MetaOperatorTest.php diff --git a/generator/config/expression/meta.yaml b/generator/config/expression/meta.yaml index a85fc8a62..9a96f7aaf 100644 --- a/generator/config/expression/meta.yaml +++ b/generator/config/expression/meta.yaml @@ -11,3 +11,29 @@ arguments: name: keyword type: - string +tests: + - + name: 'textScore' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/#-meta---textscore-' + pipeline: + - + $match: + $text: + $search: 'cake' + - + $group: + _id: + $meta: 'textScore' + count: + $sum: 1 + - + name: 'indexKey' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/#-meta---indexkey-' + pipeline: + - + $match: + type: 'apparel' + - + $addFields: + idxKey: + $meta: 'indexKey' diff --git a/tests/Builder/Expression/MetaOperatorTest.php b/tests/Builder/Expression/MetaOperatorTest.php new file mode 100644 index 000000000..b8bc3d933 --- /dev/null +++ b/tests/Builder/Expression/MetaOperatorTest.php @@ -0,0 +1,49 @@ +assertSamePipeline(Pipelines::MetaIndexKey, $pipeline); + } + + public function testTextScore(): void + { + $pipeline = new Pipeline( + Stage::match( + Query::text( + search: 'cake', + ), + ), + Stage::group( + _id: Expression::meta('textScore'), + count: Accumulator::sum(1), + ), + ); + + $this->assertSamePipeline(Pipelines::MetaTextScore, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index 8bfde10a6..f5a4bcec1 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -3111,6 +3111,57 @@ enum Pipelines: string ] JSON; + /** + * textScore + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/#-meta---textscore- + */ + case MetaTextScore = <<<'JSON' + [ + { + "$match": { + "$text": { + "$search": "cake" + } + } + }, + { + "$group": { + "_id": { + "$meta": "textScore" + }, + "count": { + "$sum": { + "$numberInt": "1" + } + } + } + } + ] + JSON; + + /** + * indexKey + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/#-meta---indexkey- + */ + case MetaIndexKey = <<<'JSON' + [ + { + "$match": { + "type": "apparel" + } + }, + { + "$addFields": { + "idxKey": { + "$meta": "indexKey" + } + } + } + ] + JSON; + /** * Example * From 592b0a0a1c60b3e412c1cfa91da8262c7a83fd72 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 2 Feb 2024 11:31:15 +0100 Subject: [PATCH 56/95] PHPLIB-1384: Use default value in Query::exists() (#60) * PHPLIB-1384: Use default value in Query::exists() * Remove default value from exists() calls --- generator/config/query/exists.yaml | 8 ++++++++ src/Builder/Query/ExistsOperator.php | 2 +- src/Builder/Query/FactoryTrait.php | 2 +- tests/Builder/Query/AndOperatorTest.php | 2 +- tests/Builder/Query/ExistsOperatorTest.php | 17 +++++++++++++---- tests/Builder/Query/Pipelines.php | 13 +++++++++++++ tests/Builder/Stage/CurrentOpStageTest.php | 2 +- tests/Builder/Stage/FacetStageTest.php | 2 +- 8 files changed, 39 insertions(+), 9 deletions(-) diff --git a/generator/config/query/exists.yaml b/generator/config/query/exists.yaml index c1124ca3f..00d7ce2cb 100644 --- a/generator/config/query/exists.yaml +++ b/generator/config/query/exists.yaml @@ -11,6 +11,7 @@ arguments: name: exists type: - bool + default: true tests: - name: 'Exists and Not Equal To' @@ -29,3 +30,10 @@ tests: $match: qty: $exists: true + - + name: 'Missing Field' + pipeline: + - + $match: + qty: + $exists: false diff --git a/src/Builder/Query/ExistsOperator.php b/src/Builder/Query/ExistsOperator.php index 050d1b0ed..92c04f066 100644 --- a/src/Builder/Query/ExistsOperator.php +++ b/src/Builder/Query/ExistsOperator.php @@ -27,7 +27,7 @@ class ExistsOperator implements FieldQueryInterface, OperatorInterface /** * @param bool $exists */ - public function __construct(bool $exists) + public function __construct(bool $exists = true) { $this->exists = $exists; } diff --git a/src/Builder/Query/FactoryTrait.php b/src/Builder/Query/FactoryTrait.php index fc8cb80af..0a096b572 100644 --- a/src/Builder/Query/FactoryTrait.php +++ b/src/Builder/Query/FactoryTrait.php @@ -175,7 +175,7 @@ public static function eq(Type|stdClass|array|bool|float|int|null|string $value) * @see https://www.mongodb.com/docs/manual/reference/operator/query/exists/ * @param bool $exists */ - public static function exists(bool $exists): ExistsOperator + public static function exists(bool $exists = true): ExistsOperator { return new ExistsOperator($exists); } diff --git a/tests/Builder/Query/AndOperatorTest.php b/tests/Builder/Query/AndOperatorTest.php index 32446faae..778aa3f97 100644 --- a/tests/Builder/Query/AndOperatorTest.php +++ b/tests/Builder/Query/AndOperatorTest.php @@ -23,7 +23,7 @@ public function testANDQueriesWithMultipleExpressionsSpecifyingTheSameField(): v price: Query::ne(1.99), ), Query::query( - price: Query::exists(true), + price: Query::exists(), ), ), ), diff --git a/tests/Builder/Query/ExistsOperatorTest.php b/tests/Builder/Query/ExistsOperatorTest.php index 33366162c..27ebeec77 100644 --- a/tests/Builder/Query/ExistsOperatorTest.php +++ b/tests/Builder/Query/ExistsOperatorTest.php @@ -19,7 +19,7 @@ public function testExistsAndNotEqualTo(): void $pipeline = new Pipeline( Stage::match( qty: [ - Query::exists(true), + Query::exists(), Query::nin([5, 15]), ], ), @@ -28,13 +28,22 @@ public function testExistsAndNotEqualTo(): void $this->assertSamePipeline(Pipelines::ExistsExistsAndNotEqualTo, $pipeline); } + public function testMissingField(): void + { + $pipeline = new Pipeline( + Stage::match( + qty: Query::exists(false), + ), + ); + + $this->assertSamePipeline(Pipelines::ExistsMissingField, $pipeline); + } + public function testNullValues(): void { $pipeline = new Pipeline( Stage::match( - qty: [ - Query::exists(true), - ], + qty: Query::exists(), ), ); diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index d72636a54..8394e1e65 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -696,6 +696,19 @@ enum Pipelines: string ] JSON; + /** Missing Field */ + case ExistsMissingField = <<<'JSON' + [ + { + "$match": { + "qty": { + "$exists": false + } + } + } + ] + JSON; + /** * Compare Two Fields from A Single Document * diff --git a/tests/Builder/Stage/CurrentOpStageTest.php b/tests/Builder/Stage/CurrentOpStageTest.php index e1370a23e..6e50cb0b9 100644 --- a/tests/Builder/Stage/CurrentOpStageTest.php +++ b/tests/Builder/Stage/CurrentOpStageTest.php @@ -23,7 +23,7 @@ public function testInactiveSessions(): void ), Stage::match( active: false, - transaction: Query::exists(true), + transaction: Query::exists(), ), ); diff --git a/tests/Builder/Stage/FacetStageTest.php b/tests/Builder/Stage/FacetStageTest.php index 52ad2c428..121131617 100644 --- a/tests/Builder/Stage/FacetStageTest.php +++ b/tests/Builder/Stage/FacetStageTest.php @@ -40,7 +40,7 @@ public function testExample(): void ), categorizedByPrice: new Pipeline( Stage::match( - price: Query::exists(true), + price: Query::exists(), ), Stage::bucket( groupBy: Expression::numberFieldPath('price'), From 040833d519596e446d2e5ac4d312eb45334fac06 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 2 Feb 2024 13:28:58 +0100 Subject: [PATCH 57/95] Add tests for $search and $searchMeta stages (#61) --- generator/config/stage/search.yaml | 27 +++++++ generator/config/stage/searchMeta.yaml | 14 ++++ tests/Builder/Stage/Pipelines.php | 84 +++++++++++++++++++++ tests/Builder/Stage/SearchMetaStageTest.php | 33 ++++++++ tests/Builder/Stage/SearchStageTest.php | 44 +++++++++++ 5 files changed, 202 insertions(+) create mode 100644 tests/Builder/Stage/SearchMetaStageTest.php create mode 100644 tests/Builder/Stage/SearchStageTest.php diff --git a/generator/config/stage/search.yaml b/generator/config/stage/search.yaml index 54b053425..2531e75f6 100644 --- a/generator/config/stage/search.yaml +++ b/generator/config/stage/search.yaml @@ -12,3 +12,30 @@ arguments: name: search type: - object + +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/query-syntax/#aggregation-variable' + pipeline: + - + $search: + near: + path: 'released' + origin: !bson_utcdatetime '2011-09-01T00:00:00.000+00:00' + pivot: 7776000000 + - + $project: + _id: 0 + title: 1 + released: 1 + - + $limit: 5 + - + $facet: + docs: [] + meta: + - + $replaceWith: '$$SEARCH_META' + - + $limit: 1 diff --git a/generator/config/stage/searchMeta.yaml b/generator/config/stage/searchMeta.yaml index 80d28c11f..322d048eb 100644 --- a/generator/config/stage/searchMeta.yaml +++ b/generator/config/stage/searchMeta.yaml @@ -12,3 +12,17 @@ arguments: name: meta type: - object + +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/atlas/atlas-search/query-syntax/#example' + pipeline: + - + $searchMeta: + range: + path: 'year' + gte: 1998 + lt: 1999 + count: + type: 'total' diff --git a/tests/Builder/Stage/Pipelines.php b/tests/Builder/Stage/Pipelines.php index 77d39e330..8b76342ba 100644 --- a/tests/Builder/Stage/Pipelines.php +++ b/tests/Builder/Stage/Pipelines.php @@ -2554,6 +2554,90 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/atlas/atlas-search/query-syntax/#aggregation-variable + */ + case SearchExample = <<<'JSON' + [ + { + "$search": { + "near": { + "path": "released", + "origin": { + "$date": { + "$numberLong": "1314835200000" + } + }, + "pivot": { + "$numberLong": "7776000000" + } + } + } + }, + { + "$project": { + "_id": { + "$numberInt": "0" + }, + "title": { + "$numberInt": "1" + }, + "released": { + "$numberInt": "1" + } + } + }, + { + "$limit": { + "$numberInt": "5" + } + }, + { + "$facet": { + "docs": [], + "meta": [ + { + "$replaceWith": "$$SEARCH_META" + }, + { + "$limit": { + "$numberInt": "1" + } + } + ] + } + } + ] + JSON; + + /** + * Example + * + * @see https://www.mongodb.com/docs/atlas/atlas-search/query-syntax/#example + */ + case SearchMetaExample = <<<'JSON' + [ + { + "$searchMeta": { + "range": { + "path": "year", + "gte": { + "$numberInt": "1998" + }, + "lt": { + "$numberInt": "1999" + } + }, + "count": { + "type": "total" + } + } + } + ] + JSON; + /** * Using Two $set Stages * diff --git a/tests/Builder/Stage/SearchMetaStageTest.php b/tests/Builder/Stage/SearchMetaStageTest.php new file mode 100644 index 000000000..3b8283424 --- /dev/null +++ b/tests/Builder/Stage/SearchMetaStageTest.php @@ -0,0 +1,33 @@ +assertSamePipeline(Pipelines::SearchMetaExample, $pipeline); + } +} diff --git a/tests/Builder/Stage/SearchStageTest.php b/tests/Builder/Stage/SearchStageTest.php new file mode 100644 index 000000000..656b9b9ee --- /dev/null +++ b/tests/Builder/Stage/SearchStageTest.php @@ -0,0 +1,44 @@ +assertSamePipeline(Pipelines::SearchExample, $pipeline); + } +} From ceac70dc15b1f78bffc62469ec1db435f695adc9 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 2 Feb 2024 15:32:36 +0100 Subject: [PATCH 58/95] PHPLIB-1273: Improve $switch syntax (#62) * PHPLIB-1273: Improve $switch syntax * Restore previously deleted encoding type in schema * Introduce dedicated interface for branches in $switch * Remove optional parameter in encodeAsObject * Use case operator throughout tests --- generator/config/expression/case.yaml | 21 ++++++++ generator/config/expression/switch.yaml | 2 +- generator/config/expressions.php | 4 ++ generator/config/schema.json | 2 + .../src/Definition/OperatorDefinition.php | 1 + src/Builder/Encoder/OperatorEncoder.php | 5 +- src/Builder/Expression/CaseOperator.php | 49 +++++++++++++++++++ src/Builder/Expression/FactoryTrait.php | 15 ++++++ src/Builder/Type/Encode.php | 5 ++ src/Builder/Type/SwitchBranchInterface.php | 14 ++++++ .../Expression/ConvertOperatorTest.php | 6 +-- .../Expression/IsNumberOperatorTest.php | 12 ++--- .../Builder/Expression/SwitchOperatorTest.php | 8 ++- .../Builder/Expression/ToBoolOperatorTest.php | 6 +-- 14 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 generator/config/expression/case.yaml create mode 100644 src/Builder/Expression/CaseOperator.php create mode 100644 src/Builder/Type/SwitchBranchInterface.php diff --git a/generator/config/expression/case.yaml b/generator/config/expression/case.yaml new file mode 100644 index 000000000..b86aef878 --- /dev/null +++ b/generator/config/expression/case.yaml @@ -0,0 +1,21 @@ +# $schema: ../schema.json +name: $case +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/switch/' +type: + - switchBranch +encode: flat_object +description: | + Represents a single case in a $switch expression +arguments: + - + name: case + type: + - resolvesToBool + description: | + Can be any valid expression that resolves to a boolean. If the result is not a boolean, it is coerced to a boolean value. More information about how MongoDB evaluates expressions as either true or false can be found here. + - + name: then + type: + - expression + description: | + Can be any valid expression. diff --git a/generator/config/expression/switch.yaml b/generator/config/expression/switch.yaml index e7b13061b..d668e3d94 100644 --- a/generator/config/expression/switch.yaml +++ b/generator/config/expression/switch.yaml @@ -10,7 +10,7 @@ arguments: - name: branches type: - - array # of object{case:resolvesToBool, then:expression} + - array # of CaseOperator description: | An array of control branch documents. Each branch is a document with the following fields: - case Can be any valid expression that resolves to a boolean. If the result is not a boolean, it is coerced to a boolean value. More information about how MongoDB evaluates expressions as either true or false can be found here. diff --git a/generator/config/expressions.php b/generator/config/expressions.php index 76e54fead..974efb46a 100644 --- a/generator/config/expressions.php +++ b/generator/config/expressions.php @@ -113,6 +113,10 @@ 'returnType' => Type\GeometryInterface::class, 'acceptedTypes' => [Type\GeometryInterface::class, ...$bsonTypes['object']], ], + 'switchBranch' => [ + 'returnType' => Type\SwitchBranchInterface::class, + 'acceptedTypes' => [Type\SwitchBranchInterface::class, ...$bsonTypes['object']], + ], // @todo add enum values 'Granularity' => [ diff --git a/generator/config/schema.json b/generator/config/schema.json index b48b65a98..82a2d5e30 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -29,6 +29,7 @@ "filter", "window", "geometry", + "switchBranch", "resolvesToAny", "resolvesToNumber", "resolvesToDouble", @@ -59,6 +60,7 @@ "enum": [ "array", "object", + "flat_object", "dollar_object", "single", "group" diff --git a/generator/src/Definition/OperatorDefinition.php b/generator/src/Definition/OperatorDefinition.php index 50dd3db28..ce39fe4d7 100644 --- a/generator/src/Definition/OperatorDefinition.php +++ b/generator/src/Definition/OperatorDefinition.php @@ -39,6 +39,7 @@ public function __construct( 'single' => Encode::Single, 'array' => Encode::Array, 'object' => Encode::Object, + 'flat_object' => Encode::FlatObject, 'dollar_object' => Encode::DollarObject, 'group' => Encode::Group, default => throw new UnexpectedValueException(sprintf('Unexpected "encode" value for operator "%s". Got "%s"', $name, $encode)), diff --git a/src/Builder/Encoder/OperatorEncoder.php b/src/Builder/Encoder/OperatorEncoder.php index 4561f22cb..9c4d1d7ab 100644 --- a/src/Builder/Encoder/OperatorEncoder.php +++ b/src/Builder/Encoder/OperatorEncoder.php @@ -46,6 +46,7 @@ public function encode(mixed $value): stdClass return $this->encodeAsArray($value); case Encode::Object: + case Encode::FlatObject: return $this->encodeAsObject($value); case Encode::DollarObject: @@ -107,7 +108,9 @@ private function encodeAsObject(OperatorInterface $value): stdClass $result->{$key} = $this->recursiveEncode($val); } - return $this->wrap($value, $result); + return $value::ENCODE === Encode::FlatObject + ? $result + : $this->wrap($value, $result); } private function encodeAsDollarObject(OperatorInterface $value): stdClass diff --git a/src/Builder/Expression/CaseOperator.php b/src/Builder/Expression/CaseOperator.php new file mode 100644 index 000000000..463a3b741 --- /dev/null +++ b/src/Builder/Expression/CaseOperator.php @@ -0,0 +1,49 @@ +case = $case; + $this->then = $then; + } + + public function getOperator(): string + { + return '$case'; + } +} diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index dbc0a59c0..19fcd539b 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -306,6 +306,21 @@ public static function bsonSize( return new BsonSizeOperator($object); } + /** + * Represents a single case in a $switch expression + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/switch/ + * @param ResolvesToBool|bool $case Can be any valid expression that resolves to a boolean. If the result is not a boolean, it is coerced to a boolean value. More information about how MongoDB evaluates expressions as either true or false can be found here. + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $then Can be any valid expression. + */ + public static function case( + ResolvesToBool|bool $case, + Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $then, + ): CaseOperator + { + return new CaseOperator($case, $then); + } + /** * Returns the smallest integer greater than or equal to the specified number. * diff --git a/src/Builder/Type/Encode.php b/src/Builder/Type/Encode.php index c42b15806..85a5eaf66 100644 --- a/src/Builder/Type/Encode.php +++ b/src/Builder/Type/Encode.php @@ -23,6 +23,11 @@ enum Encode */ case Object; + /** + * Same as Object, but only parameters are returned. The operator name will not be used. + */ + case FlatObject; + /** * Parameters are encoded as an object with keys matching the parameter names prefixed with a dollar sign ($) */ diff --git a/src/Builder/Type/SwitchBranchInterface.php b/src/Builder/Type/SwitchBranchInterface.php new file mode 100644 index 000000000..65c8a88a0 --- /dev/null +++ b/src/Builder/Type/SwitchBranchInterface.php @@ -0,0 +1,14 @@ + Date: Thu, 8 Feb 2024 17:31:34 +0100 Subject: [PATCH 59/95] PHPLIB-1381 PHPLIB-1269 Add enum for Sort and TimeUnit (#63) --- generator/config/accumulator/derivative.yaml | 2 +- generator/config/accumulator/integral.yaml | 2 +- generator/config/expression/dateAdd.yaml | 2 +- generator/config/expression/dateDiff.yaml | 2 +- generator/config/expression/dateSubtract.yaml | 2 +- generator/config/expression/dateTrunc.yaml | 2 +- generator/config/expression/sortArray.yaml | 1 + generator/config/expressions.php | 8 +++++ generator/config/query/text.yaml | 2 ++ generator/config/schema.json | 2 ++ generator/config/stage/sort.yaml | 4 ++- psalm-baseline.xml | 10 +++++- src/Builder/Accumulator.php | 3 +- .../Accumulator/DerivativeAccumulator.php | 10 +++--- src/Builder/Accumulator/FactoryTrait.php | 9 +++--- .../Accumulator/IntegralAccumulator.php | 9 +++--- src/Builder/BuilderEncoder.php | 9 ++++-- .../Encoder/AbstractExpressionEncoder.php | 2 +- src/Builder/Encoder/DictionaryEncoder.php | 31 +++++++++++++++++++ src/Builder/Encoder/ExpressionEncoder.php | 2 +- src/Builder/Expression/DateAddOperator.php | 9 +++--- src/Builder/Expression/DateDiffOperator.php | 9 +++--- .../Expression/DateSubtractOperator.php | 9 +++--- src/Builder/Expression/DateTruncOperator.php | 9 +++--- src/Builder/Expression/FactoryTrait.php | 22 +++++++------ src/Builder/Expression/SortArrayOperator.php | 9 +++--- src/Builder/Stage/FactoryTrait.php | 9 ++++-- src/Builder/Stage/SortStage.php | 27 ++++++++++++---- src/Builder/Type/DictionaryInterface.php | 10 ++++++ src/Builder/Type/OutputWindow.php | 2 +- src/Builder/Type/Sort.php | 26 ++++++++++++++++ src/Builder/Type/TimeUnit.php | 26 ++++++++++++++++ .../Accumulator/AddToSetAccumulatorTest.php | 3 +- .../Accumulator/AvgAccumulatorTest.php | 3 +- .../Accumulator/BottomAccumulatorTest.php | 5 +-- .../Accumulator/BottomNAccumulatorTest.php | 7 +++-- .../Accumulator/CountAccumulatorTest.php | 3 +- .../CovariancePopAccumulatorTest.php | 3 +- .../CovarianceSampAccumulatorTest.php | 3 +- .../Accumulator/DenseRankAccumulatorTest.php | 5 +-- .../Accumulator/DerivativeAccumulatorTest.php | 8 +++-- .../DocumentNumberAccumulatorTest.php | 3 +- .../ExpMovingAvgAccumulatorTest.php | 5 +-- .../Accumulator/FirstAccumulatorTest.php | 11 ++++--- .../Accumulator/FirstNAccumulatorTest.php | 5 ++- .../Accumulator/IntegralAccumulatorTest.php | 8 +++-- .../Accumulator/LastAccumulatorTest.php | 11 ++++--- .../Accumulator/LastNAccumulatorTest.php | 9 +++--- .../Accumulator/LinearFillAccumulatorTest.php | 5 +-- .../Accumulator/LocfAccumulatorTest.php | 3 +- .../Accumulator/MaxAccumulatorTest.php | 3 +- .../Accumulator/MedianAccumulatorTest.php | 3 +- .../Accumulator/MinAccumulatorTest.php | 3 +- .../Accumulator/PercentileAccumulatorTest.php | 3 +- .../Accumulator/PushAccumulatorTest.php | 9 +++--- .../Accumulator/RankAccumulatorTest.php | 5 +-- .../Accumulator/ShiftAccumulatorTest.php | 5 +-- .../Accumulator/StdDevPopAccumulatorTest.php | 3 +- .../Accumulator/StdDevSampAccumulatorTest.php | 3 +- .../Accumulator/SumAccumulatorTest.php | 3 +- .../Accumulator/TopAccumulatorTest.php | 5 +-- .../Accumulator/TopNAccumulatorTest.php | 7 +++-- tests/Builder/BuilderEncoderTest.php | 8 +++-- .../Expression/BsonSizeOperatorTest.php | 5 ++- .../Expression/DateAddOperatorTest.php | 13 ++++---- .../Expression/DateDiffOperatorTest.php | 15 ++++----- .../Expression/DateSubtractOperatorTest.php | 13 ++++---- .../Expression/DateTruncOperatorTest.php | 5 +-- .../Expression/SortArrayOperatorTest.php | 14 ++++----- .../Builder/Expression/SplitOperatorTest.php | 5 ++- .../Builder/Expression/ToDateOperatorTest.php | 7 ++--- .../Builder/Expression/ToLongOperatorTest.php | 7 ++--- .../Expression/ToObjectIdOperatorTest.php | 7 ++--- .../Expression/ToStringOperatorTest.php | 7 ++--- tests/Builder/Query/Pipelines.php | 6 +++- tests/Builder/Query/TextOperatorTest.php | 12 +++---- tests/Builder/Stage/DensifyStageTest.php | 3 +- tests/Builder/Stage/FillStageTest.php | 9 +++--- tests/Builder/Stage/GroupStageTest.php | 7 ++--- .../Stage/SetWindowFieldsStageTest.php | 20 ++++++------ tests/Builder/Stage/SortStageTest.php | 15 +++------ tests/Builder/Stage/UnionWithStageTest.php | 15 +++------ tests/Builder/Stage/UnwindStageTest.php | 7 ++--- tests/Builder/Type/OutputWindowTest.php | 5 +-- 84 files changed, 400 insertions(+), 238 deletions(-) create mode 100644 src/Builder/Encoder/DictionaryEncoder.php create mode 100644 src/Builder/Type/DictionaryInterface.php create mode 100644 src/Builder/Type/Sort.php create mode 100644 src/Builder/Type/TimeUnit.php diff --git a/generator/config/accumulator/derivative.yaml b/generator/config/accumulator/derivative.yaml index 90c2ee0ea..5745e9380 100644 --- a/generator/config/accumulator/derivative.yaml +++ b/generator/config/accumulator/derivative.yaml @@ -16,7 +16,7 @@ arguments: - name: unit type: - - string + - timeUnit optional: true description: | A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". diff --git a/generator/config/accumulator/integral.yaml b/generator/config/accumulator/integral.yaml index 6c84548d1..efc803597 100644 --- a/generator/config/accumulator/integral.yaml +++ b/generator/config/accumulator/integral.yaml @@ -16,7 +16,7 @@ arguments: - name: unit type: - - resolvesToString + - timeUnit optional: true description: | A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". diff --git a/generator/config/expression/dateAdd.yaml b/generator/config/expression/dateAdd.yaml index 19ef3ce51..c7d85d571 100644 --- a/generator/config/expression/dateAdd.yaml +++ b/generator/config/expression/dateAdd.yaml @@ -18,7 +18,7 @@ arguments: - name: unit type: - - resolvesToString + - timeUnit description: | The unit used to measure the amount of time added to the startDate. - diff --git a/generator/config/expression/dateDiff.yaml b/generator/config/expression/dateDiff.yaml index d2b86a58c..42cb55d15 100644 --- a/generator/config/expression/dateDiff.yaml +++ b/generator/config/expression/dateDiff.yaml @@ -26,7 +26,7 @@ arguments: - name: unit type: - - resolvesToString + - timeUnit description: | The time measurement unit between the startDate and endDate - diff --git a/generator/config/expression/dateSubtract.yaml b/generator/config/expression/dateSubtract.yaml index 1fd3cd144..e463fe8f8 100644 --- a/generator/config/expression/dateSubtract.yaml +++ b/generator/config/expression/dateSubtract.yaml @@ -18,7 +18,7 @@ arguments: - name: unit type: - - resolvesToString + - timeUnit description: | The unit used to measure the amount of time added to the startDate. - diff --git a/generator/config/expression/dateTrunc.yaml b/generator/config/expression/dateTrunc.yaml index 0ac6af68e..aa3dcd6ca 100644 --- a/generator/config/expression/dateTrunc.yaml +++ b/generator/config/expression/dateTrunc.yaml @@ -18,7 +18,7 @@ arguments: - name: unit type: - - resolvesToString + - timeUnit description: | The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. Together, binSize and unit specify the time period used in the $dateTrunc calculation. diff --git a/generator/config/expression/sortArray.yaml b/generator/config/expression/sortArray.yaml index febd73807..962ebf1ce 100644 --- a/generator/config/expression/sortArray.yaml +++ b/generator/config/expression/sortArray.yaml @@ -20,6 +20,7 @@ arguments: type: - object # SortSpec - int + - sortSpec description: | The document specifies a sort ordering. tests: diff --git a/generator/config/expressions.php b/generator/config/expressions.php index 974efb46a..f731234b2 100644 --- a/generator/config/expressions.php +++ b/generator/config/expressions.php @@ -117,6 +117,14 @@ 'returnType' => Type\SwitchBranchInterface::class, 'acceptedTypes' => [Type\SwitchBranchInterface::class, ...$bsonTypes['object']], ], + 'timeUnit' => [ + 'returnType' => Type\TimeUnit::class, + 'acceptedTypes' => [Type\TimeUnit::class, ResolvesToString::class, ...$bsonTypes['string']], + ], + 'sortSpec' => [ + 'returnType' => Type\Sort::class, + 'acceptedTypes' => [Type\Sort::class], + ], // @todo add enum values 'Granularity' => [ diff --git a/generator/config/query/text.yaml b/generator/config/query/text.yaml index a2709737e..5ec26aaec 100644 --- a/generator/config/query/text.yaml +++ b/generator/config/query/text.yaml @@ -102,6 +102,8 @@ tests: $text: $search: 'CAFÉ' $diacriticSensitive: true + - + $project: score: $meta: 'textScore' - diff --git a/generator/config/schema.json b/generator/config/schema.json index 82a2d5e30..a68564e8e 100644 --- a/generator/config/schema.json +++ b/generator/config/schema.json @@ -115,6 +115,8 @@ "expression", "geometry", "fieldPath", + "timeUnit", + "sortSpec", "any", "resolvesToNumber", "numberFieldPath", "number", "resolvesToDouble", "doubleFieldPath", "double", diff --git a/generator/config/stage/sort.yaml b/generator/config/stage/sort.yaml index 0cc7e02e0..d35e23b63 100644 --- a/generator/config/stage/sort.yaml +++ b/generator/config/stage/sort.yaml @@ -10,7 +10,9 @@ arguments: - name: sort type: - - object # SortSpec + - expression + - sortSpec + variadic: object tests: - name: 'Ascending Descending Sort' diff --git a/psalm-baseline.xml b/psalm-baseline.xml index a8793bf11..64790c9f0 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + $val @@ -113,6 +113,14 @@ stdClass + + + $sort + + + stdClass + + diff --git a/src/Builder/Accumulator.php b/src/Builder/Accumulator.php index 9bdb87456..75d460bd1 100644 --- a/src/Builder/Accumulator.php +++ b/src/Builder/Accumulator.php @@ -8,6 +8,7 @@ use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\OutputWindow; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Builder\Type\WindowInterface; use stdClass; @@ -31,7 +32,7 @@ public static function outputWindow( Document|Serializable|WindowInterface|stdClass|array $operator, Optional|array $documents = Optional::Undefined, Optional|array $range = Optional::Undefined, - Optional|string $unit = Optional::Undefined, + Optional|TimeUnit|string $unit = Optional::Undefined, ): OutputWindow { return new OutputWindow($operator, $documents, $range, $unit); } diff --git a/src/Builder/Accumulator/DerivativeAccumulator.php b/src/Builder/Accumulator/DerivativeAccumulator.php index cd109536a..8010050d9 100644 --- a/src/Builder/Accumulator/DerivativeAccumulator.php +++ b/src/Builder/Accumulator/DerivativeAccumulator.php @@ -13,9 +13,11 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Expression\ResolvesToDate; use MongoDB\Builder\Expression\ResolvesToNumber; +use MongoDB\Builder\Expression\ResolvesToString; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Builder\Type\WindowInterface; /** @@ -32,19 +34,19 @@ class DerivativeAccumulator implements WindowInterface, OperatorInterface public readonly Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input; /** - * @var Optional|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @var Optional|ResolvesToString|TimeUnit|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ - public readonly Optional|string $unit; + public readonly Optional|ResolvesToString|TimeUnit|string $unit; /** * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input - * @param Optional|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @param Optional|ResolvesToString|TimeUnit|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ public function __construct( Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input, - Optional|string $unit = Optional::Undefined, + Optional|ResolvesToString|TimeUnit|string $unit = Optional::Undefined, ) { $this->input = $input; $this->unit = $unit; diff --git a/src/Builder/Accumulator/FactoryTrait.php b/src/Builder/Accumulator/FactoryTrait.php index 4b889da47..622b4cf46 100644 --- a/src/Builder/Accumulator/FactoryTrait.php +++ b/src/Builder/Accumulator/FactoryTrait.php @@ -24,6 +24,7 @@ use MongoDB\Builder\Expression\ResolvesToString; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Model\BSONArray; use stdClass; @@ -180,12 +181,12 @@ public static function denseRank(): DenseRankAccumulator * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/derivative/ * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input - * @param Optional|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @param Optional|ResolvesToString|TimeUnit|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ public static function derivative( Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input, - Optional|string $unit = Optional::Undefined, + Optional|ResolvesToString|TimeUnit|string $unit = Optional::Undefined, ): DerivativeAccumulator { return new DerivativeAccumulator($input, $unit); @@ -260,12 +261,12 @@ public static function firstN( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/integral/ * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input - * @param Optional|ResolvesToString|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @param Optional|ResolvesToString|TimeUnit|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ public static function integral( Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input, - Optional|ResolvesToString|string $unit = Optional::Undefined, + Optional|ResolvesToString|TimeUnit|string $unit = Optional::Undefined, ): IntegralAccumulator { return new IntegralAccumulator($input, $unit); diff --git a/src/Builder/Accumulator/IntegralAccumulator.php b/src/Builder/Accumulator/IntegralAccumulator.php index 9f9921165..5a5280ba2 100644 --- a/src/Builder/Accumulator/IntegralAccumulator.php +++ b/src/Builder/Accumulator/IntegralAccumulator.php @@ -17,6 +17,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Builder\Type\WindowInterface; /** @@ -33,19 +34,19 @@ class IntegralAccumulator implements WindowInterface, OperatorInterface public readonly Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input; /** - * @var Optional|ResolvesToString|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @var Optional|ResolvesToString|TimeUnit|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ - public readonly Optional|ResolvesToString|string $unit; + public readonly Optional|ResolvesToString|TimeUnit|string $unit; /** * @param Decimal128|Int64|ResolvesToDate|ResolvesToNumber|UTCDateTime|float|int $input - * @param Optional|ResolvesToString|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". + * @param Optional|ResolvesToString|TimeUnit|string $unit A string that specifies the time unit. Use one of these strings: "week", "day","hour", "minute", "second", "millisecond". * If the sortBy field is not a date, you must omit a unit. If you specify a unit, you must specify a date in the sortBy field. */ public function __construct( Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int $input, - Optional|ResolvesToString|string $unit = Optional::Undefined, + Optional|ResolvesToString|TimeUnit|string $unit = Optional::Undefined, ) { $this->input = $input; $this->unit = $unit; diff --git a/src/Builder/BuilderEncoder.php b/src/Builder/BuilderEncoder.php index 8cc96990d..8b25325ce 100644 --- a/src/Builder/BuilderEncoder.php +++ b/src/Builder/BuilderEncoder.php @@ -5,6 +5,7 @@ namespace MongoDB\Builder; use MongoDB\Builder\Encoder\CombinedFieldQueryEncoder; +use MongoDB\Builder\Encoder\DictionaryEncoder; use MongoDB\Builder\Encoder\ExpressionEncoder; use MongoDB\Builder\Encoder\FieldPathEncoder; use MongoDB\Builder\Encoder\OperatorEncoder; @@ -14,6 +15,7 @@ use MongoDB\Builder\Encoder\VariableEncoder; use MongoDB\Builder\Expression\Variable; use MongoDB\Builder\Type\CombinedFieldQuery; +use MongoDB\Builder\Type\DictionaryInterface; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\FieldPathInterface; use MongoDB\Builder\Type\OperatorInterface; @@ -29,16 +31,17 @@ use function array_key_exists; use function is_object; -/** @template-implements Encoder */ +/** @template-implements Encoder */ class BuilderEncoder implements Encoder { - /** @template-use EncodeIfSupported */ + /** @template-use EncodeIfSupported */ use EncodeIfSupported; /** @var array> */ private array $defaultEncoders = [ Pipeline::class => PipelineEncoder::class, Variable::class => VariableEncoder::class, + DictionaryInterface::class => DictionaryEncoder::class, FieldPathInterface::class => FieldPathEncoder::class, CombinedFieldQuery::class => CombinedFieldQueryEncoder::class, QueryObject::class => QueryEncoder::class, @@ -64,7 +67,7 @@ public function canEncode(mixed $value): bool return (bool) $this->getEncoderFor($value)?->canEncode($value); } - public function encode(mixed $value): stdClass|array|string + public function encode(mixed $value): stdClass|array|string|int { $encoder = $this->getEncoderFor($value); diff --git a/src/Builder/Encoder/AbstractExpressionEncoder.php b/src/Builder/Encoder/AbstractExpressionEncoder.php index 8bbdbaef0..49d7a34f1 100644 --- a/src/Builder/Encoder/AbstractExpressionEncoder.php +++ b/src/Builder/Encoder/AbstractExpressionEncoder.php @@ -11,7 +11,7 @@ use function is_array; /** - * @template BSONType of stdClass|array|string + * @template BSONType of stdClass|array|string|int * @template NativeType * @template-implements ExpressionEncoder */ diff --git a/src/Builder/Encoder/DictionaryEncoder.php b/src/Builder/Encoder/DictionaryEncoder.php new file mode 100644 index 000000000..078db76ed --- /dev/null +++ b/src/Builder/Encoder/DictionaryEncoder.php @@ -0,0 +1,31 @@ + */ +class DictionaryEncoder extends AbstractExpressionEncoder +{ + /** @template-use EncodeIfSupported */ + use EncodeIfSupported; + + public function canEncode(mixed $value): bool + { + return $value instanceof DictionaryInterface; + } + + public function encode(mixed $value): string|int|array|stdClass + { + if (! $this->canEncode($value)) { + throw UnsupportedValueException::invalidEncodableValue($value); + } + + return $value->getValue(); + } +} diff --git a/src/Builder/Encoder/ExpressionEncoder.php b/src/Builder/Encoder/ExpressionEncoder.php index 6613c45f2..1fe94790e 100644 --- a/src/Builder/Encoder/ExpressionEncoder.php +++ b/src/Builder/Encoder/ExpressionEncoder.php @@ -9,7 +9,7 @@ use stdClass; /** - * @template BSONType of stdClass|array|string + * @template BSONType of stdClass|array|string|int * @template NativeType * @template-extends Encoder */ diff --git a/src/Builder/Expression/DateAddOperator.php b/src/Builder/Expression/DateAddOperator.php index 9d35d3beb..b68484663 100644 --- a/src/Builder/Expression/DateAddOperator.php +++ b/src/Builder/Expression/DateAddOperator.php @@ -15,6 +15,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\TimeUnit; /** * Adds a number of time units to a date object. @@ -28,8 +29,8 @@ class DateAddOperator implements ResolvesToDate, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate; - /** @var ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. */ - public readonly ResolvesToString|string $unit; + /** @var ResolvesToString|TimeUnit|string $unit The unit used to measure the amount of time added to the startDate. */ + public readonly ResolvesToString|TimeUnit|string $unit; /** @var Int64|ResolvesToInt|ResolvesToLong|int $amount */ public readonly Int64|ResolvesToInt|ResolvesToLong|int $amount; @@ -39,13 +40,13 @@ class DateAddOperator implements ResolvesToDate, OperatorInterface /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. + * @param ResolvesToString|TimeUnit|string $unit The unit used to measure the amount of time added to the startDate. * @param Int64|ResolvesToInt|ResolvesToLong|int $amount * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, - ResolvesToString|string $unit, + ResolvesToString|TimeUnit|string $unit, Int64|ResolvesToInt|ResolvesToLong|int $amount, Optional|ResolvesToString|string $timezone = Optional::Undefined, ) { diff --git a/src/Builder/Expression/DateDiffOperator.php b/src/Builder/Expression/DateDiffOperator.php index e28d43508..a13e484fc 100644 --- a/src/Builder/Expression/DateDiffOperator.php +++ b/src/Builder/Expression/DateDiffOperator.php @@ -14,6 +14,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\TimeUnit; /** * Returns the difference between two dates. @@ -30,8 +31,8 @@ class DateDiffOperator implements ResolvesToInt, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $endDate The end of the time period. The endDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $endDate; - /** @var ResolvesToString|string $unit The time measurement unit between the startDate and endDate */ - public readonly ResolvesToString|string $unit; + /** @var ResolvesToString|TimeUnit|string $unit The time measurement unit between the startDate and endDate */ + public readonly ResolvesToString|TimeUnit|string $unit; /** @var Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public readonly Optional|ResolvesToString|string $timezone; @@ -42,14 +43,14 @@ class DateDiffOperator implements ResolvesToInt, OperatorInterface /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The start of the time period. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $endDate The end of the time period. The endDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|string $unit The time measurement unit between the startDate and endDate + * @param ResolvesToString|TimeUnit|string $unit The time measurement unit between the startDate and endDate * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. * @param Optional|ResolvesToString|string $startOfWeek Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $endDate, - ResolvesToString|string $unit, + ResolvesToString|TimeUnit|string $unit, Optional|ResolvesToString|string $timezone = Optional::Undefined, Optional|ResolvesToString|string $startOfWeek = Optional::Undefined, ) { diff --git a/src/Builder/Expression/DateSubtractOperator.php b/src/Builder/Expression/DateSubtractOperator.php index e1a583096..90efa4846 100644 --- a/src/Builder/Expression/DateSubtractOperator.php +++ b/src/Builder/Expression/DateSubtractOperator.php @@ -15,6 +15,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\TimeUnit; /** * Subtracts a number of time units from a date object. @@ -28,8 +29,8 @@ class DateSubtractOperator implements ResolvesToDate, OperatorInterface /** @var ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. */ public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate; - /** @var ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. */ - public readonly ResolvesToString|string $unit; + /** @var ResolvesToString|TimeUnit|string $unit The unit used to measure the amount of time added to the startDate. */ + public readonly ResolvesToString|TimeUnit|string $unit; /** @var Int64|ResolvesToInt|ResolvesToLong|int $amount */ public readonly Int64|ResolvesToInt|ResolvesToLong|int $amount; @@ -39,13 +40,13 @@ class DateSubtractOperator implements ResolvesToDate, OperatorInterface /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. + * @param ResolvesToString|TimeUnit|string $unit The unit used to measure the amount of time added to the startDate. * @param Int64|ResolvesToInt|ResolvesToLong|int $amount * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, - ResolvesToString|string $unit, + ResolvesToString|TimeUnit|string $unit, Int64|ResolvesToInt|ResolvesToLong|int $amount, Optional|ResolvesToString|string $timezone = Optional::Undefined, ) { diff --git a/src/Builder/Expression/DateTruncOperator.php b/src/Builder/Expression/DateTruncOperator.php index cf56cdfa2..78be17040 100644 --- a/src/Builder/Expression/DateTruncOperator.php +++ b/src/Builder/Expression/DateTruncOperator.php @@ -16,6 +16,7 @@ use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\TimeUnit; /** * Truncates a date. @@ -30,10 +31,10 @@ class DateTruncOperator implements ResolvesToDate, OperatorInterface public readonly ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date; /** - * @var ResolvesToString|string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. + * @var ResolvesToString|TimeUnit|string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. * Together, binSize and unit specify the time period used in the $dateTrunc calculation. */ - public readonly ResolvesToString|string $unit; + public readonly ResolvesToString|TimeUnit|string $unit; /** * @var Optional|Decimal128|Int64|ResolvesToNumber|float|int $binSize The numeric time value, specified as an expression that must resolve to a positive non-zero number. Defaults to 1. @@ -52,7 +53,7 @@ class DateTruncOperator implements ResolvesToDate, OperatorInterface /** * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to truncate, specified in UTC. The date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. + * @param ResolvesToString|TimeUnit|string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. * Together, binSize and unit specify the time period used in the $dateTrunc calculation. * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $binSize The numeric time value, specified as an expression that must resolve to a positive non-zero number. Defaults to 1. * Together, binSize and unit specify the time period used in the $dateTrunc calculation. @@ -62,7 +63,7 @@ class DateTruncOperator implements ResolvesToDate, OperatorInterface */ public function __construct( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, - ResolvesToString|string $unit, + ResolvesToString|TimeUnit|string $unit, Optional|Decimal128|Int64|ResolvesToNumber|float|int $binSize = Optional::Undefined, Optional|ResolvesToString|string $timezone = Optional::Undefined, Optional|string $startOfWeek = Optional::Undefined, diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 19fcd539b..cb521066c 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -22,6 +22,8 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\Optional; +use MongoDB\Builder\Type\Sort; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Model\BSONArray; use stdClass; @@ -439,13 +441,13 @@ public static function cosh(Decimal128|Int64|ResolvesToNumber|float|int $express * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateAdd/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. + * @param ResolvesToString|TimeUnit|string $unit The unit used to measure the amount of time added to the startDate. * @param Int64|ResolvesToInt|ResolvesToLong|int $amount * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function dateAdd( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, - ResolvesToString|string $unit, + ResolvesToString|TimeUnit|string $unit, Int64|ResolvesToInt|ResolvesToLong|int $amount, Optional|ResolvesToString|string $timezone = Optional::Undefined, ): DateAddOperator @@ -459,14 +461,14 @@ public static function dateAdd( * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateDiff/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The start of the time period. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $endDate The end of the time period. The endDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|string $unit The time measurement unit between the startDate and endDate + * @param ResolvesToString|TimeUnit|string $unit The time measurement unit between the startDate and endDate * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. * @param Optional|ResolvesToString|string $startOfWeek Used when the unit is equal to week. Defaults to Sunday. The startOfWeek parameter is an expression that resolves to a case insensitive string */ public static function dateDiff( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $endDate, - ResolvesToString|string $unit, + ResolvesToString|TimeUnit|string $unit, Optional|ResolvesToString|string $timezone = Optional::Undefined, Optional|ResolvesToString|string $startOfWeek = Optional::Undefined, ): DateDiffOperator @@ -536,13 +538,13 @@ public static function dateFromString( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateSubtract/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $startDate The beginning date, in UTC, for the addition operation. The startDate can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|string $unit The unit used to measure the amount of time added to the startDate. + * @param ResolvesToString|TimeUnit|string $unit The unit used to measure the amount of time added to the startDate. * @param Int64|ResolvesToInt|ResolvesToLong|int $amount * @param Optional|ResolvesToString|string $timezone The timezone to carry out the operation. $timezone must be a valid expression that resolves to a string formatted as either an Olson Timezone Identifier or a UTC Offset. If no timezone is provided, the result is displayed in UTC. */ public static function dateSubtract( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $startDate, - ResolvesToString|string $unit, + ResolvesToString|TimeUnit|string $unit, Int64|ResolvesToInt|ResolvesToLong|int $amount, Optional|ResolvesToString|string $timezone = Optional::Undefined, ): DateSubtractOperator @@ -593,7 +595,7 @@ public static function dateToString( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/dateTrunc/ * @param ObjectId|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|Timestamp|UTCDateTime|int $date The date to truncate, specified in UTC. The date can be any expression that resolves to a Date, a Timestamp, or an ObjectID. - * @param ResolvesToString|string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. + * @param ResolvesToString|TimeUnit|string $unit The unit of time, specified as an expression that must resolve to one of these strings: year, quarter, week, month, day, hour, minute, second. * Together, binSize and unit specify the time period used in the $dateTrunc calculation. * @param Optional|Decimal128|Int64|ResolvesToNumber|float|int $binSize The numeric time value, specified as an expression that must resolve to a positive non-zero number. Defaults to 1. * Together, binSize and unit specify the time period used in the $dateTrunc calculation. @@ -603,7 +605,7 @@ public static function dateToString( */ public static function dateTrunc( ObjectId|Timestamp|UTCDateTime|ResolvesToDate|ResolvesToObjectId|ResolvesToTimestamp|int $date, - ResolvesToString|string $unit, + ResolvesToString|TimeUnit|string $unit, Optional|Decimal128|Int64|ResolvesToNumber|float|int $binSize = Optional::Undefined, Optional|ResolvesToString|string $timezone = Optional::Undefined, Optional|string $startOfWeek = Optional::Undefined, @@ -1815,11 +1817,11 @@ public static function slice( * @param BSONArray|PackedArray|ResolvesToArray|array $input The array to be sorted. * The result is null if the expression: is missing, evaluates to null, or evaluates to undefined * If the expression evaluates to any other non-array value, the document returns an error. - * @param Document|Serializable|array|int|stdClass $sortBy The document specifies a sort ordering. + * @param Document|Serializable|Sort|array|int|stdClass $sortBy The document specifies a sort ordering. */ public static function sortArray( PackedArray|ResolvesToArray|BSONArray|array $input, - Document|Serializable|stdClass|array|int $sortBy, + Document|Serializable|Sort|stdClass|array|int $sortBy, ): SortArrayOperator { return new SortArrayOperator($input, $sortBy); diff --git a/src/Builder/Expression/SortArrayOperator.php b/src/Builder/Expression/SortArrayOperator.php index 225a602a5..983408432 100644 --- a/src/Builder/Expression/SortArrayOperator.php +++ b/src/Builder/Expression/SortArrayOperator.php @@ -13,6 +13,7 @@ use MongoDB\BSON\Serializable; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\Sort; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; use stdClass; @@ -36,18 +37,18 @@ class SortArrayOperator implements ResolvesToArray, OperatorInterface */ public readonly PackedArray|ResolvesToArray|BSONArray|array $input; - /** @var Document|Serializable|array|int|stdClass $sortBy The document specifies a sort ordering. */ - public readonly Document|Serializable|stdClass|array|int $sortBy; + /** @var Document|Serializable|Sort|array|int|stdClass $sortBy The document specifies a sort ordering. */ + public readonly Document|Serializable|Sort|stdClass|array|int $sortBy; /** * @param BSONArray|PackedArray|ResolvesToArray|array $input The array to be sorted. * The result is null if the expression: is missing, evaluates to null, or evaluates to undefined * If the expression evaluates to any other non-array value, the document returns an error. - * @param Document|Serializable|array|int|stdClass $sortBy The document specifies a sort ordering. + * @param Document|Serializable|Sort|array|int|stdClass $sortBy The document specifies a sort ordering. */ public function __construct( PackedArray|ResolvesToArray|BSONArray|array $input, - Document|Serializable|stdClass|array|int $sortBy, + Document|Serializable|Sort|stdClass|array|int $sortBy, ) { if (is_array($input) && ! array_is_list($input)) { throw new InvalidArgumentException('Expected $input argument to be a list, got an associative array.'); diff --git a/src/Builder/Stage/FactoryTrait.php b/src/Builder/Stage/FactoryTrait.php index cce28140b..4d0086611 100644 --- a/src/Builder/Stage/FactoryTrait.php +++ b/src/Builder/Stage/FactoryTrait.php @@ -24,6 +24,7 @@ use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\QueryInterface; +use MongoDB\Builder\Type\Sort; use MongoDB\Model\BSONArray; use stdClass; @@ -633,11 +634,13 @@ public static function skip(int $skip): SkipStage * Reorders the document stream by a specified sort key. Only the order changes; the documents remain unmodified. For each input document, outputs one document. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/ - * @param Document|Serializable|array|stdClass $sort + * @param ExpressionInterface|Sort|Type|array|bool|float|int|null|stdClass|string ...$sort */ - public static function sort(Document|Serializable|stdClass|array $sort): SortStage + public static function sort( + Type|ExpressionInterface|Sort|stdClass|array|bool|float|int|null|string ...$sort, + ): SortStage { - return new SortStage($sort); + return new SortStage(...$sort); } /** diff --git a/src/Builder/Stage/SortStage.php b/src/Builder/Stage/SortStage.php index fca4d5d43..36f351c69 100644 --- a/src/Builder/Stage/SortStage.php +++ b/src/Builder/Stage/SortStage.php @@ -8,13 +8,17 @@ namespace MongoDB\Builder\Stage; -use MongoDB\BSON\Document; -use MongoDB\BSON\Serializable; +use MongoDB\BSON\Type; use MongoDB\Builder\Type\Encode; +use MongoDB\Builder\Type\ExpressionInterface; use MongoDB\Builder\Type\OperatorInterface; +use MongoDB\Builder\Type\Sort; use MongoDB\Builder\Type\StageInterface; +use MongoDB\Exception\InvalidArgumentException; use stdClass; +use function is_string; + /** * Reorders the document stream by a specified sort key. Only the order changes; the documents remain unmodified. For each input document, outputs one document. * @@ -24,14 +28,25 @@ class SortStage implements StageInterface, OperatorInterface { public const ENCODE = Encode::Single; - /** @var Document|Serializable|array|stdClass $sort */ - public readonly Document|Serializable|stdClass|array $sort; + /** @var stdClass $sort */ + public readonly stdClass $sort; /** - * @param Document|Serializable|array|stdClass $sort + * @param ExpressionInterface|Sort|Type|array|bool|float|int|null|stdClass|string ...$sort */ - public function __construct(Document|Serializable|stdClass|array $sort) + public function __construct(Type|ExpressionInterface|Sort|stdClass|array|bool|float|int|null|string ...$sort) { + if (\count($sort) < 1) { + throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $sort, got %d.', 1, \count($sort))); + } + + foreach($sort as $key => $value) { + if (! is_string($key)) { + throw new InvalidArgumentException('Expected $sort arguments to be a map (object), named arguments (:) or array unpacking ...[\'\' => ] must be used'); + } + } + + $sort = (object) $sort; $this->sort = $sort; } diff --git a/src/Builder/Type/DictionaryInterface.php b/src/Builder/Type/DictionaryInterface.php new file mode 100644 index 000000000..5d88abffa --- /dev/null +++ b/src/Builder/Type/DictionaryInterface.php @@ -0,0 +1,10 @@ +operator = $operator; diff --git a/src/Builder/Type/Sort.php b/src/Builder/Type/Sort.php new file mode 100644 index 000000000..5d1196311 --- /dev/null +++ b/src/Builder/Type/Sort.php @@ -0,0 +1,26 @@ + 1, + self::Desc => -1, + self::TextScore => ['$meta' => 'textScore'], + }; + } +} diff --git a/src/Builder/Type/TimeUnit.php b/src/Builder/Type/TimeUnit.php new file mode 100644 index 000000000..b5e76d4e2 --- /dev/null +++ b/src/Builder/Type/TimeUnit.php @@ -0,0 +1,26 @@ +value; + } +} diff --git a/tests/Builder/Accumulator/AddToSetAccumulatorTest.php b/tests/Builder/Accumulator/AddToSetAccumulatorTest.php index a63b3270f..bd3b20912 100644 --- a/tests/Builder/Accumulator/AddToSetAccumulatorTest.php +++ b/tests/Builder/Accumulator/AddToSetAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -38,7 +39,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( cakeTypesForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/AvgAccumulatorTest.php b/tests/Builder/Accumulator/AvgAccumulatorTest.php index 9fd0eb3ed..78e37a905 100644 --- a/tests/Builder/Accumulator/AvgAccumulatorTest.php +++ b/tests/Builder/Accumulator/AvgAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -43,7 +44,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( averageQuantityForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/BottomAccumulatorTest.php b/tests/Builder/Accumulator/BottomAccumulatorTest.php index 447e352e8..674c60ddb 100644 --- a/tests/Builder/Accumulator/BottomAccumulatorTest.php +++ b/tests/Builder/Accumulator/BottomAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -31,7 +32,7 @@ public function testFindTheBottomScore(): void Expression::fieldPath('score'), ], sortBy: object( - score: -1, + score: Sort::Desc, ), ), ), @@ -51,7 +52,7 @@ public function testFindingTheBottomScoreAcrossMultipleGames(): void Expression::fieldPath('score'), ], sortBy: object( - score: -1, + score: Sort::Desc, ), ), ), diff --git a/tests/Builder/Accumulator/BottomNAccumulatorTest.php b/tests/Builder/Accumulator/BottomNAccumulatorTest.php index 3cf7276ab..8ea89f669 100644 --- a/tests/Builder/Accumulator/BottomNAccumulatorTest.php +++ b/tests/Builder/Accumulator/BottomNAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -35,7 +36,7 @@ public function testComputingNBasedOnTheGroupKeyForGroup(): void else: 3, ), sortBy: object( - score: -1, + score: Sort::Desc, ), ), ), @@ -58,7 +59,7 @@ public function testFindTheThreeLowestScores(): void Expression::fieldPath('score'), ], sortBy: object( - score: -1, + score: Sort::Desc, ), n: 3, ), @@ -79,7 +80,7 @@ public function testFindingTheThreeLowestScoreDocumentsAcrossMultipleGames(): vo Expression::fieldPath('score'), ], sortBy: object( - score: -1, + score: Sort::Desc, ), n: 3, ), diff --git a/tests/Builder/Accumulator/CountAccumulatorTest.php b/tests/Builder/Accumulator/CountAccumulatorTest.php index 49e05f472..6c33d7293 100644 --- a/tests/Builder/Accumulator/CountAccumulatorTest.php +++ b/tests/Builder/Accumulator/CountAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -35,7 +36,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( countNumberOfDocumentsForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/CovariancePopAccumulatorTest.php b/tests/Builder/Accumulator/CovariancePopAccumulatorTest.php index a07eb3e83..516ecca95 100644 --- a/tests/Builder/Accumulator/CovariancePopAccumulatorTest.php +++ b/tests/Builder/Accumulator/CovariancePopAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -23,7 +24,7 @@ public function testExample(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( covariancePopForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/CovarianceSampAccumulatorTest.php b/tests/Builder/Accumulator/CovarianceSampAccumulatorTest.php index a0db117ce..d2f176006 100644 --- a/tests/Builder/Accumulator/CovarianceSampAccumulatorTest.php +++ b/tests/Builder/Accumulator/CovarianceSampAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -23,7 +24,7 @@ public function testExample(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( covarianceSampForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/DenseRankAccumulatorTest.php b/tests/Builder/Accumulator/DenseRankAccumulatorTest.php index 54dac4bc4..7ba7b8a54 100644 --- a/tests/Builder/Accumulator/DenseRankAccumulatorTest.php +++ b/tests/Builder/Accumulator/DenseRankAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -23,7 +24,7 @@ public function testDenseRankPartitionsByADateField(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( denseRankOrderDateForState: Accumulator::outputWindow( @@ -42,7 +43,7 @@ public function testDenseRankPartitionsByAnIntegerField(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), sortBy: object( - quantity: -1, + quantity: Sort::Desc, ), output: object( // The outputWindow is optional when no window property is set. diff --git a/tests/Builder/Accumulator/DerivativeAccumulatorTest.php b/tests/Builder/Accumulator/DerivativeAccumulatorTest.php index 448d11387..7941c4b6a 100644 --- a/tests/Builder/Accumulator/DerivativeAccumulatorTest.php +++ b/tests/Builder/Accumulator/DerivativeAccumulatorTest.php @@ -9,6 +9,8 @@ use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -24,16 +26,16 @@ public function testExample(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('truckID'), sortBy: object( - timeStamp: 1, + timeStamp: Sort::Asc, ), output: object( truckAverageSpeed: Accumulator::outputWindow( Accumulator::derivative( input: Expression::numberFieldPath('miles'), - unit: 'hour', + unit: TimeUnit::Hour, ), range: [-30, 0], - unit: 'second', + unit: TimeUnit::Second, ), ), ), diff --git a/tests/Builder/Accumulator/DocumentNumberAccumulatorTest.php b/tests/Builder/Accumulator/DocumentNumberAccumulatorTest.php index 832fd4c30..fbc13c4cb 100644 --- a/tests/Builder/Accumulator/DocumentNumberAccumulatorTest.php +++ b/tests/Builder/Accumulator/DocumentNumberAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -23,7 +24,7 @@ public function testDocumentNumberForEachState(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), sortBy: object( - quantity: -1, + quantity: Sort::Desc, ), output: object( documentNumberForState: Accumulator::documentNumber(), diff --git a/tests/Builder/Accumulator/ExpMovingAvgAccumulatorTest.php b/tests/Builder/Accumulator/ExpMovingAvgAccumulatorTest.php index 2e1a33b08..59a6156d4 100644 --- a/tests/Builder/Accumulator/ExpMovingAvgAccumulatorTest.php +++ b/tests/Builder/Accumulator/ExpMovingAvgAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -23,7 +24,7 @@ public function testExponentialMovingAverageUsingAlpha(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('stock'), sortBy: object( - date: 1, + date: Sort::Asc, ), output: object( expMovingAvgForStock: Accumulator::expMovingAvg( @@ -43,7 +44,7 @@ public function testExponentialMovingAverageUsingN(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('stock'), sortBy: object( - date: 1, + date: Sort::Asc, ), output: object( expMovingAvgForStock: Accumulator::expMovingAvg( diff --git a/tests/Builder/Accumulator/FirstAccumulatorTest.php b/tests/Builder/Accumulator/FirstAccumulatorTest.php index dc453af26..e78f9c53d 100644 --- a/tests/Builder/Accumulator/FirstAccumulatorTest.php +++ b/tests/Builder/Accumulator/FirstAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -20,10 +21,10 @@ class FirstAccumulatorTest extends PipelineTestCase public function testUseInGroupStage(): void { $pipeline = new Pipeline( - Stage::sort(object( - item: 1, - date: 1, - )), + Stage::sort( + item: Sort::Asc, + date: Sort::Asc, + ), Stage::group( _id: Expression::fieldPath('item'), firstSale: Accumulator::first(Expression::dateFieldPath('date')), @@ -39,7 +40,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( firstOrderTypeForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/FirstNAccumulatorTest.php b/tests/Builder/Accumulator/FirstNAccumulatorTest.php index 58879b836..fe4f36384 100644 --- a/tests/Builder/Accumulator/FirstNAccumulatorTest.php +++ b/tests/Builder/Accumulator/FirstNAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -106,9 +107,7 @@ public function testUsingSortWithFirstN(): void { $pipeline = new Pipeline( Stage::sort( - object( - score: -1, - ), + score: Sort::Desc, ), Stage::group( _id: Expression::fieldPath('gameId'), diff --git a/tests/Builder/Accumulator/IntegralAccumulatorTest.php b/tests/Builder/Accumulator/IntegralAccumulatorTest.php index d596d12e4..636456518 100644 --- a/tests/Builder/Accumulator/IntegralAccumulatorTest.php +++ b/tests/Builder/Accumulator/IntegralAccumulatorTest.php @@ -8,6 +8,8 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -23,16 +25,16 @@ public function testExample(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('powerMeterID'), sortBy: object( - timeStamp: 1, + timeStamp: Sort::Asc, ), output: object( powerMeterKilowattHours: Accumulator::outputWindow( Accumulator::integral( input: Expression::numberFieldPath('kilowatts'), - unit: 'hour', + unit: TimeUnit::Hour, ), range: ['unbounded', 'current'], - unit: 'hour', + unit: TimeUnit::Hour, ), ), ), diff --git a/tests/Builder/Accumulator/LastAccumulatorTest.php b/tests/Builder/Accumulator/LastAccumulatorTest.php index 427e180c4..e0439b662 100644 --- a/tests/Builder/Accumulator/LastAccumulatorTest.php +++ b/tests/Builder/Accumulator/LastAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -20,10 +21,10 @@ class LastAccumulatorTest extends PipelineTestCase public function testUseInGroupStage(): void { $pipeline = new Pipeline( - Stage::sort(object( - item: 1, - date: 1, - )), + Stage::sort( + item: Sort::Asc, + date: Sort::Asc, + ), Stage::group( _id: '$item', lastSalesDate: Accumulator::last(Expression::dateFieldPath('date')), @@ -39,7 +40,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( lastOrderTypeForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/LastNAccumulatorTest.php b/tests/Builder/Accumulator/LastNAccumulatorTest.php index 9040b919a..caf957fde 100644 --- a/tests/Builder/Accumulator/LastNAccumulatorTest.php +++ b/tests/Builder/Accumulator/LastNAccumulatorTest.php @@ -8,10 +8,9 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; - /** * Test $lastN accumulator */ @@ -81,9 +80,9 @@ public function testFindingTheLastThreePlayerScoresAcrossMultipleGames(): void public function testUsingSortWithLastN(): void { $pipeline = new Pipeline( - Stage::sort(object( - score: -1, - )), + Stage::sort( + score: Sort::Desc, + ), Stage::group( _id: Expression::fieldPath('gameId'), playerId: Accumulator::lastN( diff --git a/tests/Builder/Accumulator/LinearFillAccumulatorTest.php b/tests/Builder/Accumulator/LinearFillAccumulatorTest.php index 32c4a0202..0142d0d68 100644 --- a/tests/Builder/Accumulator/LinearFillAccumulatorTest.php +++ b/tests/Builder/Accumulator/LinearFillAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -22,7 +23,7 @@ public function testFillMissingValuesWithLinearInterpolation(): void $pipeline = new Pipeline( Stage::setWindowFields( sortBy: object( - time: 1, + time: Sort::Asc, ), output: object( price: Accumulator::linearFill( @@ -40,7 +41,7 @@ public function testUseMultipleFillMethodsInASingleStage(): void $pipeline = new Pipeline( Stage::setWindowFields( sortBy: object( - time: 1, + time: Sort::Asc, ), output: object( linearFillPrice: Accumulator::linearFill( diff --git a/tests/Builder/Accumulator/LocfAccumulatorTest.php b/tests/Builder/Accumulator/LocfAccumulatorTest.php index 0723e9acb..259a92aff 100644 --- a/tests/Builder/Accumulator/LocfAccumulatorTest.php +++ b/tests/Builder/Accumulator/LocfAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -22,7 +23,7 @@ public function testFillMissingValuesWithTheLastObservedValue(): void $pipeline = new Pipeline( Stage::setWindowFields( sortBy: object( - time: 1, + time: Sort::Asc, ), output: object( price: Accumulator::locf( diff --git a/tests/Builder/Accumulator/MaxAccumulatorTest.php b/tests/Builder/Accumulator/MaxAccumulatorTest.php index cb948d903..6a75d85c9 100644 --- a/tests/Builder/Accumulator/MaxAccumulatorTest.php +++ b/tests/Builder/Accumulator/MaxAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -43,7 +44,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( maximumQuantityForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/MedianAccumulatorTest.php b/tests/Builder/Accumulator/MedianAccumulatorTest.php index 21460eef4..c1f453347 100644 --- a/tests/Builder/Accumulator/MedianAccumulatorTest.php +++ b/tests/Builder/Accumulator/MedianAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -37,7 +38,7 @@ public function testUseMedianInASetWindowFieldStage(): void $pipeline = new Pipeline( Stage::setWindowFields( sortBy: object( - test01: 1, + test01: Sort::Asc, ), output: object( test01_median: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/MinAccumulatorTest.php b/tests/Builder/Accumulator/MinAccumulatorTest.php index f71d26e6c..442a643a3 100644 --- a/tests/Builder/Accumulator/MinAccumulatorTest.php +++ b/tests/Builder/Accumulator/MinAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -37,7 +38,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( minimumQuantityForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/PercentileAccumulatorTest.php b/tests/Builder/Accumulator/PercentileAccumulatorTest.php index 4c2f58541..a3fc1708d 100644 --- a/tests/Builder/Accumulator/PercentileAccumulatorTest.php +++ b/tests/Builder/Accumulator/PercentileAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -69,7 +70,7 @@ public function testUsePercentileInASetWindowFieldStage(): void $pipeline = new Pipeline( Stage::setWindowFields( sortBy: object( - test01: 1, + test01: Sort::Asc, ), output: object( test01_95percentile: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/PushAccumulatorTest.php b/tests/Builder/Accumulator/PushAccumulatorTest.php index 25a2327f2..9194a6e56 100644 --- a/tests/Builder/Accumulator/PushAccumulatorTest.php +++ b/tests/Builder/Accumulator/PushAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -21,10 +22,8 @@ public function testUseInGroupStage(): void { $pipeline = new Pipeline( Stage::sort( - object( - date: 1, - item: 1, - ), + date: Sort::Asc, + item: Sort::Asc, ), Stage::group( _id: object( @@ -53,7 +52,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( quantitiesForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/RankAccumulatorTest.php b/tests/Builder/Accumulator/RankAccumulatorTest.php index d2420a96e..3be7e63aa 100644 --- a/tests/Builder/Accumulator/RankAccumulatorTest.php +++ b/tests/Builder/Accumulator/RankAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -23,7 +24,7 @@ public function testRankPartitionsByADateField(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), sortBy: object( - orderDate: 1, + orderDate:Sort::Asc, ), output: object( rankOrderDateForState: Accumulator::rank(), @@ -40,7 +41,7 @@ public function testRankPartitionsByAnIntegerField(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), sortBy: object( - quantity: -1, + quantity: Sort::Desc, ), output: object( rankQuantityForState: Accumulator::rank(), diff --git a/tests/Builder/Accumulator/ShiftAccumulatorTest.php b/tests/Builder/Accumulator/ShiftAccumulatorTest.php index c36c68095..7d8c58856 100644 --- a/tests/Builder/Accumulator/ShiftAccumulatorTest.php +++ b/tests/Builder/Accumulator/ShiftAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -23,7 +24,7 @@ public function testShiftUsingANegativeInteger(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), sortBy: object( - quantity: -1, + quantity: Sort::Desc, ), output: object( shiftQuantityForState: Accumulator::shift( @@ -44,7 +45,7 @@ public function testShiftUsingAPositiveInteger(): void Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), sortBy: object( - quantity: -1, + quantity: Sort::Desc, ), output: object( shiftQuantityForState: Accumulator::shift( diff --git a/tests/Builder/Accumulator/StdDevPopAccumulatorTest.php b/tests/Builder/Accumulator/StdDevPopAccumulatorTest.php index 78943d9ad..7d38829e4 100644 --- a/tests/Builder/Accumulator/StdDevPopAccumulatorTest.php +++ b/tests/Builder/Accumulator/StdDevPopAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -37,7 +38,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( stdDevPopQuantityForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/StdDevSampAccumulatorTest.php b/tests/Builder/Accumulator/StdDevSampAccumulatorTest.php index 704eab827..c1f49e00a 100644 --- a/tests/Builder/Accumulator/StdDevSampAccumulatorTest.php +++ b/tests/Builder/Accumulator/StdDevSampAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -38,7 +39,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( stdDevSampQuantityForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/SumAccumulatorTest.php b/tests/Builder/Accumulator/SumAccumulatorTest.php index 83a9ef147..21be6da72 100644 --- a/tests/Builder/Accumulator/SumAccumulatorTest.php +++ b/tests/Builder/Accumulator/SumAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -48,7 +49,7 @@ public function testUseInSetWindowFieldsStage(): void Stage::setWindowFields( partitionBy: Expression::fieldPath('state'), sortBy: object( - orderDate: 1, + orderDate: Sort::Asc, ), output: object( sumQuantityForState: Accumulator::outputWindow( diff --git a/tests/Builder/Accumulator/TopAccumulatorTest.php b/tests/Builder/Accumulator/TopAccumulatorTest.php index 48fd0f782..c6d32edb5 100644 --- a/tests/Builder/Accumulator/TopAccumulatorTest.php +++ b/tests/Builder/Accumulator/TopAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -31,7 +32,7 @@ public function testFindTheTopScore(): void Expression::fieldPath('score'), ], sortBy: object( - score: -1, + score: Sort::Desc, ), ), ), @@ -51,7 +52,7 @@ public function testFindTheTopScoreAcrossMultipleGames(): void Expression::fieldPath('score'), ], sortBy: object( - score: -1, + score: Sort::Desc, ), ), ), diff --git a/tests/Builder/Accumulator/TopNAccumulatorTest.php b/tests/Builder/Accumulator/TopNAccumulatorTest.php index c286c0610..6d14a9c6f 100644 --- a/tests/Builder/Accumulator/TopNAccumulatorTest.php +++ b/tests/Builder/Accumulator/TopNAccumulatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -35,7 +36,7 @@ public function testComputingNBasedOnTheGroupKeyForGroup(): void else: 3, ), sortBy: object( - score: -1, + score: Sort::Desc, ), ), ), @@ -58,7 +59,7 @@ public function testFindTheThreeHighestScores(): void Expression::fieldPath('score'), ], sortBy: object( - score: -1, + score: Sort::Desc, ), n: 3, ), @@ -79,7 +80,7 @@ public function testFindingTheThreeHighestScoreDocumentsAcrossMultipleGames(): v Expression::fieldPath('score'), ], sortBy: object( - score: -1, + score: Sort::Desc, ), n: 3, ), diff --git a/tests/Builder/BuilderEncoderTest.php b/tests/Builder/BuilderEncoderTest.php index 32ecce738..9d3292bb2 100644 --- a/tests/Builder/BuilderEncoderTest.php +++ b/tests/Builder/BuilderEncoderTest.php @@ -12,6 +12,7 @@ use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Builder\Variable; use PHPUnit\Framework\TestCase; @@ -98,7 +99,10 @@ public function testMatchNumericFieldName(): void public function testSort(): void { $pipeline = new Pipeline( - Stage::sort(object(age: -1, posts: 1)), + Stage::sort( + age: Sort::Desc, + posts: Sort::Asc, + ), ); $expected = [ @@ -228,7 +232,7 @@ public function testSetWindowFields(): void $pipeline = new Pipeline( Stage::setWindowFields( partitionBy: Expression::year(Expression::dateFieldPath('orderDate')), - sortBy: object(orderDate: 1), + sortBy: object(orderDate: Sort::Asc), output: object( cumulativeQuantityForYear: Accumulator::outputWindow( Accumulator::sum(Expression::intFieldPath('quantity')), diff --git a/tests/Builder/Expression/BsonSizeOperatorTest.php b/tests/Builder/Expression/BsonSizeOperatorTest.php index 8da3239d7..49707b120 100644 --- a/tests/Builder/Expression/BsonSizeOperatorTest.php +++ b/tests/Builder/Expression/BsonSizeOperatorTest.php @@ -8,10 +8,9 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; - /** * Test $bsonSize expression */ @@ -43,7 +42,7 @@ public function testReturnDocumentWithLargestSpecifiedField(): void ), ), Stage::sort( - object(task_object_size: -1), + task_object_size: Sort::Desc, ), Stage::limit(1), ); diff --git a/tests/Builder/Expression/DateAddOperatorTest.php b/tests/Builder/Expression/DateAddOperatorTest.php index 0a06fee6b..32ecac36b 100644 --- a/tests/Builder/Expression/DateAddOperatorTest.php +++ b/tests/Builder/Expression/DateAddOperatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Tests\Builder\PipelineTestCase; /** @@ -21,7 +22,7 @@ public function testAddAFutureDate(): void Stage::project( expectedDeliveryDate: Expression::dateAdd( startDate: Expression::dateFieldPath('purchaseDate'), - unit: 'day', + unit: TimeUnit::Day, amount: 3, ), ), @@ -45,7 +46,7 @@ public function testAdjustForDaylightSavingsTime(): void format: '%Y-%m-%d %H:%M', date: Expression::dateAdd( startDate: Expression::dateFieldPath('login'), - unit: 'day', + unit: TimeUnit::Day, amount: 1, timezone: Expression::stringFieldPath('location'), ), @@ -54,7 +55,7 @@ public function testAdjustForDaylightSavingsTime(): void format: '%Y-%m-%d %H:%M', date: Expression::dateAdd( startDate: Expression::dateFieldPath('login'), - unit: 'hour', + unit: TimeUnit::Hour, amount: 24, timezone: Expression::stringFieldPath('location'), ), @@ -68,7 +69,7 @@ public function testAdjustForDaylightSavingsTime(): void format: '%Y-%m-%d %H:%M', date: Expression::dateAdd( startDate: Expression::dateFieldPath('login'), - unit: 'day', + unit: TimeUnit::Day, amount: 1, timezone: Expression::stringFieldPath('location'), ), @@ -78,7 +79,7 @@ public function testAdjustForDaylightSavingsTime(): void format: '%Y-%m-%d %H:%M', date: Expression::dateAdd( startDate: Expression::dateFieldPath('login'), - unit: 'hour', + unit: TimeUnit::Hour, amount: 24, timezone: Expression::stringFieldPath('location'), ), @@ -99,7 +100,7 @@ public function testFilterOnADateRange(): void Expression::dateFieldPath('deliveryDate'), Expression::dateAdd( startDate: Expression::dateFieldPath('purchaseDate'), - unit: 'day', + unit: TimeUnit::Day, amount: 5, ), ), diff --git a/tests/Builder/Expression/DateDiffOperatorTest.php b/tests/Builder/Expression/DateDiffOperatorTest.php index 15584a502..571e0473c 100644 --- a/tests/Builder/Expression/DateDiffOperatorTest.php +++ b/tests/Builder/Expression/DateDiffOperatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Tests\Builder\PipelineTestCase; /** @@ -24,7 +25,7 @@ public function testElapsedTime(): void Expression::dateDiff( startDate: Expression::dateFieldPath('purchased'), endDate: Expression::dateFieldPath('delivered'), - unit: 'day', + unit: TimeUnit::Day, ), ), ), @@ -49,17 +50,17 @@ public function testResultPrecision(): void years: Expression::dateDiff( startDate: Expression::dateFieldPath('start'), endDate: Expression::dateFieldPath('end'), - unit: 'year', + unit: TimeUnit::Year, ), months: Expression::dateDiff( startDate: Expression::dateFieldPath('start'), endDate: Expression::dateFieldPath('end'), - unit: 'month', + unit: TimeUnit::Month, ), days: Expression::dateDiff( startDate: Expression::dateFieldPath('start'), endDate: Expression::dateFieldPath('end'), - unit: 'day', + unit: TimeUnit::Day, ), _id: 0, ), @@ -75,18 +76,18 @@ public function testWeeksPerMonth(): void wks_default: Expression::dateDiff( startDate: Expression::dateFieldPath('start'), endDate: Expression::dateFieldPath('end'), - unit: 'week', + unit: TimeUnit::Week, ), wks_monday: Expression::dateDiff( startDate: Expression::dateFieldPath('start'), endDate: Expression::dateFieldPath('end'), - unit: 'week', + unit: TimeUnit::Week, startOfWeek: 'Monday', ), wks_friday: Expression::dateDiff( startDate: Expression::dateFieldPath('start'), endDate: Expression::dateFieldPath('end'), - unit: 'week', + unit: TimeUnit::Week, startOfWeek: 'fri', ), _id: 0, diff --git a/tests/Builder/Expression/DateSubtractOperatorTest.php b/tests/Builder/Expression/DateSubtractOperatorTest.php index b516302b4..013a3db74 100644 --- a/tests/Builder/Expression/DateSubtractOperatorTest.php +++ b/tests/Builder/Expression/DateSubtractOperatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Tests\Builder\PipelineTestCase; /** @@ -29,7 +30,7 @@ public function testAdjustForDaylightSavingsTime(): void format: '%Y-%m-%d %H:%M', date: Expression::dateSubtract( startDate: Expression::dateFieldPath('login'), - unit: 'day', + unit: TimeUnit::Day, amount: 1, timezone: Expression::stringFieldPath('location'), ), @@ -38,7 +39,7 @@ public function testAdjustForDaylightSavingsTime(): void format: '%Y-%m-%d %H:%M', date: Expression::dateSubtract( startDate: Expression::dateFieldPath('login'), - unit: 'hour', + unit: TimeUnit::Hour, amount: 24, timezone: Expression::stringFieldPath('location'), ), @@ -52,7 +53,7 @@ public function testAdjustForDaylightSavingsTime(): void format: '%Y-%m-%d %H:%M', date: Expression::dateSubtract( startDate: Expression::dateFieldPath('login'), - unit: 'day', + unit: TimeUnit::Day, amount: 1, timezone: Expression::stringFieldPath('location'), ), @@ -62,7 +63,7 @@ public function testAdjustForDaylightSavingsTime(): void format: '%Y-%m-%d %H:%M', date: Expression::dateSubtract( startDate: Expression::dateFieldPath('login'), - unit: 'hour', + unit: TimeUnit::Hour, amount: 24, timezone: Expression::stringFieldPath('location'), ), @@ -83,7 +84,7 @@ public function testFilterByRelativeDates(): void Expression::dateFieldPath('logoutTime'), Expression::dateSubtract( startDate: Expression::variable('NOW'), - unit: 'week', + unit: TimeUnit::Week, amount: 1, ), ), @@ -118,7 +119,7 @@ public function testSubtractAFixedAmount(): void Stage::project( logoutTime: Expression::dateSubtract( startDate: Expression::dateFieldPath('logout'), - unit: 'hour', + unit: TimeUnit::Hour, amount: 3, ), ), diff --git a/tests/Builder/Expression/DateTruncOperatorTest.php b/tests/Builder/Expression/DateTruncOperatorTest.php index 8534c3311..37bf8592a 100644 --- a/tests/Builder/Expression/DateTruncOperatorTest.php +++ b/tests/Builder/Expression/DateTruncOperatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -24,7 +25,7 @@ public function testTruncateOrderDatesAndObtainQuantitySumInAGroupPipelineStage( _id: object( truncatedOrderDate: Expression::dateTrunc( date: Expression::dateFieldPath('orderDate'), - unit: 'month', + unit: TimeUnit::Month, binSize: 6, ), ), @@ -45,7 +46,7 @@ public function testTruncateOrderDatesInAProjectPipelineStage(): void orderDate: 1, truncatedOrderDate: Expression::dateTrunc( date: Expression::dateFieldPath('orderDate'), - unit: 'week', + unit: TimeUnit::Week, binSize: 2, timezone: 'America/Los_Angeles', startOfWeek: 'Monday', diff --git a/tests/Builder/Expression/SortArrayOperatorTest.php b/tests/Builder/Expression/SortArrayOperatorTest.php index 3b2b2ef3e..a2b738f38 100644 --- a/tests/Builder/Expression/SortArrayOperatorTest.php +++ b/tests/Builder/Expression/SortArrayOperatorTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -24,7 +25,7 @@ public function testSortAnArrayOfIntegers(): void _id: 0, result: Expression::sortArray( input: [1, 4, 1, 6, 12, 5], - sortBy: 1, + sortBy: Sort::Asc, ), ), ); @@ -41,7 +42,7 @@ public function testSortOnAField(): void input: Expression::arrayFieldPath('team'), // @todo This object should be typed as "sort spec" sortBy: object( - name: 1, + name: Sort::Asc, ), ), ), @@ -57,9 +58,8 @@ public function testSortOnASubfield(): void _id: 0, result: Expression::sortArray( input: Expression::arrayFieldPath('team'), - // @todo This array should be typed as "sort spec" sortBy: [ - 'address.city' => -1, + 'address.city' => Sort::Desc, ], ), ), @@ -87,7 +87,7 @@ public function testSortOnMixedTypeFields(): void new Decimal128('10.23'), ['a' => 'On sale'], ], - sortBy: 1, + sortBy: Sort::Asc, ), ), ); @@ -104,8 +104,8 @@ public function testSortOnMultipleFields(): void input: Expression::arrayFieldPath('team'), // @todo This array should be typed as "sort spec" sortBy: object( - age: -1, - name: 1, + age: Sort::Desc, + name: Sort::Asc, ), ), ), diff --git a/tests/Builder/Expression/SplitOperatorTest.php b/tests/Builder/Expression/SplitOperatorTest.php index 9519b2899..c302f4e9d 100644 --- a/tests/Builder/Expression/SplitOperatorTest.php +++ b/tests/Builder/Expression/SplitOperatorTest.php @@ -9,6 +9,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -43,9 +44,7 @@ public function testExample(): void ), ), Stage::sort( - object( - total_qty: -1, - ), + total_qty: Sort::Desc, ), ); diff --git a/tests/Builder/Expression/ToDateOperatorTest.php b/tests/Builder/Expression/ToDateOperatorTest.php index a7f8748fa..9b03b1121 100644 --- a/tests/Builder/Expression/ToDateOperatorTest.php +++ b/tests/Builder/Expression/ToDateOperatorTest.php @@ -7,10 +7,9 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; - /** * Test $toDate expression */ @@ -25,9 +24,7 @@ public function testExample(): void ), ), Stage::sort( - object( - convertedDate: 1, - ), + convertedDate: Sort::Asc, ), ); diff --git a/tests/Builder/Expression/ToLongOperatorTest.php b/tests/Builder/Expression/ToLongOperatorTest.php index c21fef612..53c6f1f07 100644 --- a/tests/Builder/Expression/ToLongOperatorTest.php +++ b/tests/Builder/Expression/ToLongOperatorTest.php @@ -7,10 +7,9 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; - /** * Test $toLong expression */ @@ -25,9 +24,7 @@ public function testExample(): void ), ), Stage::sort( - object( - convertedQty: -1, - ), + convertedQty: Sort::Desc, ), ); diff --git a/tests/Builder/Expression/ToObjectIdOperatorTest.php b/tests/Builder/Expression/ToObjectIdOperatorTest.php index 856c76575..396507241 100644 --- a/tests/Builder/Expression/ToObjectIdOperatorTest.php +++ b/tests/Builder/Expression/ToObjectIdOperatorTest.php @@ -7,10 +7,9 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; - /** * Test $toObjectId expression */ @@ -25,9 +24,7 @@ public function testExample(): void ), ), Stage::sort( - object( - convertedId: -1, - ), + convertedId: Sort::Desc, ), ); diff --git a/tests/Builder/Expression/ToStringOperatorTest.php b/tests/Builder/Expression/ToStringOperatorTest.php index dae89b5c7..d33cb2e3b 100644 --- a/tests/Builder/Expression/ToStringOperatorTest.php +++ b/tests/Builder/Expression/ToStringOperatorTest.php @@ -7,10 +7,9 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; - /** * Test $toString expression */ @@ -25,9 +24,7 @@ public function testExample(): void ), ), Stage::sort( - object( - convertedZipCode: 1, - ), + convertedZipCode: Sort::Asc, ), ); diff --git a/tests/Builder/Query/Pipelines.php b/tests/Builder/Query/Pipelines.php index 8394e1e65..78611649d 100644 --- a/tests/Builder/Query/Pipelines.php +++ b/tests/Builder/Query/Pipelines.php @@ -1874,7 +1874,11 @@ enum Pipelines: string "$text": { "$search": "CAF\u00c9", "$diacriticSensitive": true - }, + } + } + }, + { + "$project": { "score": { "$meta": "textScore" } diff --git a/tests/Builder/Query/TextOperatorTest.php b/tests/Builder/Query/TextOperatorTest.php index ebce1172e..813c5460c 100644 --- a/tests/Builder/Query/TextOperatorTest.php +++ b/tests/Builder/Query/TextOperatorTest.php @@ -4,9 +4,11 @@ namespace MongoDB\Tests\Builder\Query; +use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; /** @@ -97,20 +99,18 @@ public function testSearchForASingleWord(): void public function testTextSearchScoreExamples(): void { - /** - * @todo: add support for $meta: "textScore" - * @todo: add support for $sort spec with object - */ $pipeline = new Pipeline( Stage::match( Query::text( search: 'CAFÉ', diacriticSensitive: true, ), - score: ['$meta' => 'textScore'], + ), + Stage::project( + score: Expression::meta('textScore'), ), Stage::sort( - ['score' => ['$meta' => 'textScore']], + score: Sort::TextScore, ), Stage::limit(5), ); diff --git a/tests/Builder/Stage/DensifyStageTest.php b/tests/Builder/Stage/DensifyStageTest.php index 18630ed5d..142233fcf 100644 --- a/tests/Builder/Stage/DensifyStageTest.php +++ b/tests/Builder/Stage/DensifyStageTest.php @@ -8,6 +8,7 @@ use MongoDB\BSON\UTCDateTime; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -40,7 +41,7 @@ public function testDensifyTimeSeriesData(): void field: 'timestamp', range: object( step: 1, - unit: 'hour', + unit: TimeUnit::Hour, bounds: [ new UTCDateTime(new DateTimeImmutable('2021-05-18T00:00:00.000Z')), new UTCDateTime(new DateTimeImmutable('2021-05-18T08:00:00.000Z')), diff --git a/tests/Builder/Stage/FillStageTest.php b/tests/Builder/Stage/FillStageTest.php index 6d11796e1..e464cfaef 100644 --- a/tests/Builder/Stage/FillStageTest.php +++ b/tests/Builder/Stage/FillStageTest.php @@ -7,6 +7,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -21,7 +22,7 @@ public function testFillDataForDistinctPartitions(): void $pipeline = new Pipeline( Stage::fill( sortBy: object( - date: 1, + date: Sort::Asc, ), partitionBy: object( restaurant: Expression::stringFieldPath('restaurant'), @@ -40,7 +41,7 @@ public function testFillMissingFieldValuesBasedOnTheLastObservedValue(): void $pipeline = new Pipeline( Stage::fill( sortBy: object( - date: 1, + date: Sort::Asc, ), output: object( score: object(method: 'locf'), @@ -71,7 +72,7 @@ public function testFillMissingFieldValuesWithLinearInterpolation(): void $pipeline = new Pipeline( Stage::fill( sortBy: object( - time: 1, + time: Sort::Asc, ), output: object( price: object(method: 'linear'), @@ -97,7 +98,7 @@ public function testIndicateIfAFieldWasPopulatedUsingFill(): void ), Stage::fill( sortBy: object( - date: 1, + date: Sort::Asc, ), output: object( score: object(method: 'locf'), diff --git a/tests/Builder/Stage/GroupStageTest.php b/tests/Builder/Stage/GroupStageTest.php index 74bfe0265..ca742312f 100644 --- a/tests/Builder/Stage/GroupStageTest.php +++ b/tests/Builder/Stage/GroupStageTest.php @@ -11,10 +11,9 @@ use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; - /** * Test $group stage */ @@ -43,9 +42,7 @@ public function testCalculateCountSumAndAverage(): void count: Accumulator::sum(1), ), Stage::sort( - object( - totalSaleAmount: -1, - ), + totalSaleAmount: Sort::Desc, ), ); diff --git a/tests/Builder/Stage/SetWindowFieldsStageTest.php b/tests/Builder/Stage/SetWindowFieldsStageTest.php index f14c94992..442d959bf 100644 --- a/tests/Builder/Stage/SetWindowFieldsStageTest.php +++ b/tests/Builder/Stage/SetWindowFieldsStageTest.php @@ -8,6 +8,8 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Tests\Builder\PipelineTestCase; use function MongoDB\object; @@ -22,7 +24,7 @@ public function testRangeWindowExample(): void $pipeline = new Pipeline( Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), - sortBy: object(price: 1), + sortBy: object(price: Sort::Asc), output: object( quantityFromSimilarOrders: Accumulator::outputWindow( Accumulator::sum( @@ -42,14 +44,14 @@ public function testUseATimeRangeWindowWithANegativeUpperBound(): void $pipeline = new Pipeline( Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), - sortBy: object(orderDate: 1), + sortBy: object(orderDate: Sort::Asc), output: object( recentOrders: Accumulator::outputWindow( Accumulator::push( Expression::dateFieldPath('orderDate'), ), range: ['unbounded', -10], - unit: 'month', + unit: TimeUnit::Month, ), ), ), @@ -63,14 +65,14 @@ public function testUseATimeRangeWindowWithAPositiveUpperBound(): void $pipeline = new Pipeline( Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), - sortBy: object(orderDate: 1), + sortBy: object(orderDate: Sort::Asc), output: object( recentOrders: Accumulator::outputWindow( Accumulator::push( Expression::dateFieldPath('orderDate'), ), range: ['unbounded', 10], - unit: 'month', + unit: TimeUnit::Month, ), ), ), @@ -86,7 +88,7 @@ public function testUseDocumentsWindowToObtainCumulativeAndMaximumQuantityForEac partitionBy: Expression::year( Expression::dateFieldPath('orderDate'), ), - sortBy: object(orderDate: 1), + sortBy: object(orderDate: Sort::Asc), output: object( cumulativeQuantityForYear: Accumulator::outputWindow( Accumulator::sum( @@ -112,7 +114,7 @@ public function testUseDocumentsWindowToObtainCumulativeQuantityForEachState(): $pipeline = new Pipeline( Stage::setWindowFields( partitionBy: Expression::stringFieldPath('state'), - sortBy: object(orderDate: 1), + sortBy: object(orderDate: Sort::Asc), output: object( cumulativeQuantityForState: Accumulator::outputWindow( Accumulator::sum( @@ -134,7 +136,7 @@ public function testUseDocumentsWindowToObtainCumulativeQuantityForEachYear(): v partitionBy: Expression::year( Expression::dateFieldPath('orderDate'), ), - sortBy: object(orderDate: 1), + sortBy: object(orderDate: Sort::Asc), output: object( cumulativeQuantityForYear: Accumulator::outputWindow( Accumulator::sum( @@ -156,7 +158,7 @@ public function testUseDocumentsWindowToObtainMovingAverageQuantityForEachYear() partitionBy: Expression::year( Expression::dateFieldPath('orderDate'), ), - sortBy: object(orderDate: 1), + sortBy: object(orderDate: Sort::Asc), output: object( averageQuantity: Accumulator::outputWindow( Accumulator::avg( diff --git a/tests/Builder/Stage/SortStageTest.php b/tests/Builder/Stage/SortStageTest.php index 2b9a093d6..5590454bc 100644 --- a/tests/Builder/Stage/SortStageTest.php +++ b/tests/Builder/Stage/SortStageTest.php @@ -7,10 +7,9 @@ use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; - /** * Test $sort stage */ @@ -20,10 +19,8 @@ public function testAscendingDescendingSort(): void { $pipeline = new Pipeline( Stage::sort( - object( - age: -1, - posts: 1, - ), + age: Sort::Desc, + posts: Sort::Asc, ), ); @@ -37,10 +34,8 @@ public function testTextScoreMetadataSort(): void Query::text('operating'), ), Stage::sort( - object( - score: ['$meta' => 'textScore'], - posts: -1, - ), + score: Sort::TextScore, + posts: Sort::Desc, ), ); diff --git a/tests/Builder/Stage/UnionWithStageTest.php b/tests/Builder/Stage/UnionWithStageTest.php index dc6ebcb89..fcf2edf90 100644 --- a/tests/Builder/Stage/UnionWithStageTest.php +++ b/tests/Builder/Stage/UnionWithStageTest.php @@ -8,10 +8,9 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; - /** * Test $unionWith stage */ @@ -48,11 +47,9 @@ public function testReport1AllSalesByYearAndStoresAndItems(): void ), ), Stage::sort( - object( - _id: 1, - store: 1, - item: 1, - ), + _id: Sort::Asc, + store: Sort::Asc, + item: Sort::Asc, ), ); @@ -72,9 +69,7 @@ public function testReport2AggregatedSalesByItems(): void ), ), Stage::sort( - object( - total: -1, - ), + total: Sort::Desc, ), ); diff --git a/tests/Builder/Stage/UnwindStageTest.php b/tests/Builder/Stage/UnwindStageTest.php index c572c0240..26b1d377c 100644 --- a/tests/Builder/Stage/UnwindStageTest.php +++ b/tests/Builder/Stage/UnwindStageTest.php @@ -8,10 +8,9 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; +use MongoDB\Builder\Type\Sort; use MongoDB\Tests\Builder\PipelineTestCase; -use function MongoDB\object; - /** * Test $unwind stage */ @@ -31,9 +30,7 @@ public function testGroupByUnwoundValues(): void ), ), Stage::sort( - object( - averagePrice: -1, - ), + averagePrice: Sort::Desc, ), ); diff --git a/tests/Builder/Type/OutputWindowTest.php b/tests/Builder/Type/OutputWindowTest.php index 46245e718..fed42a3eb 100644 --- a/tests/Builder/Type/OutputWindowTest.php +++ b/tests/Builder/Type/OutputWindowTest.php @@ -7,6 +7,7 @@ use Generator; use MongoDB\Builder\Type\Optional; use MongoDB\Builder\Type\OutputWindow; +use MongoDB\Builder\Type\TimeUnit; use MongoDB\Builder\Type\WindowInterface; use MongoDB\Exception\InvalidArgumentException; use PHPUnit\Framework\TestCase; @@ -49,11 +50,11 @@ public function testWithUnit(): void { $outputWindow = new OutputWindow( operator: $operator = $this->createMock(WindowInterface::class), - unit: 'day', + unit: TimeUnit::Day, ); $this->assertSame($operator, $outputWindow->operator); - $this->assertEquals((object) ['unit' => 'day'], $outputWindow->window); + $this->assertEquals((object) ['unit' => TimeUnit::Day], $outputWindow->window); } /** From 5ddbc2fa0d43cd7f9788de620330d081677eeca6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Mar 2024 09:52:57 +0100 Subject: [PATCH 60/95] Bump ramsey/composer-install from 2.2.0 to 3.0.0 (#66) Bumps [ramsey/composer-install](https://github.com/ramsey/composer-install) from 2.2.0 to 3.0.0. - [Release notes](https://github.com/ramsey/composer-install/releases) - [Commits](https://github.com/ramsey/composer-install/compare/2.2.0...3.0.0) --- updated-dependencies: - dependency-name: ramsey/composer-install dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/coding-standards.yml | 2 +- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 650ae577b..6447d978e 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -54,7 +54,7 @@ jobs: run: "php --ri mongodb" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@2.2.0" + uses: "ramsey/composer-install@3.0.0" with: composer-options: "--no-suggest" diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index a2b4fd883..7ca6eca13 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -54,7 +54,7 @@ jobs: run: "php --ri mongodb" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@2.2.0" + uses: "ramsey/composer-install@3.0.0" with: composer-options: "--no-suggest" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e3caaabc2..8d4c44595 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -75,7 +75,7 @@ jobs: run: "php --ri mongodb" - name: "Install dependencies with Composer" - uses: "ramsey/composer-install@2.2.0" + uses: "ramsey/composer-install@3.0.0" with: composer-options: "--no-suggest" From 0622bb2c46f15e21cad1a90110d56a7cf09ebf4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 8 Mar 2024 10:30:45 +0100 Subject: [PATCH 61/95] Fix links --- src/Builder/Type/ExpressionInterface.php | 2 +- src/Builder/Type/StageInterface.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Builder/Type/ExpressionInterface.php b/src/Builder/Type/ExpressionInterface.php index 8715d68b8..8b11479bd 100644 --- a/src/Builder/Type/ExpressionInterface.php +++ b/src/Builder/Type/ExpressionInterface.php @@ -8,7 +8,7 @@ * Expressions can include field paths, literals, system variables, expression objects, and expression operators. * Expressions can be nested. * - * @see https://www.mongodb.com/docs/manual/meta/aggregation-quick-reference/#expressions + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/ */ interface ExpressionInterface { diff --git a/src/Builder/Type/StageInterface.php b/src/Builder/Type/StageInterface.php index 2e4d4dd66..6d4fcf2e6 100644 --- a/src/Builder/Type/StageInterface.php +++ b/src/Builder/Type/StageInterface.php @@ -7,7 +7,7 @@ /** * Aggregation Pipeline Stages * - * @see https://www.mongodb.com/docs/manual/meta/aggregation-quick-reference/#stages + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/ */ interface StageInterface { From 12d951c4da3e609388c706a79aa755e059f6ac29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 13 Mar 2024 17:44:20 +0100 Subject: [PATCH 62/95] Add fluent builder for Laravel (#67) --- generator/config/definitions.php | 2 + generator/src/FluentStageFactoryGenerator.php | 132 +++ src/Builder/Stage/FluentFactoryTrait.php | 792 ++++++++++++++++++ tests/Builder/FluentPipelineFactoryTest.php | 34 + 4 files changed, 960 insertions(+) create mode 100644 generator/src/FluentStageFactoryGenerator.php create mode 100644 src/Builder/Stage/FluentFactoryTrait.php create mode 100644 tests/Builder/FluentPipelineFactoryTest.php diff --git a/generator/config/definitions.php b/generator/config/definitions.php index ca3854c2f..a4f952d39 100644 --- a/generator/config/definitions.php +++ b/generator/config/definitions.php @@ -4,6 +4,7 @@ namespace MongoDB\CodeGenerator\Config; +use MongoDB\CodeGenerator\FluentStageFactoryGenerator; use MongoDB\CodeGenerator\OperatorClassGenerator; use MongoDB\CodeGenerator\OperatorFactoryGenerator; use MongoDB\CodeGenerator\OperatorTestGenerator; @@ -18,6 +19,7 @@ OperatorClassGenerator::class, OperatorFactoryGenerator::class, OperatorTestGenerator::class, + FluentStageFactoryGenerator::class, ], ], diff --git a/generator/src/FluentStageFactoryGenerator.php b/generator/src/FluentStageFactoryGenerator.php new file mode 100644 index 000000000..2dfcee04a --- /dev/null +++ b/generator/src/FluentStageFactoryGenerator.php @@ -0,0 +1,132 @@ +writeFile($this->createFluentFactoryTrait($definition)); + } + + private function createFluentFactoryTrait(GeneratorDefinition $definition): PhpNamespace + { + $namespace = new PhpNamespace($definition->namespace); + $trait = $namespace->addTrait('FluentFactoryTrait'); + + $namespace->addUse(self::FACTORY_CLASS); + $namespace->addUse(StageInterface::class); + $namespace->addUse(Pipeline::class); + + $trait->addProperty('pipeline') + ->setType('array') + ->setComment('@var list') + ->setValue([]); + $trait->addMethod('getPipeline') + ->setReturnType(Pipeline::class) + ->setBody(<<<'PHP' + return new Pipeline(...$this->pipeline); + PHP); + + $this->addUsesFrom(self::FACTORY_CLASS, $namespace); + $staticFactory = ClassType::from(self::FACTORY_CLASS); + assert($staticFactory instanceof ClassType); + + // Import the methods customized in the factory class + foreach ($staticFactory->getMethods() as $method) { + $this->addMethod($method, $trait); + } + + // Import the other methods provided by the generated trait + foreach ($staticFactory->getTraits() as $usedTrait) { + $this->addUsesFrom($usedTrait->getName(), $namespace); + $staticFactory = TraitType::from($usedTrait->getName()); + assert($staticFactory instanceof TraitType); + foreach ($staticFactory->getMethods() as $method) { + $this->addMethod($method, $trait); + } + } + + return $namespace; + } + + private function addMethod(Method $factoryMethod, TraitType $trait): void + { + // Non-public methods are not part of the API + if (! $factoryMethod->isPublic()) { + return; + } + + // Some methods can be overridden in the class, so we skip them + // when importing the methods provided by the trait. + if ($trait->hasMethod($factoryMethod->getName())) { + return; + } + + $method = $trait->addMethod($factoryMethod->getName()); + + $method->setComment($factoryMethod->getComment()); + $method->setParameters($factoryMethod->getParameters()); + + $args = array_map( + fn (Parameter $param): string => '$' . $param->getName(), + $factoryMethod->getParameters(), + ); + + if ($factoryMethod->isVariadic()) { + $method->setVariadic(); + $args[array_key_last($args)] = '...' . $args[array_key_last($args)]; + } + + $method->setReturnType('static'); + $method->setBody(sprintf( + <<<'PHP' + $this->pipeline[] = %s::%s(%s); + + return $this; + PHP, + (new ReflectionClass(self::FACTORY_CLASS))->getShortName(), + $factoryMethod->getName(), + implode(', ', $args), + )); + } + + private static function addUsesFrom(string $classLike, PhpNamespace $namespace): void + { + $file = PhpFile::fromCode(file_get_contents((new ReflectionClass($classLike))->getFileName())); + + foreach ($file->getNamespaces() as $ns) { + foreach ($ns->getUses() as $use) { + $namespace->addUse($use); + } + } + } +} diff --git a/src/Builder/Stage/FluentFactoryTrait.php b/src/Builder/Stage/FluentFactoryTrait.php new file mode 100644 index 000000000..0ffbcd47a --- /dev/null +++ b/src/Builder/Stage/FluentFactoryTrait.php @@ -0,0 +1,792 @@ + */ + public array $pipeline = []; + + public function getPipeline(): Pipeline + { + return new Pipeline(...$this->pipeline); + } + + /** + * Filters the document stream to allow only matching documents to pass unmodified into the next pipeline stage. $match uses standard MongoDB queries. For each input document, outputs either one document (a match) or zero documents (no match). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/match/ + * + * @param QueryInterface|FieldQueryInterface|Type|stdClass|array|bool|float|int|string|null ...$queries The query predicates to match + */ + public function match( + QueryInterface|FieldQueryInterface|Type|stdClass|array|string|int|float|bool|null ...$queries, + ): static + { + $this->pipeline[] = Stage::match(...$queries); + + return $this; + } + + /** + * Adds new fields to documents. Outputs documents that contain all existing fields from the input documents and newly added fields. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/addFields/ + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$expression Specify the name of each field to add and set its value to an aggregation expression or an empty object. + */ + public function addFields( + Type|ExpressionInterface|stdClass|array|string|int|float|bool|null ...$expression, + ): static + { + $this->pipeline[] = Stage::addFields(...$expression); + + return $this; + } + + /** + * Categorizes incoming documents into groups, called buckets, based on a specified expression and bucket boundaries. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucket/ + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. + * Unless $bucket includes a default specification, each input document must resolve the groupBy field path or expression to a value that falls within one of the ranges specified by the boundaries. + * @param BSONArray|PackedArray|array $boundaries An array of values based on the groupBy expression that specify the boundaries for each bucket. Each adjacent pair of values acts as the inclusive lower boundary and the exclusive upper boundary for the bucket. You must specify at least two boundaries. + * The specified values must be in ascending order and all of the same type. The exception is if the values are of mixed numeric types, such as: + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $default A literal that specifies the _id of an additional bucket that contains all documents whose groupBy expression result does not fall into a bucket specified by boundaries. + * If unspecified, each input document must resolve the groupBy expression to a value within one of the bucket ranges specified by boundaries or the operation throws an error. + * The default value must be less than the lowest boundaries value, or greater than or equal to the highest boundaries value. + * The default value can be of a different type than the entries in boundaries. + * @param Optional|Document|Serializable|array|stdClass $output A document that specifies the fields to include in the output documents in addition to the _id field. To specify the field to include, you must use accumulator expressions. + * If you do not specify an output document, the operation returns a count field containing the number of documents in each bucket. + * If you specify an output document, only the fields specified in the document are returned; i.e. the count field is not returned unless it is explicitly included in the output document. + */ + public function bucket( + Type|ExpressionInterface|stdClass|array|string|int|float|bool|null $groupBy, + PackedArray|BSONArray|array $boundaries, + Optional|Type|ExpressionInterface|stdClass|array|string|int|float|bool|null $default = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $output = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::bucket($groupBy, $boundaries, $default, $output); + + return $this; + } + + /** + * Categorizes incoming documents into a specific number of groups, called buckets, based on a specified expression. Bucket boundaries are automatically determined in an attempt to evenly distribute the documents into the specified number of buckets. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/bucketAuto/ + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $groupBy An expression to group documents by. To specify a field path, prefix the field name with a dollar sign $ and enclose it in quotes. + * @param int $buckets A positive 32-bit integer that specifies the number of buckets into which input documents are grouped. + * @param Optional|Document|Serializable|array|stdClass $output A document that specifies the fields to include in the output documents in addition to the _id field. To specify the field to include, you must use accumulator expressions. + * The default count field is not included in the output document when output is specified. Explicitly specify the count expression as part of the output document to include it. + * @param Optional|Document|Serializable|array|stdClass $granularity A string that specifies the preferred number series to use to ensure that the calculated boundary edges end on preferred round numbers or their powers of 10. + * Available only if the all groupBy values are numeric and none of them are NaN. + */ + public function bucketAuto( + Type|ExpressionInterface|stdClass|array|string|int|float|bool|null $groupBy, + int $buckets, + Optional|Document|Serializable|stdClass|array $output = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $granularity = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::bucketAuto($groupBy, $buckets, $output, $granularity); + + return $this; + } + + /** + * Returns a Change Stream cursor for the collection or database. This stage can only occur once in an aggregation pipeline and it must occur as the first stage. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/changeStream/ + * @param Optional|bool $allChangesForCluster A flag indicating whether the stream should report all changes that occur on the deployment, aside from those on internal databases or collections. + * @param Optional|string $fullDocument Specifies whether change notifications include a copy of the full document when modified by update operations. + * @param Optional|string $fullDocumentBeforeChange Valid values are "off", "whenAvailable", or "required". If set to "off", the "fullDocumentBeforeChange" field of the output document is always omitted. If set to "whenAvailable", the "fullDocumentBeforeChange" field will be populated with the pre-image of the document modified by the current change event if such a pre-image is available, and will be omitted otherwise. If set to "required", then the "fullDocumentBeforeChange" field is always populated and an exception is thrown if the pre-image is not available. + * @param Optional|int $resumeAfter Specifies a resume token as the logical starting point for the change stream. Cannot be used with startAfter or startAtOperationTime fields. + * @param Optional|bool $showExpandedEvents Specifies whether to include additional change events, such as such as DDL and index operations. + * New in MongoDB 6.0. + * @param Optional|Document|Serializable|array|stdClass $startAfter Specifies a resume token as the logical starting point for the change stream. Cannot be used with resumeAfter or startAtOperationTime fields. + * @param Optional|Timestamp|int $startAtOperationTime Specifies a time as the logical starting point for the change stream. Cannot be used with resumeAfter or startAfter fields. + */ + public function changeStream( + Optional|bool $allChangesForCluster = Optional::Undefined, + Optional|string $fullDocument = Optional::Undefined, + Optional|string $fullDocumentBeforeChange = Optional::Undefined, + Optional|int $resumeAfter = Optional::Undefined, + Optional|bool $showExpandedEvents = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $startAfter = Optional::Undefined, + Optional|Timestamp|int $startAtOperationTime = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::changeStream($allChangesForCluster, $fullDocument, $fullDocumentBeforeChange, $resumeAfter, $showExpandedEvents, $startAfter, $startAtOperationTime); + + return $this; + } + + /** + * Splits large change stream events that exceed 16 MB into smaller fragments returned in a change stream cursor. + * You can only use $changeStreamSplitLargeEvent in a $changeStream pipeline and it must be the final stage in the pipeline. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/changeStreamSplitLargeEvent/ + */ + public function changeStreamSplitLargeEvent(): static + { + $this->pipeline[] = Stage::changeStreamSplitLargeEvent(); + + return $this; + } + + /** + * Returns statistics regarding a collection or view. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/collStats/ + * @param Optional|Document|Serializable|array|stdClass $latencyStats + * @param Optional|Document|Serializable|array|stdClass $storageStats + * @param Optional|Document|Serializable|array|stdClass $count + * @param Optional|Document|Serializable|array|stdClass $queryExecStats + */ + public function collStats( + Optional|Document|Serializable|stdClass|array $latencyStats = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $storageStats = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $count = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $queryExecStats = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::collStats($latencyStats, $storageStats, $count, $queryExecStats); + + return $this; + } + + /** + * Returns a count of the number of documents at this stage of the aggregation pipeline. + * Distinct from the $count aggregation accumulator. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/count/ + * @param string $field Name of the output field which has the count as its value. It must be a non-empty string, must not start with $ and must not contain the . character. + */ + public function count(string $field): static + { + $this->pipeline[] = Stage::count($field); + + return $this; + } + + /** + * Returns information on active and/or dormant operations for the MongoDB deployment. To run, use the db.aggregate() method. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/currentOp/ + * @param Optional|bool $allUsers + * @param Optional|bool $idleConnections + * @param Optional|bool $idleCursors + * @param Optional|bool $idleSessions + * @param Optional|bool $localOps + */ + public function currentOp( + Optional|bool $allUsers = Optional::Undefined, + Optional|bool $idleConnections = Optional::Undefined, + Optional|bool $idleCursors = Optional::Undefined, + Optional|bool $idleSessions = Optional::Undefined, + Optional|bool $localOps = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::currentOp($allUsers, $idleConnections, $idleCursors, $idleSessions, $localOps); + + return $this; + } + + /** + * Creates new documents in a sequence of documents where certain values in a field are missing. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/densify/ + * @param string $field The field to densify. The values of the specified field must either be all numeric values or all dates. + * Documents that do not contain the specified field continue through the pipeline unmodified. + * To specify a in an embedded document or in an array, use dot notation. + * @param Document|Serializable|array|stdClass $range Specification for range based densification. + * @param Optional|BSONArray|PackedArray|array $partitionByFields The field(s) that will be used as the partition keys. + */ + public function densify( + string $field, + Document|Serializable|stdClass|array $range, + Optional|PackedArray|BSONArray|array $partitionByFields = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::densify($field, $range, $partitionByFields); + + return $this; + } + + /** + * Returns literal documents from input values. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/documents/ + * @param BSONArray|PackedArray|ResolvesToArray|array $documents $documents accepts any valid expression that resolves to an array of objects. This includes: + * - system variables, such as $$NOW or $$SEARCH_META + * - $let expressions + * - variables in scope from $lookup expressions + * Expressions that do not resolve to a current document, like $myField or $$ROOT, will result in an error. + */ + public function documents(PackedArray|ResolvesToArray|BSONArray|array $documents): static + { + $this->pipeline[] = Stage::documents($documents); + + return $this; + } + + /** + * Processes multiple aggregation pipelines within a single stage on the same set of input documents. Enables the creation of multi-faceted aggregations capable of characterizing data across multiple dimensions, or facets, in a single stage. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/facet/ + * @param BSONArray|PackedArray|Pipeline|array ...$facet + */ + public function facet(PackedArray|Pipeline|BSONArray|array ...$facet): static + { + $this->pipeline[] = Stage::facet(...$facet); + + return $this; + } + + /** + * Populates null and missing field values within documents. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/fill/ + * @param Document|Serializable|array|stdClass $output Specifies an object containing each field for which to fill missing values. You can specify multiple fields in the output object. + * The object name is the name of the field to fill. The object value specifies how the field is filled. + * @param Optional|Document|Serializable|array|stdClass|string $partitionBy Specifies an expression to group the documents. In the $fill stage, a group of documents is known as a partition. + * If you omit partitionBy and partitionByFields, $fill uses one partition for the entire collection. + * partitionBy and partitionByFields are mutually exclusive. + * @param Optional|BSONArray|PackedArray|array $partitionByFields Specifies an array of fields as the compound key to group the documents. In the $fill stage, each group of documents is known as a partition. + * If you omit partitionBy and partitionByFields, $fill uses one partition for the entire collection. + * partitionBy and partitionByFields are mutually exclusive. + * @param Optional|Document|Serializable|array|stdClass $sortBy Specifies the field or fields to sort the documents within each partition. Uses the same syntax as the $sort stage. + */ + public function fill( + Document|Serializable|stdClass|array $output, + Optional|Document|Serializable|stdClass|array|string $partitionBy = Optional::Undefined, + Optional|PackedArray|BSONArray|array $partitionByFields = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $sortBy = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::fill($output, $partitionBy, $partitionByFields, $sortBy); + + return $this; + } + + /** + * Returns an ordered stream of documents based on the proximity to a geospatial point. Incorporates the functionality of $match, $sort, and $limit for geospatial data. The output documents include an additional distance field and can include a location identifier field. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/geoNear/ + * @param string $distanceField The output field that contains the calculated distance. To specify a field within an embedded document, use dot notation. + * @param Document|ResolvesToObject|Serializable|array|stdClass $near The point for which to find the closest documents. + * @param Optional|Decimal128|Int64|float|int $distanceMultiplier The factor to multiply all distances returned by the query. For example, use the distanceMultiplier to convert radians, as returned by a spherical query, to kilometers by multiplying by the radius of the Earth. + * @param Optional|string $includeLocs This specifies the output field that identifies the location used to calculate the distance. This option is useful when a location field contains multiple locations. To specify a field within an embedded document, use dot notation. + * @param Optional|string $key Specify the geospatial indexed field to use when calculating the distance. + * @param Optional|Decimal128|Int64|float|int $maxDistance The maximum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall within the specified distance from the center point. + * Specify the distance in meters if the specified point is GeoJSON and in radians if the specified point is legacy coordinate pairs. + * @param Optional|Decimal128|Int64|float|int $minDistance The minimum distance from the center point that the documents can be. MongoDB limits the results to those documents that fall outside the specified distance from the center point. + * Specify the distance in meters for GeoJSON data and in radians for legacy coordinate pairs. + * @param Optional|QueryInterface|array $query Limits the results to the documents that match the query. The query syntax is the usual MongoDB read operation query syntax. + * You cannot specify a $near predicate in the query field of the $geoNear stage. + * @param Optional|bool $spherical Determines how MongoDB calculates the distance between two points: + * - When true, MongoDB uses $nearSphere semantics and calculates distances using spherical geometry. + * - When false, MongoDB uses $near semantics: spherical geometry for 2dsphere indexes and planar geometry for 2d indexes. + * Default: false. + */ + public function geoNear( + string $distanceField, + Document|Serializable|ResolvesToObject|stdClass|array $near, + Optional|Decimal128|Int64|int|float $distanceMultiplier = Optional::Undefined, + Optional|string $includeLocs = Optional::Undefined, + Optional|string $key = Optional::Undefined, + Optional|Decimal128|Int64|int|float $maxDistance = Optional::Undefined, + Optional|Decimal128|Int64|int|float $minDistance = Optional::Undefined, + Optional|QueryInterface|array $query = Optional::Undefined, + Optional|bool $spherical = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::geoNear($distanceField, $near, $distanceMultiplier, $includeLocs, $key, $maxDistance, $minDistance, $query, $spherical); + + return $this; + } + + /** + * Performs a recursive search on a collection. To each output document, adds a new array field that contains the traversal results of the recursive search for that document. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/ + * @param string $from Target collection for the $graphLookup operation to search, recursively matching the connectFromField to the connectToField. The from collection must be in the same database as any other collections used in the operation. + * Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. + * @param BSONArray|ExpressionInterface|PackedArray|Type|array|bool|float|int|null|stdClass|string $startWith Expression that specifies the value of the connectFromField with which to start the recursive search. Optionally, startWith may be array of values, each of which is individually followed through the traversal process. + * @param string $connectFromField Field name whose value $graphLookup uses to recursively match against the connectToField of other documents in the collection. If the value is an array, each element is individually followed through the traversal process. + * @param string $connectToField Field name in other documents against which to match the value of the field specified by the connectFromField parameter. + * @param string $as Name of the array field added to each output document. Contains the documents traversed in the $graphLookup stage to reach the document. + * @param Optional|int $maxDepth Non-negative integral number specifying the maximum recursion depth. + * @param Optional|string $depthField Name of the field to add to each traversed document in the search path. The value of this field is the recursion depth for the document, represented as a NumberLong. Recursion depth value starts at zero, so the first lookup corresponds to zero depth. + * @param Optional|QueryInterface|array $restrictSearchWithMatch A document specifying additional conditions for the recursive search. The syntax is identical to query filter syntax. + */ + public function graphLookup( + string $from, + PackedArray|Type|ExpressionInterface|BSONArray|stdClass|array|string|int|float|bool|null $startWith, + string $connectFromField, + string $connectToField, + string $as, + Optional|int $maxDepth = Optional::Undefined, + Optional|string $depthField = Optional::Undefined, + Optional|QueryInterface|array $restrictSearchWithMatch = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::graphLookup($from, $startWith, $connectFromField, $connectToField, $as, $maxDepth, $depthField, $restrictSearchWithMatch); + + return $this; + } + + /** + * Groups input documents by a specified identifier expression and applies the accumulator expression(s), if specified, to each group. Consumes all input documents and outputs one document per each distinct group. The output documents only contain the identifier field and, if specified, accumulated fields. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/ + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $_id The _id expression specifies the group key. If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. + * @param AccumulatorInterface|Document|Serializable|array|stdClass ...$field Computed using the accumulator operators. + */ + public function group( + Type|ExpressionInterface|stdClass|array|string|int|float|bool|null $_id, + Document|Serializable|AccumulatorInterface|stdClass|array ...$field, + ): static + { + $this->pipeline[] = Stage::group($_id, ...$field); + + return $this; + } + + /** + * Returns statistics regarding the use of each index for the collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/indexStats/ + */ + public function indexStats(): static + { + $this->pipeline[] = Stage::indexStats(); + + return $this; + } + + /** + * Passes the first n documents unmodified to the pipeline where n is the specified limit. For each input document, outputs either one document (for the first n documents) or zero documents (after the first n documents). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/limit/ + * @param int $limit + */ + public function limit(int $limit): static + { + $this->pipeline[] = Stage::limit($limit); + + return $this; + } + + /** + * Lists all active sessions recently in use on the currently connected mongos or mongod instance. These sessions may have not yet propagated to the system.sessions collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listLocalSessions/ + * @param Optional|BSONArray|PackedArray|array $users Returns all sessions for the specified users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster to list sessions for other users. + * @param Optional|bool $allUsers Returns all sessions for all users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster. + */ + public function listLocalSessions( + Optional|PackedArray|BSONArray|array $users = Optional::Undefined, + Optional|bool $allUsers = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::listLocalSessions($users, $allUsers); + + return $this; + } + + /** + * Lists sampled queries for all collections or a specific collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSampledQueries/ + * @param Optional|string $namespace + */ + public function listSampledQueries(Optional|string $namespace = Optional::Undefined): static + { + $this->pipeline[] = Stage::listSampledQueries($namespace); + + return $this; + } + + /** + * Returns information about existing Atlas Search indexes on a specified collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSearchIndexes/ + * @param Optional|string $id The id of the index to return information about. + * @param Optional|string $name The name of the index to return information about. + */ + public function listSearchIndexes( + Optional|string $id = Optional::Undefined, + Optional|string $name = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::listSearchIndexes($id, $name); + + return $this; + } + + /** + * Lists all sessions that have been active long enough to propagate to the system.sessions collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/listSessions/ + * @param Optional|BSONArray|PackedArray|array $users Returns all sessions for the specified users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster to list sessions for other users. + * @param Optional|bool $allUsers Returns all sessions for all users. If running with access control, the authenticated user must have privileges with listSessions action on the cluster. + */ + public function listSessions( + Optional|PackedArray|BSONArray|array $users = Optional::Undefined, + Optional|bool $allUsers = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::listSessions($users, $allUsers); + + return $this; + } + + /** + * Performs a left outer join to another collection in the same database to filter in documents from the "joined" collection for processing. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/lookup/ + * @param string $as Specifies the name of the new array field to add to the input documents. The new array field contains the matching documents from the from collection. If the specified name already exists in the input document, the existing field is overwritten. + * @param Optional|string $from Specifies the collection in the same database to perform the join with. + * from is optional, you can use a $documents stage in a $lookup stage instead. For an example, see Use a $documents Stage in a $lookup Stage. + * Starting in MongoDB 5.1, the collection specified in the from parameter can be sharded. + * @param Optional|string $localField Specifies the field from the documents input to the $lookup stage. $lookup performs an equality match on the localField to the foreignField from the documents of the from collection. If an input document does not contain the localField, the $lookup treats the field as having a value of null for matching purposes. + * @param Optional|string $foreignField Specifies the field from the documents in the from collection. $lookup performs an equality match on the foreignField to the localField from the input documents. If a document in the from collection does not contain the foreignField, the $lookup treats the value as null for matching purposes. + * @param Optional|Document|Serializable|array|stdClass $let Specifies variables to use in the pipeline stages. Use the variable expressions to access the fields from the joined collection's documents that are input to the pipeline. + * @param Optional|BSONArray|PackedArray|Pipeline|array $pipeline Specifies the pipeline to run on the joined collection. The pipeline determines the resulting documents from the joined collection. To return all documents, specify an empty pipeline []. + * The pipeline cannot include the $out stage or the $merge stage. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. + * The pipeline cannot directly access the joined document fields. Instead, define variables for the joined document fields using the let option and then reference the variables in the pipeline stages. + */ + public function lookup( + string $as, + Optional|string $from = Optional::Undefined, + Optional|string $localField = Optional::Undefined, + Optional|string $foreignField = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $let = Optional::Undefined, + Optional|PackedArray|Pipeline|BSONArray|array $pipeline = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::lookup($as, $from, $localField, $foreignField, $let, $pipeline); + + return $this; + } + + /** + * Writes the resulting documents of the aggregation pipeline to a collection. The stage can incorporate (insert new documents, merge documents, replace documents, keep existing documents, fail the operation, process documents with a custom update pipeline) the results into an output collection. To use the $merge stage, it must be the last stage in the pipeline. + * New in MongoDB 4.2. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/merge/ + * @param Document|Serializable|array|stdClass|string $into The output collection. + * @param Optional|BSONArray|PackedArray|array|string $on Field or fields that act as a unique identifier for a document. The identifier determines if a results document matches an existing document in the output collection. + * @param Optional|Document|Serializable|array|stdClass $let Specifies variables for use in the whenMatched pipeline. + * @param Optional|BSONArray|PackedArray|Pipeline|array|string $whenMatched The behavior of $merge if a result document and an existing document in the collection have the same value for the specified on field(s). + * @param Optional|string $whenNotMatched The behavior of $merge if a result document does not match an existing document in the out collection. + */ + public function merge( + Document|Serializable|stdClass|array|string $into, + Optional|PackedArray|BSONArray|array|string $on = Optional::Undefined, + Optional|Document|Serializable|stdClass|array $let = Optional::Undefined, + Optional|PackedArray|Pipeline|BSONArray|array|string $whenMatched = Optional::Undefined, + Optional|string $whenNotMatched = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::merge($into, $on, $let, $whenMatched, $whenNotMatched); + + return $this; + } + + /** + * Writes the resulting documents of the aggregation pipeline to a collection. To use the $out stage, it must be the last stage in the pipeline. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/out/ + * @param Document|Serializable|array|stdClass|string $coll Target database name to write documents from $out to. + */ + public function out(Document|Serializable|stdClass|array|string $coll): static + { + $this->pipeline[] = Stage::out($coll); + + return $this; + } + + /** + * Returns plan cache information for a collection. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/planCacheStats/ + */ + public function planCacheStats(): static + { + $this->pipeline[] = Stage::planCacheStats(); + + return $this; + } + + /** + * Reshapes each document in the stream, such as by adding new fields or removing existing fields. For each input document, outputs one document. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/ + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$specification + */ + public function project( + Type|ExpressionInterface|stdClass|array|string|int|float|bool|null ...$specification, + ): static + { + $this->pipeline[] = Stage::project(...$specification); + + return $this; + } + + /** + * Reshapes each document in the stream by restricting the content for each document based on information stored in the documents themselves. Incorporates the functionality of $project and $match. Can be used to implement field level redaction. For each input document, outputs either one or zero documents. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/ + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression + */ + public function redact(Type|ExpressionInterface|stdClass|array|string|int|float|bool|null $expression): static + { + $this->pipeline[] = Stage::redact($expression); + + return $this; + } + + /** + * Replaces a document with the specified embedded document. The operation replaces all existing fields in the input document, including the _id field. Specify a document embedded in the input document to promote the embedded document to the top level. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceRoot/ + * @param Document|ResolvesToObject|Serializable|array|stdClass $newRoot + */ + public function replaceRoot(Document|Serializable|ResolvesToObject|stdClass|array $newRoot): static + { + $this->pipeline[] = Stage::replaceRoot($newRoot); + + return $this; + } + + /** + * Replaces a document with the specified embedded document. The operation replaces all existing fields in the input document, including the _id field. Specify a document embedded in the input document to promote the embedded document to the top level. + * Alias for $replaceRoot. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceWith/ + * @param Document|ResolvesToObject|Serializable|array|stdClass $expression + */ + public function replaceWith(Document|Serializable|ResolvesToObject|stdClass|array $expression): static + { + $this->pipeline[] = Stage::replaceWith($expression); + + return $this; + } + + /** + * Randomly selects the specified number of documents from its input. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sample/ + * @param int $size The number of documents to randomly select. + */ + public function sample(int $size): static + { + $this->pipeline[] = Stage::sample($size); + + return $this; + } + + /** + * Performs a full-text search of the field or fields in an Atlas collection. + * NOTE: $search is only available for MongoDB Atlas clusters, and is not available for self-managed deployments. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/search/ + * @param Document|Serializable|array|stdClass $search + */ + public function search(Document|Serializable|stdClass|array $search): static + { + $this->pipeline[] = Stage::search($search); + + return $this; + } + + /** + * Returns different types of metadata result documents for the Atlas Search query against an Atlas collection. + * NOTE: $searchMeta is only available for MongoDB Atlas clusters running MongoDB v4.4.9 or higher, and is not available for self-managed deployments. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/searchMeta/ + * @param Document|Serializable|array|stdClass $meta + */ + public function searchMeta(Document|Serializable|stdClass|array $meta): static + { + $this->pipeline[] = Stage::searchMeta($meta); + + return $this; + } + + /** + * Adds new fields to documents. Outputs documents that contain all existing fields from the input documents and newly added fields. + * Alias for $addFields. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/set/ + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string ...$field + */ + public function set(Type|ExpressionInterface|stdClass|array|string|int|float|bool|null ...$field): static + { + $this->pipeline[] = Stage::set(...$field); + + return $this; + } + + /** + * Groups documents into windows and applies one or more operators to the documents in each window. + * New in MongoDB 5.0. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setWindowFields/ + * @param Document|Serializable|array|stdClass $sortBy Specifies the field(s) to sort the documents by in the partition. Uses the same syntax as the $sort stage. Default is no sorting. + * @param Document|Serializable|array|stdClass $output Specifies the field(s) to append to the documents in the output returned by the $setWindowFields stage. Each field is set to the result returned by the window operator. + * A field can contain dots to specify embedded document fields and array fields. The semantics for the embedded document dotted notation in the $setWindowFields stage are the same as the $addFields and $set stages. + * @param Optional|ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $partitionBy Specifies an expression to group the documents. In the $setWindowFields stage, the group of documents is known as a partition. Default is one partition for the entire collection. + */ + public function setWindowFields( + Document|Serializable|stdClass|array $sortBy, + Document|Serializable|stdClass|array $output, + Optional|Type|ExpressionInterface|stdClass|array|string|int|float|bool|null $partitionBy = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::setWindowFields($sortBy, $output, $partitionBy); + + return $this; + } + + /** + * Provides data and size distribution information on sharded collections. + * New in MongoDB 6.0.3. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/shardedDataDistribution/ + */ + public function shardedDataDistribution(): static + { + $this->pipeline[] = Stage::shardedDataDistribution(); + + return $this; + } + + /** + * Skips the first n documents where n is the specified skip number and passes the remaining documents unmodified to the pipeline. For each input document, outputs either zero documents (for the first n documents) or one document (if after the first n documents). + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/skip/ + * @param int $skip + */ + public function skip(int $skip): static + { + $this->pipeline[] = Stage::skip($skip); + + return $this; + } + + /** + * Reorders the document stream by a specified sort key. Only the order changes; the documents remain unmodified. For each input document, outputs one document. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/ + * @param ExpressionInterface|Sort|Type|array|bool|float|int|null|stdClass|string ...$sort + */ + public function sort(Type|ExpressionInterface|Sort|stdClass|array|string|int|float|bool|null ...$sort): static + { + $this->pipeline[] = Stage::sort(...$sort); + + return $this; + } + + /** + * Groups incoming documents based on the value of a specified expression, then computes the count of documents in each distinct group. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/sortByCount/ + * @param ExpressionInterface|Type|array|bool|float|int|null|stdClass|string $expression + */ + public function sortByCount( + Type|ExpressionInterface|stdClass|array|string|int|float|bool|null $expression, + ): static + { + $this->pipeline[] = Stage::sortByCount($expression); + + return $this; + } + + /** + * Performs a union of two collections; i.e. combines pipeline results from two collections into a single result set. + * New in MongoDB 4.4. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unionWith/ + * @param string $coll The collection or view whose pipeline results you wish to include in the result set. + * @param Optional|BSONArray|PackedArray|Pipeline|array $pipeline An aggregation pipeline to apply to the specified coll. + * The pipeline cannot include the $out and $merge stages. Starting in v6.0, the pipeline can contain the Atlas Search $search stage as the first stage inside the pipeline. + */ + public function unionWith( + string $coll, + Optional|PackedArray|Pipeline|BSONArray|array $pipeline = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::unionWith($coll, $pipeline); + + return $this; + } + + /** + * Removes or excludes fields from documents. + * Alias for $project stage that removes or excludes fields. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unset/ + * @no-named-arguments + * @param FieldPath|string ...$field + */ + public function unset(FieldPath|string ...$field): static + { + $this->pipeline[] = Stage::unset(...$field); + + return $this; + } + + /** + * Deconstructs an array field from the input documents to output a document for each element. Each output document replaces the array with an element value. For each input document, outputs n documents where n is the number of array elements and can be zero for an empty array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/unwind/ + * @param ArrayFieldPath|string $path Field path to an array field. + * @param Optional|string $includeArrayIndex The name of a new field to hold the array index of the element. The name cannot start with a dollar sign $. + * @param Optional|bool $preserveNullAndEmptyArrays If true, if the path is null, missing, or an empty array, $unwind outputs the document. + * If false, if path is null, missing, or an empty array, $unwind does not output a document. + * The default value is false. + */ + public function unwind( + ArrayFieldPath|string $path, + Optional|string $includeArrayIndex = Optional::Undefined, + Optional|bool $preserveNullAndEmptyArrays = Optional::Undefined, + ): static + { + $this->pipeline[] = Stage::unwind($path, $includeArrayIndex, $preserveNullAndEmptyArrays); + + return $this; + } +} diff --git a/tests/Builder/FluentPipelineFactoryTest.php b/tests/Builder/FluentPipelineFactoryTest.php new file mode 100644 index 000000000..e1d84a2e4 --- /dev/null +++ b/tests/Builder/FluentPipelineFactoryTest.php @@ -0,0 +1,34 @@ +match(x: Query::eq(1)) + ->project(_id: false, x: true) + ->sort(x: Sort::Asc) + ->getPipeline(); + + $expected = <<<'json' + [ + {"$match": {"x": {"$eq": {"$numberInt": "1"}}}}, + {"$project": {"_id": false, "x": true}}, + {"$sort": {"x": {"$numberInt": "1"}}} + ] + json; + + $this->assertSamePipeline($expected, $pipeline); + } +} From fe8f353ef0b08721abda029b0b5d3ad02d1749c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 13 Mar 2024 23:19:14 +0100 Subject: [PATCH 63/95] Fix MongoDB namespace for InvalidArgumentException (#68) --- generator/src/OperatorClassGenerator.php | 3 ++- src/Builder/Expression/AddOperator.php | 2 +- src/Builder/Expression/AndOperator.php | 2 +- src/Builder/Expression/AvgOperator.php | 2 +- src/Builder/Expression/BitAndOperator.php | 2 +- src/Builder/Expression/BitOrOperator.php | 2 +- src/Builder/Expression/BitXorOperator.php | 2 +- src/Builder/Expression/ConcatArraysOperator.php | 2 +- src/Builder/Expression/ConcatOperator.php | 2 +- src/Builder/Expression/IfNullOperator.php | 2 +- src/Builder/Expression/MaxOperator.php | 2 +- src/Builder/Expression/MergeObjectsOperator.php | 2 +- src/Builder/Expression/MinOperator.php | 2 +- src/Builder/Expression/MultiplyOperator.php | 2 +- src/Builder/Expression/OrOperator.php | 2 +- src/Builder/Expression/SetEqualsOperator.php | 2 +- src/Builder/Expression/SetIntersectionOperator.php | 2 +- src/Builder/Expression/SetUnionOperator.php | 2 +- src/Builder/Expression/StdDevPopOperator.php | 2 +- src/Builder/Expression/StdDevSampOperator.php | 2 +- src/Builder/Expression/SumOperator.php | 2 +- src/Builder/Query/AllOperator.php | 2 +- src/Builder/Query/AndOperator.php | 2 +- src/Builder/Query/NorOperator.php | 2 +- src/Builder/Query/OrOperator.php | 2 +- src/Builder/Query/TypeOperator.php | 2 +- src/Builder/Stage/AddFieldsStage.php | 2 +- src/Builder/Stage/FacetStage.php | 2 +- src/Builder/Stage/ProjectStage.php | 2 +- src/Builder/Stage/SetStage.php | 2 +- src/Builder/Stage/SortStage.php | 2 +- src/Builder/Stage/UnsetStage.php | 2 +- 32 files changed, 33 insertions(+), 32 deletions(-) diff --git a/generator/src/OperatorClassGenerator.php b/generator/src/OperatorClassGenerator.php index f0a9d2dc1..73fcf09bf 100644 --- a/generator/src/OperatorClassGenerator.php +++ b/generator/src/OperatorClassGenerator.php @@ -78,9 +78,10 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition $constuctor->addComment('@param ' . $type->doc . ' ...$' . $argument->name . rtrim(' ' . $argument->description)); if ($argument->variadicMin > 0) { + $namespace->addUse(InvalidArgumentException::class); $constuctor->addBody(<<name}) < {$argument->variadicMin}) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for \${$argument->name}, got %d.', {$argument->variadicMin}, \count(\${$argument->name}))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for \${$argument->name}, got %d.', {$argument->variadicMin}, \count(\${$argument->name}))); } PHP); diff --git a/src/Builder/Expression/AddOperator.php b/src/Builder/Expression/AddOperator.php index eb36b6ea9..580731680 100644 --- a/src/Builder/Expression/AddOperator.php +++ b/src/Builder/Expression/AddOperator.php @@ -36,7 +36,7 @@ class AddOperator implements ResolvesToInt, ResolvesToLong, ResolvesToDouble, Re public function __construct(Decimal128|Int64|UTCDateTime|ResolvesToDate|ResolvesToNumber|float|int ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/AndOperator.php b/src/Builder/Expression/AndOperator.php index 989a27f8f..c4f3d7e6c 100644 --- a/src/Builder/Expression/AndOperator.php +++ b/src/Builder/Expression/AndOperator.php @@ -39,7 +39,7 @@ public function __construct( Decimal128|Int64|Type|ResolvesToBool|ResolvesToNull|ResolvesToNumber|ResolvesToString|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, ) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/AvgOperator.php b/src/Builder/Expression/AvgOperator.php index f21219e25..898bbe252 100644 --- a/src/Builder/Expression/AvgOperator.php +++ b/src/Builder/Expression/AvgOperator.php @@ -36,7 +36,7 @@ class AvgOperator implements ResolvesToNumber, OperatorInterface public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/BitAndOperator.php b/src/Builder/Expression/BitAndOperator.php index 37e0cf4a5..97b910a3f 100644 --- a/src/Builder/Expression/BitAndOperator.php +++ b/src/Builder/Expression/BitAndOperator.php @@ -35,7 +35,7 @@ class BitAndOperator implements ResolvesToInt, ResolvesToLong, OperatorInterface public function __construct(Int64|ResolvesToInt|ResolvesToLong|int ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/BitOrOperator.php b/src/Builder/Expression/BitOrOperator.php index 9f9d6af4f..f14ac9583 100644 --- a/src/Builder/Expression/BitOrOperator.php +++ b/src/Builder/Expression/BitOrOperator.php @@ -35,7 +35,7 @@ class BitOrOperator implements ResolvesToInt, ResolvesToLong, OperatorInterface public function __construct(Int64|ResolvesToInt|ResolvesToLong|int ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/BitXorOperator.php b/src/Builder/Expression/BitXorOperator.php index d0ae1ba6f..5382065b6 100644 --- a/src/Builder/Expression/BitXorOperator.php +++ b/src/Builder/Expression/BitXorOperator.php @@ -35,7 +35,7 @@ class BitXorOperator implements ResolvesToInt, ResolvesToLong, OperatorInterface public function __construct(Int64|ResolvesToInt|ResolvesToLong|int ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/ConcatArraysOperator.php b/src/Builder/Expression/ConcatArraysOperator.php index 45d2dabd9..f8b790d52 100644 --- a/src/Builder/Expression/ConcatArraysOperator.php +++ b/src/Builder/Expression/ConcatArraysOperator.php @@ -35,7 +35,7 @@ class ConcatArraysOperator implements ResolvesToArray, OperatorInterface public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$array) { if (\count($array) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $array, got %d.', 1, \count($array))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $array, got %d.', 1, \count($array))); } if (! array_is_list($array)) { diff --git a/src/Builder/Expression/ConcatOperator.php b/src/Builder/Expression/ConcatOperator.php index 20bd3e95d..d9f0d9168 100644 --- a/src/Builder/Expression/ConcatOperator.php +++ b/src/Builder/Expression/ConcatOperator.php @@ -33,7 +33,7 @@ class ConcatOperator implements ResolvesToString, OperatorInterface public function __construct(ResolvesToString|string ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/IfNullOperator.php b/src/Builder/Expression/IfNullOperator.php index eae888e69..f0a6a9ced 100644 --- a/src/Builder/Expression/IfNullOperator.php +++ b/src/Builder/Expression/IfNullOperator.php @@ -36,7 +36,7 @@ class IfNullOperator implements ResolvesToAny, OperatorInterface public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/MaxOperator.php b/src/Builder/Expression/MaxOperator.php index ca5a1cfe4..0573929b6 100644 --- a/src/Builder/Expression/MaxOperator.php +++ b/src/Builder/Expression/MaxOperator.php @@ -37,7 +37,7 @@ class MaxOperator implements ResolvesToAny, OperatorInterface public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/MergeObjectsOperator.php b/src/Builder/Expression/MergeObjectsOperator.php index e1e170f3c..162f2cd49 100644 --- a/src/Builder/Expression/MergeObjectsOperator.php +++ b/src/Builder/Expression/MergeObjectsOperator.php @@ -36,7 +36,7 @@ class MergeObjectsOperator implements ResolvesToObject, OperatorInterface public function __construct(Document|Serializable|ResolvesToObject|stdClass|array ...$document) { if (\count($document) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $document, got %d.', 1, \count($document))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $document, got %d.', 1, \count($document))); } if (! array_is_list($document)) { diff --git a/src/Builder/Expression/MinOperator.php b/src/Builder/Expression/MinOperator.php index 3ec303a4e..c3fa2ed4b 100644 --- a/src/Builder/Expression/MinOperator.php +++ b/src/Builder/Expression/MinOperator.php @@ -37,7 +37,7 @@ class MinOperator implements ResolvesToAny, OperatorInterface public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/MultiplyOperator.php b/src/Builder/Expression/MultiplyOperator.php index 652d570e1..9f15aeb39 100644 --- a/src/Builder/Expression/MultiplyOperator.php +++ b/src/Builder/Expression/MultiplyOperator.php @@ -39,7 +39,7 @@ class MultiplyOperator implements ResolvesToDecimal, OperatorInterface public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/OrOperator.php b/src/Builder/Expression/OrOperator.php index f544c39a3..a34896116 100644 --- a/src/Builder/Expression/OrOperator.php +++ b/src/Builder/Expression/OrOperator.php @@ -37,7 +37,7 @@ public function __construct( Type|ResolvesToBool|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression, ) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/SetEqualsOperator.php b/src/Builder/Expression/SetEqualsOperator.php index 4e1377d77..051da408d 100644 --- a/src/Builder/Expression/SetEqualsOperator.php +++ b/src/Builder/Expression/SetEqualsOperator.php @@ -35,7 +35,7 @@ class SetEqualsOperator implements ResolvesToBool, OperatorInterface public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/SetIntersectionOperator.php b/src/Builder/Expression/SetIntersectionOperator.php index b3568ffc2..42e3257d6 100644 --- a/src/Builder/Expression/SetIntersectionOperator.php +++ b/src/Builder/Expression/SetIntersectionOperator.php @@ -35,7 +35,7 @@ class SetIntersectionOperator implements ResolvesToArray, OperatorInterface public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/SetUnionOperator.php b/src/Builder/Expression/SetUnionOperator.php index 9f297bb2c..80080fc5a 100644 --- a/src/Builder/Expression/SetUnionOperator.php +++ b/src/Builder/Expression/SetUnionOperator.php @@ -35,7 +35,7 @@ class SetUnionOperator implements ResolvesToArray, OperatorInterface public function __construct(PackedArray|ResolvesToArray|BSONArray|array ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/StdDevPopOperator.php b/src/Builder/Expression/StdDevPopOperator.php index 41eda3df1..3845f7a24 100644 --- a/src/Builder/Expression/StdDevPopOperator.php +++ b/src/Builder/Expression/StdDevPopOperator.php @@ -37,7 +37,7 @@ class StdDevPopOperator implements ResolvesToDouble, OperatorInterface public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/StdDevSampOperator.php b/src/Builder/Expression/StdDevSampOperator.php index 41d390186..3b6a3c0a7 100644 --- a/src/Builder/Expression/StdDevSampOperator.php +++ b/src/Builder/Expression/StdDevSampOperator.php @@ -36,7 +36,7 @@ class StdDevSampOperator implements ResolvesToDouble, OperatorInterface public function __construct(Decimal128|Int64|ResolvesToNumber|float|int ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Expression/SumOperator.php b/src/Builder/Expression/SumOperator.php index c74232d67..dde3535ee 100644 --- a/src/Builder/Expression/SumOperator.php +++ b/src/Builder/Expression/SumOperator.php @@ -39,7 +39,7 @@ public function __construct( Decimal128|Int64|PackedArray|ResolvesToArray|ResolvesToNumber|BSONArray|array|float|int ...$expression, ) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } if (! array_is_list($expression)) { diff --git a/src/Builder/Query/AllOperator.php b/src/Builder/Query/AllOperator.php index 1b51961fd..c4ae5a212 100644 --- a/src/Builder/Query/AllOperator.php +++ b/src/Builder/Query/AllOperator.php @@ -36,7 +36,7 @@ class AllOperator implements FieldQueryInterface, OperatorInterface public function __construct(Type|FieldQueryInterface|stdClass|array|bool|float|int|null|string ...$value) { if (\count($value) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $value, got %d.', 1, \count($value))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $value, got %d.', 1, \count($value))); } if (! array_is_list($value)) { diff --git a/src/Builder/Query/AndOperator.php b/src/Builder/Query/AndOperator.php index 071c37097..40337e870 100644 --- a/src/Builder/Query/AndOperator.php +++ b/src/Builder/Query/AndOperator.php @@ -34,7 +34,7 @@ class AndOperator implements QueryInterface, OperatorInterface public function __construct(QueryInterface|array ...$queries) { if (\count($queries) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $queries, got %d.', 1, \count($queries))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $queries, got %d.', 1, \count($queries))); } if (! array_is_list($queries)) { diff --git a/src/Builder/Query/NorOperator.php b/src/Builder/Query/NorOperator.php index 415457229..4ff7f41a4 100644 --- a/src/Builder/Query/NorOperator.php +++ b/src/Builder/Query/NorOperator.php @@ -34,7 +34,7 @@ class NorOperator implements QueryInterface, OperatorInterface public function __construct(QueryInterface|array ...$queries) { if (\count($queries) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $queries, got %d.', 1, \count($queries))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $queries, got %d.', 1, \count($queries))); } if (! array_is_list($queries)) { diff --git a/src/Builder/Query/OrOperator.php b/src/Builder/Query/OrOperator.php index 1643b1bf1..f7ae64279 100644 --- a/src/Builder/Query/OrOperator.php +++ b/src/Builder/Query/OrOperator.php @@ -34,7 +34,7 @@ class OrOperator implements QueryInterface, OperatorInterface public function __construct(QueryInterface|array ...$queries) { if (\count($queries) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $queries, got %d.', 1, \count($queries))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $queries, got %d.', 1, \count($queries))); } if (! array_is_list($queries)) { diff --git a/src/Builder/Query/TypeOperator.php b/src/Builder/Query/TypeOperator.php index 1f839c09a..cffe79092 100644 --- a/src/Builder/Query/TypeOperator.php +++ b/src/Builder/Query/TypeOperator.php @@ -34,7 +34,7 @@ class TypeOperator implements FieldQueryInterface, OperatorInterface public function __construct(int|string ...$type) { if (\count($type) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $type, got %d.', 1, \count($type))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $type, got %d.', 1, \count($type))); } if (! array_is_list($type)) { diff --git a/src/Builder/Stage/AddFieldsStage.php b/src/Builder/Stage/AddFieldsStage.php index 8e2e346d5..15f509264 100644 --- a/src/Builder/Stage/AddFieldsStage.php +++ b/src/Builder/Stage/AddFieldsStage.php @@ -36,7 +36,7 @@ class AddFieldsStage implements StageInterface, OperatorInterface public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression) { if (\count($expression) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression))); } foreach($expression as $key => $value) { diff --git a/src/Builder/Stage/FacetStage.php b/src/Builder/Stage/FacetStage.php index cc000049f..e334167bf 100644 --- a/src/Builder/Stage/FacetStage.php +++ b/src/Builder/Stage/FacetStage.php @@ -37,7 +37,7 @@ class FacetStage implements StageInterface, OperatorInterface public function __construct(PackedArray|Pipeline|BSONArray|array ...$facet) { if (\count($facet) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $facet, got %d.', 1, \count($facet))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $facet, got %d.', 1, \count($facet))); } foreach($facet as $key => $value) { diff --git a/src/Builder/Stage/ProjectStage.php b/src/Builder/Stage/ProjectStage.php index 3c42a77ea..c8cd58c04 100644 --- a/src/Builder/Stage/ProjectStage.php +++ b/src/Builder/Stage/ProjectStage.php @@ -36,7 +36,7 @@ class ProjectStage implements StageInterface, OperatorInterface public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$specification) { if (\count($specification) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $specification, got %d.', 1, \count($specification))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $specification, got %d.', 1, \count($specification))); } foreach($specification as $key => $value) { diff --git a/src/Builder/Stage/SetStage.php b/src/Builder/Stage/SetStage.php index 3814914d5..ca929a2b8 100644 --- a/src/Builder/Stage/SetStage.php +++ b/src/Builder/Stage/SetStage.php @@ -37,7 +37,7 @@ class SetStage implements StageInterface, OperatorInterface public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$field) { if (\count($field) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); } foreach($field as $key => $value) { diff --git a/src/Builder/Stage/SortStage.php b/src/Builder/Stage/SortStage.php index 36f351c69..5f4de9e19 100644 --- a/src/Builder/Stage/SortStage.php +++ b/src/Builder/Stage/SortStage.php @@ -37,7 +37,7 @@ class SortStage implements StageInterface, OperatorInterface public function __construct(Type|ExpressionInterface|Sort|stdClass|array|bool|float|int|null|string ...$sort) { if (\count($sort) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $sort, got %d.', 1, \count($sort))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $sort, got %d.', 1, \count($sort))); } foreach($sort as $key => $value) { diff --git a/src/Builder/Stage/UnsetStage.php b/src/Builder/Stage/UnsetStage.php index 7cf9f7baa..f75546ed0 100644 --- a/src/Builder/Stage/UnsetStage.php +++ b/src/Builder/Stage/UnsetStage.php @@ -36,7 +36,7 @@ class UnsetStage implements StageInterface, OperatorInterface public function __construct(FieldPath|string ...$field) { if (\count($field) < 1) { - throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $field, got %d.', 1, \count($field))); } if (! array_is_list($field)) { From 7c3375df45f1c6a301651d1604271f2227fa0899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 15 Mar 2024 13:38:41 +0100 Subject: [PATCH 64/95] Allow to merge an array of stages into a pipeline (#57) * Allow to merge an array of stages into a pipeline * Improve types * Add test to create pipeline from array --- psalm-baseline.xml | 78 ++++++++++++++++++---------------- src/Builder/Pipeline.php | 24 +++++++---- tests/Builder/PipelineTest.php | 30 +++++++++++-- 3 files changed, 83 insertions(+), 49 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 64790c9f0..e1712dfe9 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,16 +1,16 @@ - + - $val - $val - $value[$key] + + + - $filter - $filterValue + + @@ -20,105 +20,111 @@ - $result - $result[] - $val - $val - $val - $val - $val + + + + + + + - $result + + + + + + + recursiveEncode($value)]]> - $subValue - $value + + - $query + - $query + - $query + - $expression + - stdClass + - $facet + - stdClass + - $query + - $restrictSearchWithMatch + - $field + - stdClass + - $query + - $specification + - stdClass + - $field + - stdClass + - $sort + - stdClass + @@ -129,8 +135,8 @@ - $queries[$fieldPath] - $query + + + * @psalm-type stage = StageInterface|array|stdClass + * @implements IteratorAggregate */ -class Pipeline implements IteratorAggregate +final class Pipeline implements IteratorAggregate { - /** @var StageInterface[] */ private readonly array $stages; - /** @no-named-arguments */ - public function __construct(StageInterface|Pipeline ...$stagesOrPipelines) + /** + * @psalm-param stage|list ...$stagesOrPipelines + * + * @no-named-arguments + */ + public function __construct(StageInterface|Pipeline|array|stdClass ...$stagesOrPipelines) { if (! array_is_list($stagesOrPipelines)) { throw new InvalidArgumentException('Named arguments are not supported for pipelines'); @@ -36,7 +41,9 @@ public function __construct(StageInterface|Pipeline ...$stagesOrPipelines) $stages = []; foreach ($stagesOrPipelines as $stageOrPipeline) { - if ($stageOrPipeline instanceof Pipeline) { + if (is_array($stageOrPipeline) && array_is_list($stageOrPipeline)) { + $stages = array_merge($stages, $stageOrPipeline); + } elseif ($stageOrPipeline instanceof Pipeline) { $stages = array_merge($stages, $stageOrPipeline->stages); } else { $stages[] = $stageOrPipeline; @@ -46,8 +53,7 @@ public function __construct(StageInterface|Pipeline ...$stagesOrPipelines) $this->stages = $stages; } - /** @return Traversable */ - public function getIterator(): Traversable + public function getIterator(): ArrayIterator { return new ArrayIterator($this->stages); } diff --git a/tests/Builder/PipelineTest.php b/tests/Builder/PipelineTest.php index 02c9d4254..b274fecab 100644 --- a/tests/Builder/PipelineTest.php +++ b/tests/Builder/PipelineTest.php @@ -17,24 +17,46 @@ class PipelineTest extends TestCase { public function testEmptyPipeline(): void { - $pipeline = new Pipeline(); + $pipeline = new Pipeline([]); $this->assertSame([], iterator_to_array($pipeline)); } + public function testFromArray(): void + { + $pipeline = new Pipeline( + ['$match' => ['tag' => 'foo']], + [ + ['$sort' => ['_id' => 1]], + ['$skip' => 10], + ], + ['$limit' => 5], + ); + + $expected = [ + ['$match' => ['tag' => 'foo']], + ['$sort' => ['_id' => 1]], + ['$skip' => 10], + ['$limit' => 5], + ]; + + $this->assertSame($expected, iterator_to_array($pipeline)); + } + public function testMergingPipeline(): void { $stages = array_map( fn (int $i) => $this->createMock(StageInterface::class), - range(0, 5), + range(0, 7), ); $pipeline = new Pipeline( $stages[0], $stages[1], new Pipeline($stages[2], $stages[3]), - $stages[4], - new Pipeline($stages[5]), + [$stages[4], $stages[5]], + new Pipeline($stages[6]), + [$stages[7]], ); $this->assertSame($stages, iterator_to_array($pipeline)); From aa4f34cde35608b328f23d8fa6270b7cafbe5b18 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Fri, 15 Mar 2024 13:39:19 +0100 Subject: [PATCH 65/95] Add merge-up action (#69) --- .github/workflows/merge-up.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/merge-up.yml diff --git a/.github/workflows/merge-up.yml b/.github/workflows/merge-up.yml new file mode 100644 index 000000000..215c2d9ac --- /dev/null +++ b/.github/workflows/merge-up.yml @@ -0,0 +1,30 @@ +name: Merge up + +on: + push: + branches: + - "[0-9]+.[0-9]+" + +env: + GH_TOKEN: ${{ secrets.MERGE_UP_TOKEN }} + +jobs: + merge-up: + name: Create merge up pull request + runs-on: ubuntu-latest + + steps: + - name: Checkout + id: checkout + uses: actions/checkout@v4 + with: + # fetch-depth 0 is required to fetch all branches, not just the branch being built + fetch-depth: 0 + token: ${{ secrets.MERGE_UP_TOKEN }} + + - name: Create pull request + id: create-pull-request + uses: alcaeus/automatic-merge-up-action@main + with: + ref: ${{ github.ref_name }} + branchNamePattern: '.' From 6bc447f096f49c039a5782b5014a7ce85b250b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 25 Mar 2024 16:02:49 +0100 Subject: [PATCH 66/95] Fix accepted stage type in FluentFactoryTrait (#71) --- generator/src/FluentStageFactoryGenerator.php | 4 +++- src/Builder/Stage/FluentFactoryTrait.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/generator/src/FluentStageFactoryGenerator.php b/generator/src/FluentStageFactoryGenerator.php index 2dfcee04a..ff7010f15 100644 --- a/generator/src/FluentStageFactoryGenerator.php +++ b/generator/src/FluentStageFactoryGenerator.php @@ -15,6 +15,7 @@ use Nette\PhpGenerator\PhpNamespace; use Nette\PhpGenerator\TraitType; use ReflectionClass; +use stdClass; use function array_key_last; use function array_map; @@ -45,10 +46,11 @@ private function createFluentFactoryTrait(GeneratorDefinition $definition): PhpN $namespace->addUse(self::FACTORY_CLASS); $namespace->addUse(StageInterface::class); $namespace->addUse(Pipeline::class); + $namespace->addUse(stdClass::class); $trait->addProperty('pipeline') ->setType('array') - ->setComment('@var list') + ->setComment('@var list|stdClass>') ->setValue([]); $trait->addMethod('getPipeline') ->setReturnType(Pipeline::class) diff --git a/src/Builder/Stage/FluentFactoryTrait.php b/src/Builder/Stage/FluentFactoryTrait.php index 0ffbcd47a..0716a77ae 100644 --- a/src/Builder/Stage/FluentFactoryTrait.php +++ b/src/Builder/Stage/FluentFactoryTrait.php @@ -33,7 +33,7 @@ trait FluentFactoryTrait { - /** @var list */ + /** @var list|stdClass> */ public array $pipeline = []; public function getPipeline(): Pipeline From 24214b6e5a0380716357e5616c162d7025c974e6 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Mon, 9 Sep 2024 08:33:11 +0200 Subject: [PATCH 67/95] PHPLIB-955: Require PHP 8.1 (#1374) * PHPLIB-955: Require PHP 8.1 * Apply automated phpcs fixes for PHP 8.1 * Fix psalm issues * Remove unnecessary type assertions * Fix wrong type in DocumentsMatchConstraint * Use constructor property promotion where possible * Update Psalm baseline * Remove commented code * Use match statement instead of switch * Fix breakage introduced by constructor property promotion * Always access promoted options property in constructor * Restore PackedArray as invalid document in tests --- .evergreen/config.yml | 1 - .evergreen/config/build-task-groups.yml | 14 +- .evergreen/config/build-variants.yml | 8 +- .evergreen/config/generate-config.php | 10 +- .../generated/build/build-extension.yml | 84 ---------- .../test-variant/legacy-php-full.yml | 114 -------------- .../test-variant/legacy-php-full.yml | 56 ------- .github/workflows/tests.yml | 12 +- benchmark/src/Fixtures/PassThruCodec.php | 12 +- benchmark/src/Fixtures/ToObjectCodec.php | 12 +- benchmark/src/ReadLargeDocumentBench.php | 9 +- benchmark/src/ReadMultipleDocumentsBench.php | 6 +- composer.json | 2 +- examples/command_logger.php | 3 +- examples/persistable.php | 13 +- examples/sdam_logger.php | 6 +- phpcs.xml.dist | 3 +- psalm-baseline.xml | 7 +- src/BulkWriteResult.php | 8 +- src/ChangeStream.php | 8 +- src/Client.php | 2 +- src/Codec/DecodeIfSupported.php | 13 +- src/Codec/Decoder.php | 9 +- src/Codec/DocumentCodec.php | 6 +- src/Codec/EncodeIfSupported.php | 13 +- src/Codec/Encoder.php | 9 +- src/Collection.php | 50 +++--- src/Command/ListCollections.php | 9 +- src/Command/ListDatabases.php | 6 +- src/Database.php | 10 +- src/DeleteResult.php | 5 +- .../CreateEncryptedCollectionException.php | 9 +- src/Exception/InvalidArgumentException.php | 4 +- src/Exception/ResumeTokenException.php | 2 +- src/Exception/UnsupportedValueException.php | 14 +- src/GridFS/Bucket.php | 20 +-- src/GridFS/CollectionWrapper.php | 35 ++--- .../Exception/FileNotFoundException.php | 2 +- src/GridFS/Exception/LogicException.php | 6 +- src/GridFS/Exception/StreamException.php | 3 +- src/GridFS/ReadableStream.php | 9 +- src/GridFS/StreamWrapper.php | 5 +- src/GridFS/WritableStream.php | 7 +- src/InsertManyResult.php | 8 +- src/InsertOneResult.php | 10 +- src/Model/BSONIterator.php | 11 +- src/Model/ChangeStreamIterator.php | 11 +- src/Model/CodecCursor.php | 9 +- src/Model/CollectionInfo.php | 18 +-- src/Model/CollectionInfoCommandIterator.php | 6 +- src/Model/DatabaseInfo.php | 18 +-- src/Model/DatabaseInfoLegacyIterator.php | 5 +- src/Model/IndexInfo.php | 18 +-- src/Model/IndexInfoIteratorIterator.php | 6 +- src/Model/IndexInput.php | 14 +- src/Model/SearchIndexInput.php | 6 +- src/Operation/Aggregate.php | 95 +++++------- src/Operation/BulkWrite.php | 8 +- src/Operation/Count.php | 52 +++---- src/Operation/CountDocuments.php | 13 +- src/Operation/CreateCollection.php | 108 ++++++------- src/Operation/CreateEncryptedCollection.php | 22 +-- src/Operation/CreateIndexes.php | 32 ++-- src/Operation/CreateSearchIndexes.php | 9 +- src/Operation/DatabaseCommand.php | 20 +-- src/Operation/Delete.php | 43 ++---- src/Operation/DeleteMany.php | 2 +- src/Operation/DeleteOne.php | 2 +- src/Operation/Distinct.php | 47 ++---- src/Operation/DropCollection.php | 28 ++-- src/Operation/DropDatabase.php | 25 ++- src/Operation/DropIndexes.php | 35 ++--- src/Operation/DropSearchIndex.php | 12 +- src/Operation/EstimatedDocumentCount.php | 9 +- src/Operation/Explain.php | 28 ++-- src/Operation/Find.php | 144 ++++++++---------- src/Operation/FindAndModify.php | 8 +- src/Operation/FindOne.php | 2 +- src/Operation/FindOneAndDelete.php | 2 +- src/Operation/FindOneAndReplace.php | 9 +- src/Operation/FindOneAndUpdate.php | 8 +- src/Operation/InsertMany.php | 8 +- src/Operation/InsertOne.php | 47 +++--- src/Operation/ListCollections.php | 5 +- src/Operation/ListIndexes.php | 20 +-- src/Operation/ListSearchIndexes.php | 6 +- src/Operation/MapReduce.php | 101 +++++------- src/Operation/ModifyCollection.php | 31 ++-- src/Operation/RenameCollection.php | 25 ++- src/Operation/ReplaceOne.php | 9 +- src/Operation/Update.php | 20 +-- src/Operation/UpdateMany.php | 8 +- src/Operation/UpdateOne.php | 8 +- src/Operation/UpdateSearchIndex.php | 10 +- src/Operation/Watch.php | 20 +-- src/Operation/WithTransaction.php | 5 +- src/UpdateResult.php | 5 +- src/functions.php | 25 +-- tests/Database/DatabaseFunctionalTest.php | 3 +- tests/DocumentationExamplesTest.php | 4 +- tests/ExamplesTest.php | 2 +- tests/FunctionalTestCase.php | 6 +- tests/FunctionsTest.php | 7 +- tests/GridFS/BucketFunctionalTest.php | 2 +- tests/Model/CallbackIteratorTest.php | 5 +- tests/Model/ChangeStreamIteratorTest.php | 3 +- tests/Model/IndexInputTest.php | 7 +- tests/Operation/BulkWriteTest.php | 8 +- tests/Operation/CountDocumentsTest.php | 4 +- tests/Operation/CountTest.php | 4 +- tests/Operation/DatabaseCommandTest.php | 4 +- tests/Operation/DeleteTest.php | 3 +- tests/Operation/DistinctTest.php | 4 +- tests/Operation/FindOneAndDeleteTest.php | 4 +- tests/Operation/FindOneAndReplaceTest.php | 6 +- tests/Operation/FindOneAndUpdateTest.php | 6 +- tests/Operation/FindTest.php | 4 +- tests/Operation/InsertOneTest.php | 4 +- tests/Operation/MapReduceTest.php | 5 +- tests/Operation/ReplaceOneTest.php | 6 +- tests/Operation/UpdateManyTest.php | 6 +- tests/Operation/UpdateOneTest.php | 6 +- tests/Operation/UpdateSearchIndexTest.php | 5 +- tests/Operation/UpdateTest.php | 8 +- tests/Operation/WatchFunctionalTest.php | 8 +- tests/PHPUnit/Functions.php | 46 ++---- .../ClientSideEncryptionSpecTest.php | 4 +- tests/SpecTests/CommandExpectations.php | 6 +- tests/SpecTests/Context.php | 8 +- tests/SpecTests/DocumentsMatchConstraint.php | 36 ++--- tests/SpecTests/ErrorExpectation.php | 3 +- tests/SpecTests/FunctionalTestCase.php | 9 +- tests/SpecTests/Operation.php | 18 +-- tests/SpecTests/PrimaryStepDownSpecTest.php | 4 +- tests/SpecTests/ResultExpectation.php | 11 +- .../Prose3_ReturnOriginalErrorTest.php | 5 +- tests/TestCase.php | 14 +- .../Constraint/IsBsonType.php | 6 +- tests/UnifiedSpecTests/Constraint/Matches.php | 15 +- tests/UnifiedSpecTests/Context.php | 11 +- tests/UnifiedSpecTests/EntityMap.php | 16 +- tests/UnifiedSpecTests/EventCollector.php | 23 +-- tests/UnifiedSpecTests/EventObserver.php | 42 ++--- tests/UnifiedSpecTests/ExpectedError.php | 3 +- tests/UnifiedSpecTests/ExpectedResult.php | 5 +- tests/UnifiedSpecTests/Loop.php | 9 +- tests/UnifiedSpecTests/Operation.php | 10 +- .../ServerParameterHelper.php | 10 +- tests/UnifiedSpecTests/UnifiedTestCase.php | 17 +-- tests/UnifiedSpecTests/UnifiedTestRunner.php | 5 +- 150 files changed, 693 insertions(+), 1669 deletions(-) delete mode 100644 .evergreen/config/generated/test-variant/legacy-php-full.yml delete mode 100644 .evergreen/config/templates/test-variant/legacy-php-full.yml diff --git a/.evergreen/config.yml b/.evergreen/config.yml index 2843ac813..1c4e2a22a 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -71,4 +71,3 @@ include: - filename: .evergreen/config/generated/test/require-api-version.yml - filename: .evergreen/config/generated/test/csfle.yml - filename: .evergreen/config/generated/test-variant/modern-php-full.yml - - filename: .evergreen/config/generated/test-variant/legacy-php-full.yml diff --git a/.evergreen/config/build-task-groups.yml b/.evergreen/config/build-task-groups.yml index 892548181..f745f78ad 100644 --- a/.evergreen/config/build-task-groups.yml +++ b/.evergreen/config/build-task-groups.yml @@ -11,22 +11,10 @@ task_groups: # Builds all versions of PHP - name: "build-all-php" # Keep this number in sync with the number of PHP versions to allow for parallel builds - max_hosts: 4 + max_hosts: 3 setup_task: *build_setup setup_task_can_fail_task: true setup_task_timeout_secs: 1800 teardown_task: *build_teardown tasks: - ".build" - - # Builds all versions of PHP that support OpenSSL 3 (PHP 8.1+) - - name: "build-php-openssl3" - # Keep this number in sync with the number of PHP versions to allow for parallel builds - # Subtract 2 versions as PHP 7.4 and 8.0 are not built with OpenSSL 3 - max_hosts: 2 - setup_task: *build_setup - setup_task_can_fail_task: true - setup_task_timeout_secs: 1800 - teardown_task: *build_teardown - tasks: - - ".build !.php7.4 !.php8.0" diff --git a/.evergreen/config/build-variants.yml b/.evergreen/config/build-variants.yml index 2635e81a6..562791c38 100644 --- a/.evergreen/config/build-variants.yml +++ b/.evergreen/config/build-variants.yml @@ -8,7 +8,7 @@ buildvariants: tags: ["build", "debian", "x64"] run_on: debian12-small tasks: - - name: "build-php-openssl3" + - name: "build-all-php" - name: build-debian11 display_name: "Build: Debian 11" tags: ["build", "debian", "x64", "pr", "tag"] @@ -34,7 +34,7 @@ buildvariants: tags: ["build", "rhel", "x64", "pr", "tag"] run_on: rhel90-small tasks: - - name: "build-php-openssl3" + - name: "build-all-php" - name: build-rhel83-zseries display_name: "Build: RHEL 8.3 Zseries" tags: ["build", "rhel", "zseries", "tag"] @@ -66,13 +66,13 @@ buildvariants: tags: ["build", "ubuntu", "x64", "pr", "tag"] run_on: ubuntu2204-small tasks: - - name: "build-php-openssl3" + - name: "build-all-php" - name: build-ubuntu2204-arm64 display_name: "Build: Ubuntu 22.04 ARM64" tags: ["build", "ubuntu", "arm64", "tag"] run_on: ubuntu2204-arm64-small tasks: - - name: "build-php-openssl3" + - name: "build-all-php" - name: build-ubuntu2004 display_name: "Build: Ubuntu 20.04 x64" tags: ["build", "ubuntu", "x64", "pr", "tag"] diff --git a/.evergreen/config/generate-config.php b/.evergreen/config/generate-config.php index a87d262f7..ed2c6a28e 100644 --- a/.evergreen/config/generate-config.php +++ b/.evergreen/config/generate-config.php @@ -2,16 +2,11 @@ = 5.0, < 8.0 - - name: test-debian11-php-8.0-local - tags: ["test", "debian", "x64", "php8.0", "pr", "tag"] - display_name: "Test: Debian 11, PHP 8.0" - run_on: debian11-small - expansions: - FETCH_BUILD_VARIANT: "build-debian11" - FETCH_BUILD_TASK: "build-php-8.0" - PHP_VERSION: "8.0" - VARIANT: debian11 # Referenced by ADL build script for downloading MQLRun - depends_on: - - variant: "build-debian11" - name: "build-php-8.0" - tasks: - # Remember to add new major versions here as they are released - - ".standalone .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - ".replicaset .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - ".sharded .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - ".loadbalanced .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - "test-atlas-data-lake" - - # Test versions < 5.0 - - name: test-debian92-php-8.0-local - tags: ["test", "debian", "x64", "php8.0", "pr", "tag"] - display_name: "Test: Debian 9.2, PHP 8.0" - run_on: debian92-small - expansions: - FETCH_BUILD_VARIANT: "build-debian92" - FETCH_BUILD_TASK: "build-php-8.0" - PHP_VERSION: "8.0" - depends_on: - - variant: "build-debian92" - name: "build-php-8.0" - tasks: - # Remember to add new major versions here as they are released - - ".standalone .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - ".replicaset .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - ".sharded .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - ".loadbalanced .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - # Test Atlas and CSFLE on RHEL 8 - - name: test-rhel80-php-8.0-atlas - tags: ["test", "debian", "x64", "php8.0", "pr", "tag"] - display_name: "Test: RHEL 8.0, PHP 8.0" - run_on: rhel80-small - expansions: - FETCH_BUILD_VARIANT: "build-rhel80" - FETCH_BUILD_TASK: "build-php-8.0" - PHP_VERSION: "8.0" - depends_on: - - variant: "build-rhel80" - name: "build-php-8.0" - tasks: - - "test_atlas_task_group" - - ".csfle" -# TODO PHPLIB-955: This file can be removed when requiring PHP 8.1+ - # Test MongoDB >= 5.0, < 8.0 - - name: test-debian11-php-7.4-local - tags: ["test", "debian", "x64", "php7.4", "pr", "tag"] - display_name: "Test: Debian 11, PHP 7.4" - run_on: debian11-small - expansions: - FETCH_BUILD_VARIANT: "build-debian11" - FETCH_BUILD_TASK: "build-php-7.4" - PHP_VERSION: "7.4" - VARIANT: debian11 # Referenced by ADL build script for downloading MQLRun - depends_on: - - variant: "build-debian11" - name: "build-php-7.4" - tasks: - # Remember to add new major versions here as they are released - - ".standalone .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - ".replicaset .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - ".sharded .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - ".loadbalanced .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - "test-atlas-data-lake" - - # Test versions < 5.0 - - name: test-debian92-php-7.4-local - tags: ["test", "debian", "x64", "php7.4", "pr", "tag"] - display_name: "Test: Debian 9.2, PHP 7.4" - run_on: debian92-small - expansions: - FETCH_BUILD_VARIANT: "build-debian92" - FETCH_BUILD_TASK: "build-php-7.4" - PHP_VERSION: "7.4" - depends_on: - - variant: "build-debian92" - name: "build-php-7.4" - tasks: - # Remember to add new major versions here as they are released - - ".standalone .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - ".replicaset .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - ".sharded .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - ".loadbalanced .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - # Test Atlas and CSFLE on RHEL 8 - - name: test-rhel80-php-7.4-atlas - tags: ["test", "debian", "x64", "php7.4", "pr", "tag"] - display_name: "Test: RHEL 8.0, PHP 7.4" - run_on: rhel80-small - expansions: - FETCH_BUILD_VARIANT: "build-rhel80" - FETCH_BUILD_TASK: "build-php-7.4" - PHP_VERSION: "7.4" - depends_on: - - variant: "build-rhel80" - name: "build-php-7.4" - tasks: - - "test_atlas_task_group" - - ".csfle" diff --git a/.evergreen/config/templates/test-variant/legacy-php-full.yml b/.evergreen/config/templates/test-variant/legacy-php-full.yml deleted file mode 100644 index 82bef9b4b..000000000 --- a/.evergreen/config/templates/test-variant/legacy-php-full.yml +++ /dev/null @@ -1,56 +0,0 @@ -# TODO PHPLIB-955: This file can be removed when requiring PHP 8.1+ - # Test MongoDB >= 5.0, < 8.0 - - name: test-debian11-php-%phpVersion%-local - tags: ["test", "debian", "x64", "php%phpVersion%", "pr", "tag"] - display_name: "Test: Debian 11, PHP %phpVersion%" - run_on: debian11-small - expansions: - FETCH_BUILD_VARIANT: "build-debian11" - FETCH_BUILD_TASK: "build-php-%phpVersion%" - PHP_VERSION: "%phpVersion%" - VARIANT: debian11 # Referenced by ADL build script for downloading MQLRun - depends_on: - - variant: "build-debian11" - name: "build-php-%phpVersion%" - tasks: - # Remember to add new major versions here as they are released - - ".standalone .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - ".replicaset .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - ".sharded .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - ".loadbalanced .local !.csfle !.4.0 !.4.2 !.4.4 !.8.0 !.rapid !.latest" - - "test-atlas-data-lake" - - # Test versions < 5.0 - - name: test-debian92-php-%phpVersion%-local - tags: ["test", "debian", "x64", "php%phpVersion%", "pr", "tag"] - display_name: "Test: Debian 9.2, PHP %phpVersion%" - run_on: debian92-small - expansions: - FETCH_BUILD_VARIANT: "build-debian92" - FETCH_BUILD_TASK: "build-php-%phpVersion%" - PHP_VERSION: "%phpVersion%" - depends_on: - - variant: "build-debian92" - name: "build-php-%phpVersion%" - tasks: - # Remember to add new major versions here as they are released - - ".standalone .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - ".replicaset .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - ".sharded .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - ".loadbalanced .local !.csfle !.6.0 !.7.0 !.8.0 !.rapid !.latest" - - # Test Atlas and CSFLE on RHEL 8 - - name: test-rhel80-php-%phpVersion%-atlas - tags: ["test", "debian", "x64", "php%phpVersion%", "pr", "tag"] - display_name: "Test: RHEL 8.0, PHP %phpVersion%" - run_on: rhel80-small - expansions: - FETCH_BUILD_VARIANT: "build-rhel80" - FETCH_BUILD_TASK: "build-php-%phpVersion%" - PHP_VERSION: "%phpVersion%" - depends_on: - - variant: "build-rhel80" - name: "build-php-%phpVersion%" - tasks: - - "test_atlas_task_group" - - ".csfle" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e5ae92cdc..68ae09af4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,8 +29,6 @@ jobs: os: - "ubuntu-20.04" php-version: - - "7.4" - - "8.0" - "8.1" - "8.2" - "8.3" @@ -41,23 +39,23 @@ jobs: - "server" include: - os: "ubuntu-20.04" - php-version: "8.0" + php-version: "8.1" mongodb-version: "6.0" topology: "replica_set" - os: "ubuntu-20.04" - php-version: "8.0" + php-version: "8.1" mongodb-version: "6.0" topology: "sharded_cluster" - os: "ubuntu-20.04" - php-version: "8.0" + php-version: "8.1" mongodb-version: "5.0" topology: "server" - os: "ubuntu-20.04" - php-version: "8.0" + php-version: "8.1" mongodb-version: "4.4" topology: "replica_set" - os: "ubuntu-20.04" - php-version: "8.0" + php-version: "8.1" mongodb-version: "4.4" topology: "sharded_cluster" diff --git a/benchmark/src/Fixtures/PassThruCodec.php b/benchmark/src/Fixtures/PassThruCodec.php index 7a8846cc6..fe4c725b0 100644 --- a/benchmark/src/Fixtures/PassThruCodec.php +++ b/benchmark/src/Fixtures/PassThruCodec.php @@ -13,20 +13,17 @@ final class PassThruCodec implements DocumentCodec use DecodeIfSupported; use EncodeIfSupported; - /** @param mixed $value */ - public function canDecode($value): bool + public function canDecode(mixed $value): bool { return $value instanceof Document; } - /** @param mixed $value */ - public function canEncode($value): bool + public function canEncode(mixed $value): bool { return $value instanceof Document; } - /** @param mixed $value */ - public function decode($value): Document + public function decode(mixed $value): Document { if (! $value instanceof Document) { throw UnsupportedValueException::invalidDecodableValue($value); @@ -35,8 +32,7 @@ public function decode($value): Document return $value; } - /** @param mixed $value */ - public function encode($value): Document + public function encode(mixed $value): Document { if (! $value instanceof Document) { throw UnsupportedValueException::invalidEncodableValue($value); diff --git a/benchmark/src/Fixtures/ToObjectCodec.php b/benchmark/src/Fixtures/ToObjectCodec.php index c212302ab..a193d58a4 100644 --- a/benchmark/src/Fixtures/ToObjectCodec.php +++ b/benchmark/src/Fixtures/ToObjectCodec.php @@ -15,20 +15,17 @@ final class ToObjectCodec implements DocumentCodec use DecodeIfSupported; use EncodeIfSupported; - /** @param mixed $value */ - public function canDecode($value): bool + public function canDecode(mixed $value): bool { return $value instanceof Document; } - /** @param mixed $value */ - public function canEncode($value): bool + public function canEncode(mixed $value): bool { return is_object($value); } - /** @param mixed $value */ - public function decode($value): object + public function decode(mixed $value): object { if (! $value instanceof Document) { throw UnsupportedValueException::invalidDecodableValue($value); @@ -37,8 +34,7 @@ public function decode($value): object return $value->toPHP(['root' => 'stdClass', 'array' => 'array', 'document' => 'stdClass']); } - /** @param mixed $value */ - public function encode($value): Document + public function encode(mixed $value): Document { if (! is_object($value)) { throw UnsupportedValueException::invalidEncodableValue($value); diff --git a/benchmark/src/ReadLargeDocumentBench.php b/benchmark/src/ReadLargeDocumentBench.php index de7d3ad02..bdf5b021d 100644 --- a/benchmark/src/ReadLargeDocumentBench.php +++ b/benchmark/src/ReadLargeDocumentBench.php @@ -129,8 +129,7 @@ public function benchAccessLastField(array $params): void } } - /** @param array|object $document */ - private function accessId($document, string $accessor): void + private function accessId(array|object $document, string $accessor): void { switch ($accessor) { case 'array': @@ -153,8 +152,7 @@ private function accessId($document, string $accessor): void } } - /** @param array|object $document */ - private function accessLastField($document, string $accessor): void + private function accessLastField(array|object $document, string $accessor): void { switch ($accessor) { case 'array': @@ -178,8 +176,7 @@ private function accessLastField($document, string $accessor): void } } - /** @param array|object $document */ - private function accessFirstField($document, string $accessor): void + private function accessFirstField(array|object $document, string $accessor): void { switch ($accessor) { case 'array': diff --git a/benchmark/src/ReadMultipleDocumentsBench.php b/benchmark/src/ReadMultipleDocumentsBench.php index 64c6ef7e3..74dbafe13 100644 --- a/benchmark/src/ReadMultipleDocumentsBench.php +++ b/benchmark/src/ReadMultipleDocumentsBench.php @@ -116,8 +116,7 @@ public function benchAccessNestedItem(array $params): void } } - /** @param array|object $document */ - private function accessId($document, string $accessor): void + private function accessId(array|object $document, string $accessor): void { switch ($accessor) { case 'array': @@ -140,8 +139,7 @@ private function accessId($document, string $accessor): void } } - /** @param array|object $document */ - private function accessNestedItem($document, string $accessor): void + private function accessNestedItem(array|object $document, string $accessor): void { switch ($accessor) { case 'array': diff --git a/composer.json b/composer.json index fceee1108..a6dd35b5a 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ { "name": "Jérôme Tamarelle", "email": "jerome.tamarelle@mongodb.com" } ], "require": { - "php": "^7.4 || ^8.0", + "php": "^8.1", "ext-hash": "*", "ext-json": "*", "ext-mongodb": "^1.20.0", diff --git a/examples/command_logger.php b/examples/command_logger.php index 7fd932dac..eab47882a 100644 --- a/examples/command_logger.php +++ b/examples/command_logger.php @@ -11,7 +11,6 @@ use MongoDB\Driver\Monitoring\CommandSucceededEvent; use function assert; -use function get_class; use function getenv; use function is_object; use function printf; @@ -46,7 +45,7 @@ public function commandFailed(CommandFailedEvent $event): void printf("reply: %s\n", toJson($event->getReply())); $exception = $event->getError(); - printf("exception: %s\n", get_class($exception)); + printf("exception: %s\n", $exception::class); printf("exception.code: %d\n", $exception->getCode()); printf("exception.message: %s\n", $exception->getMessage()); echo "\n"; diff --git a/examples/persistable.php b/examples/persistable.php index c7aa5617d..2a4c3614e 100644 --- a/examples/persistable.php +++ b/examples/persistable.php @@ -19,15 +19,12 @@ class PersistableEntry implements Persistable { private ObjectId $id; - public string $name; - /** @var array */ public array $emails = []; - public function __construct(string $name) + public function __construct(public string $name) { $this->id = new ObjectId(); - $this->name = $name; } public function getId(): ObjectId @@ -64,14 +61,8 @@ public function bsonUnserialize(array $data): void class PersistableEmail implements Persistable { - public string $type; - - public string $address; - - public function __construct(string $type, string $address) + public function __construct(public string $type, public string $address) { - $this->type = $type; - $this->address = $address; } public function bsonSerialize(): stdClass diff --git a/examples/sdam_logger.php b/examples/sdam_logger.php index 974e11dfb..2edf82302 100644 --- a/examples/sdam_logger.php +++ b/examples/sdam_logger.php @@ -16,14 +16,12 @@ use MongoDB\Driver\Monitoring\TopologyClosedEvent; use MongoDB\Driver\Monitoring\TopologyOpeningEvent; -use function get_class; use function getenv; use function printf; require __DIR__ . '/../vendor/autoload.php'; -/** @param array|object $document */ -function toJSON($document): string +function toJSON(array|object $document): string { return Document::fromPHP($document)->toRelaxedExtendedJSON(); } @@ -67,7 +65,7 @@ public function serverHeartbeatFailed(ServerHeartbeatFailedEvent $event): void $error = $event->getError(); - printf("error: %s(%d): %s\n", get_class($error), $error->getCode(), $error->getMessage()); + printf("error: %s(%d): %s\n", $error::class, $error->getCode(), $error->getMessage()); echo "\n"; } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index c01b7025d..ace2991ac 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -17,7 +17,7 @@ rector.php - + @@ -40,6 +40,7 @@ + diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 8ba6e2760..8589b8024 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -133,6 +133,11 @@ + + + options['typeMap']]]> + + cursor->nextBatch]]> @@ -337,7 +342,7 @@ - + options['encryptedFields']]]> options['encryptedFields']]]> diff --git a/src/BulkWriteResult.php b/src/BulkWriteResult.php index 9f86c3a81..4fb556e0b 100644 --- a/src/BulkWriteResult.php +++ b/src/BulkWriteResult.php @@ -25,16 +25,10 @@ */ class BulkWriteResult { - private WriteResult $writeResult; - - private array $insertedIds; - private bool $isAcknowledged; - public function __construct(WriteResult $writeResult, array $insertedIds) + public function __construct(private WriteResult $writeResult, private array $insertedIds) { - $this->writeResult = $writeResult; - $this->insertedIds = $insertedIds; $this->isAcknowledged = $writeResult->isAcknowledged(); } diff --git a/src/ChangeStream.php b/src/ChangeStream.php index 9565adfb0..506300d80 100644 --- a/src/ChangeStream.php +++ b/src/ChangeStream.php @@ -80,8 +80,6 @@ class ChangeStream implements Iterator /** @var ResumeCallable|null */ private $resumeCallable; - private ChangeStreamIterator $iterator; - private int $key = 0; /** @@ -90,8 +88,6 @@ class ChangeStream implements Iterator */ private bool $hasAdvanced = false; - private ?DocumentCodec $codec; - /** * @see https://php.net/iterator.current * @return array|object|null @@ -207,11 +203,9 @@ public function valid() * * @param ResumeCallable $resumeCallable */ - public function __construct(ChangeStreamIterator $iterator, callable $resumeCallable, ?DocumentCodec $codec = null) + public function __construct(private ChangeStreamIterator $iterator, callable $resumeCallable, private ?DocumentCodec $codec = null) { - $this->iterator = $iterator; $this->resumeCallable = $resumeCallable; - $this->codec = $codec; if ($codec) { $this->iterator->getInnerIterator()->setTypeMap(['root' => 'bson']); diff --git a/src/Client.php b/src/Client.php index b5fc18246..0b9282cf0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -397,7 +397,7 @@ private static function getVersion(): string if (self::$version === null) { try { self::$version = InstalledVersions::getPrettyVersion('mongodb/mongodb') ?? 'unknown'; - } catch (Throwable $t) { + } catch (Throwable) { self::$version = 'error'; } } diff --git a/src/Codec/DecodeIfSupported.php b/src/Codec/DecodeIfSupported.php index 56dcfb9ec..7fd768011 100644 --- a/src/Codec/DecodeIfSupported.php +++ b/src/Codec/DecodeIfSupported.php @@ -25,27 +25,22 @@ */ trait DecodeIfSupported { - /** - * @param mixed $value - * @psalm-assert-if-true BSONType $value - */ - abstract public function canDecode($value): bool; + /** @psalm-assert-if-true BSONType $value */ + abstract public function canDecode(mixed $value): bool; /** - * @param mixed $value * @psalm-param BSONType $value * @return mixed * @psalm-return NativeType * @throws UnsupportedValueException if the decoder does not support the value */ - abstract public function decode($value); + abstract public function decode(mixed $value); /** - * @param mixed $value * @return mixed * @psalm-return ($value is BSONType ? NativeType : $value) */ - public function decodeIfSupported($value) + public function decodeIfSupported(mixed $value) { return $this->canDecode($value) ? $this->decode($value) : $value; } diff --git a/src/Codec/Decoder.php b/src/Codec/Decoder.php index 904e097fe..432fb2dd7 100644 --- a/src/Codec/Decoder.php +++ b/src/Codec/Decoder.php @@ -28,22 +28,20 @@ interface Decoder /** * Checks if the decoder supports a given value. * - * @param mixed $value * @psalm-assert-if-true BSONType $value */ - public function canDecode($value): bool; + public function canDecode(mixed $value): bool; /** * Decodes a given value. If the decoder does not support the value, it * should throw an exception. * - * @param mixed $value * @psalm-param BSONType $value * @return mixed * @psalm-return NativeType * @throws UnsupportedValueException if the decoder does not support the value */ - public function decode($value); + public function decode(mixed $value); /** * Decodes a given value if supported, otherwise returns the value as-is. @@ -51,9 +49,8 @@ public function decode($value); * The DecodeIfSupported trait provides a default implementation of this * method. * - * @param mixed $value * @return mixed * @psalm-return ($value is BSONType ? NativeType : $value) */ - public function decodeIfSupported($value); + public function decodeIfSupported(mixed $value); } diff --git a/src/Codec/DocumentCodec.php b/src/Codec/DocumentCodec.php index ba4488b08..6fabdb936 100644 --- a/src/Codec/DocumentCodec.php +++ b/src/Codec/DocumentCodec.php @@ -30,17 +30,15 @@ interface DocumentCodec extends Codec { /** - * @param mixed $value * @psalm-param Document $value * @psalm-return ObjectType * @throws UnsupportedValueException if the decoder does not support the value */ - public function decode($value): object; + public function decode(mixed $value): object; /** - * @param mixed $value * @psalm-param ObjectType $value * @throws UnsupportedValueException if the encoder does not support the value */ - public function encode($value): Document; + public function encode(mixed $value): Document; } diff --git a/src/Codec/EncodeIfSupported.php b/src/Codec/EncodeIfSupported.php index c4aebac6b..33823cfd6 100644 --- a/src/Codec/EncodeIfSupported.php +++ b/src/Codec/EncodeIfSupported.php @@ -25,27 +25,22 @@ */ trait EncodeIfSupported { - /** - * @param mixed $value - * @psalm-assert-if-true NativeType $value - */ - abstract public function canEncode($value): bool; + /** @psalm-assert-if-true NativeType $value */ + abstract public function canEncode(mixed $value): bool; /** - * @param mixed $value * @psalm-param NativeType $value * @return mixed * @psalm-return BSONType * @throws UnsupportedValueException if the encoder does not support the value */ - abstract public function encode($value); + abstract public function encode(mixed $value); /** - * @param mixed $value * @return mixed * @psalm-return ($value is NativeType ? BSONType : $value) */ - public function encodeIfSupported($value) + public function encodeIfSupported(mixed $value) { return $this->canEncode($value) ? $this->encode($value) : $value; } diff --git a/src/Codec/Encoder.php b/src/Codec/Encoder.php index dba58d9d5..0cd0d58cb 100644 --- a/src/Codec/Encoder.php +++ b/src/Codec/Encoder.php @@ -28,22 +28,20 @@ interface Encoder /** * Checks if the encoder supports a given value. * - * @param mixed $value * @psalm-assert-if-true NativeType $value */ - public function canEncode($value): bool; + public function canEncode(mixed $value): bool; /** * Encodes a given value. If the encoder does not support the value, it * should throw an exception. * - * @param mixed $value * @psalm-param NativeType $value * @return mixed * @psalm-return BSONType * @throws UnsupportedValueException if the encoder does not support the value */ - public function encode($value); + public function encode(mixed $value); /** * Encodes a given value if supported, otherwise returns the value as-is. @@ -51,9 +49,8 @@ public function encode($value); * The EncodeIfSupported trait provides a default implementation of this * method. * - * @param mixed $value * @return mixed * @psalm-return ($value is NativeType ? BSONType : $value) */ - public function encodeIfSupported($value); + public function encodeIfSupported(mixed $value); } diff --git a/src/Collection.php b/src/Collection.php index 708d43d68..5399892bf 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -86,12 +86,6 @@ class Collection private ?DocumentCodec $codec = null; - private string $collectionName; - - private string $databaseName; - - private Manager $manager; - private ReadConcern $readConcern; private ReadPreference $readPreference; @@ -130,7 +124,7 @@ class Collection * @param array $options Collection options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(Manager $manager, string $databaseName, string $collectionName, array $options = []) + public function __construct(private Manager $manager, private string $databaseName, private string $collectionName, array $options = []) { if (strlen($databaseName) < 1) { throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName); @@ -160,10 +154,6 @@ public function __construct(Manager $manager, string $databaseName, string $coll throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } - $this->manager = $manager; - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->codec = $options['codec'] ?? null; $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern(); $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference(); @@ -277,7 +267,7 @@ public function bulkWrite(array $operations, array $options = []) * * @deprecated 1.4 */ - public function count($filter = [], array $options = []) + public function count(array|object $filter = [], array $options = []) { $options = $this->inheritReadOptions($options); @@ -298,7 +288,7 @@ public function count($filter = [], array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function countDocuments($filter = [], array $options = []) + public function countDocuments(array|object $filter = [], array $options = []) { $options = $this->inheritReadOptions($options); @@ -320,7 +310,7 @@ public function countDocuments($filter = [], array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function createIndex($key, array $options = []) + public function createIndex(array|object $key, array $options = []) { $operationOptionKeys = ['comment' => 1, 'commitQuorum' => 1, 'maxTimeMS' => 1, 'session' => 1, 'writeConcern' => 1]; $indexOptions = array_diff_key($options, $operationOptionKeys); @@ -378,7 +368,7 @@ public function createIndexes(array $indexes, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function createSearchIndex($definition, array $options = []): string + public function createSearchIndex(array|object $definition, array $options = []): string { $index = ['definition' => $definition]; if (isset($options['name'])) { @@ -435,7 +425,7 @@ public function createSearchIndexes(array $indexes, array $options = []): array * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function deleteMany($filter, array $options = []) + public function deleteMany(array|object $filter, array $options = []) { $options = $this->inheritWriteOptions($options); @@ -456,7 +446,7 @@ public function deleteMany($filter, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function deleteOne($filter, array $options = []) + public function deleteOne(array|object $filter, array $options = []) { $options = $this->inheritWriteOptions($options); @@ -478,7 +468,7 @@ public function deleteOne($filter, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function distinct(string $fieldName, $filter = [], array $options = []) + public function distinct(string $fieldName, array|object $filter = [], array $options = []) { $options = $this->inheritReadOptions($options); $options = $this->inheritTypeMap($options); @@ -528,7 +518,7 @@ public function drop(array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function dropIndex($indexName, array $options = []) + public function dropIndex(string|IndexInfo $indexName, array $options = []) { $indexName = (string) $indexName; @@ -636,7 +626,7 @@ public function explain(Explainable $explainable, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function find($filter = [], array $options = []) + public function find(array|object $filter = [], array $options = []) { $options = $this->inheritReadOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -658,7 +648,7 @@ public function find($filter = [], array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function findOne($filter = [], array $options = []) + public function findOne(array|object $filter = [], array $options = []) { $options = $this->inheritReadOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -683,7 +673,7 @@ public function findOne($filter = [], array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function findOneAndDelete($filter, array $options = []) + public function findOneAndDelete(array|object $filter, array $options = []) { $options = $this->inheritWriteOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -713,7 +703,7 @@ public function findOneAndDelete($filter, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function findOneAndReplace($filter, $replacement, array $options = []) + public function findOneAndReplace(array|object $filter, array|object $replacement, array $options = []) { $options = $this->inheritWriteOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -743,7 +733,7 @@ public function findOneAndReplace($filter, $replacement, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function findOneAndUpdate($filter, $update, array $options = []) + public function findOneAndUpdate(array|object $filter, array|object $update, array $options = []) { $options = $this->inheritWriteOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -868,7 +858,7 @@ public function insertMany(array $documents, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function insertOne($document, array $options = []) + public function insertOne(array|object $document, array $options = []) { $options = $this->inheritWriteOptions($options); $options = $this->inheritCodec($options); @@ -926,7 +916,7 @@ public function listSearchIndexes(array $options = []): Iterator * @throws DriverRuntimeException for other driver errors (e.g. connection errors) * @throws UnexpectedValueException if the command response was malformed */ - public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = []) + public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, string|array|object $out, array $options = []) { $hasOutputCollection = ! is_mapreduce_output_inline($out); @@ -991,7 +981,7 @@ public function rename(string $toCollectionName, ?string $toDatabaseName = null, * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function replaceOne($filter, $replacement, array $options = []) + public function replaceOne(array|object $filter, array|object $replacement, array $options = []) { $options = $this->inheritWriteOptions($options); $options = $this->inheritCodec($options); @@ -1014,7 +1004,7 @@ public function replaceOne($filter, $replacement, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function updateMany($filter, $update, array $options = []) + public function updateMany(array|object $filter, array|object $update, array $options = []) { $options = $this->inheritWriteOptions($options); @@ -1036,7 +1026,7 @@ public function updateMany($filter, $update, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function updateOne($filter, $update, array $options = []) + public function updateOne(array|object $filter, array|object $update, array $options = []) { $options = $this->inheritWriteOptions($options); @@ -1056,7 +1046,7 @@ public function updateOne($filter, $update, array $options = []) * @throws InvalidArgumentException for parameter parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function updateSearchIndex(string $name, $definition, array $options = []): void + public function updateSearchIndex(string $name, array|object $definition, array $options = []): void { $operation = new UpdateSearchIndex($this->databaseName, $this->collectionName, $name, $definition, $options); $server = select_server_for_write($this->manager, $options); diff --git a/src/Command/ListCollections.php b/src/Command/ListCollections.php index 857b35254..454e145f3 100644 --- a/src/Command/ListCollections.php +++ b/src/Command/ListCollections.php @@ -38,10 +38,6 @@ */ class ListCollections implements Executable { - private string $databaseName; - - private array $options; - /** * Constructs a listCollections command. * @@ -71,7 +67,7 @@ class ListCollections implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, array $options = []) + public function __construct(private string $databaseName, private array $options = []) { if (isset($options['authorizedCollections']) && ! is_bool($options['authorizedCollections'])) { throw InvalidArgumentException::invalidType('"authorizedCollections" option', $options['authorizedCollections'], 'boolean'); @@ -92,9 +88,6 @@ public function __construct(string $databaseName, array $options = []) if (isset($options['session']) && ! $options['session'] instanceof Session) { throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } - - $this->databaseName = $databaseName; - $this->options = $options; } /** diff --git a/src/Command/ListDatabases.php b/src/Command/ListDatabases.php index 3aba5f835..5386559db 100644 --- a/src/Command/ListDatabases.php +++ b/src/Command/ListDatabases.php @@ -39,8 +39,6 @@ */ class ListDatabases implements Executable { - private array $options; - /** * Constructs a listDatabases command. * @@ -69,7 +67,7 @@ class ListDatabases implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(array $options = []) + public function __construct(private array $options = []) { if (isset($options['authorizedDatabases']) && ! is_bool($options['authorizedDatabases'])) { throw InvalidArgumentException::invalidType('"authorizedDatabases" option', $options['authorizedDatabases'], 'boolean'); @@ -90,8 +88,6 @@ public function __construct(array $options = []) if (isset($options['session']) && ! $options['session'] instanceof Session) { throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); } - - $this->options = $options; } /** diff --git a/src/Database.php b/src/Database.php index 95933df8a..d466dcb6a 100644 --- a/src/Database.php +++ b/src/Database.php @@ -61,10 +61,6 @@ class Database private const WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE = 8; - private string $databaseName; - - private Manager $manager; - private ReadConcern $readConcern; private ReadPreference $readPreference; @@ -100,7 +96,7 @@ class Database * @param array $options Database options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(Manager $manager, string $databaseName, array $options = []) + public function __construct(private Manager $manager, private string $databaseName, array $options = []) { if (strlen($databaseName) < 1) { throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName); @@ -122,8 +118,6 @@ public function __construct(Manager $manager, string $databaseName, array $optio throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } - $this->manager = $manager; - $this->databaseName = $databaseName; $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern(); $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference(); $this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP; @@ -237,7 +231,7 @@ public function aggregate(array $pipeline, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function command($command, array $options = []) + public function command(array|object $command, array $options = []) { if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; diff --git a/src/DeleteResult.php b/src/DeleteResult.php index 7d2744ccc..56f3f072d 100644 --- a/src/DeleteResult.php +++ b/src/DeleteResult.php @@ -25,13 +25,10 @@ */ class DeleteResult { - private WriteResult $writeResult; - private bool $isAcknowledged; - public function __construct(WriteResult $writeResult) + public function __construct(private WriteResult $writeResult) { - $this->writeResult = $writeResult; $this->isAcknowledged = $writeResult->isAcknowledged(); } diff --git a/src/Exception/CreateEncryptedCollectionException.php b/src/Exception/CreateEncryptedCollectionException.php index 3aca268fe..5f348edd7 100644 --- a/src/Exception/CreateEncryptedCollectionException.php +++ b/src/Exception/CreateEncryptedCollectionException.php @@ -19,7 +19,6 @@ use Throwable; -use function get_class; use function sprintf; /** @@ -28,13 +27,9 @@ */ final class CreateEncryptedCollectionException extends RuntimeException { - private array $encryptedFields; - - public function __construct(Throwable $previous, array $encryptedFields) + public function __construct(Throwable $previous, private array $encryptedFields) { - parent::__construct(sprintf('Creating encrypted collection failed due to previous %s: %s', get_class($previous), $previous->getMessage()), 0, $previous); - - $this->encryptedFields = $encryptedFields; + parent::__construct(sprintf('Creating encrypted collection failed due to previous %s: %s', $previous::class, $previous->getMessage()), 0, $previous); } /** diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 7091e82af..115f6a5c7 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -40,7 +40,7 @@ public static function cannotCombineCodecAndTypeMap(): self * @param string $name Name of the argument or option * @param mixed $value Actual value (used to derive the type) */ - public static function expectedDocumentType(string $name, $value): self + public static function expectedDocumentType(string $name, mixed $value): self { return new self(sprintf('Expected %s to have type "document" (array or object) but found "%s"', $name, get_debug_type($value))); } @@ -53,7 +53,7 @@ public static function expectedDocumentType(string $name, $value): self * @param string|list $expectedType Expected type as a string or an array containing one or more strings * @return self */ - public static function invalidType(string $name, $value, $expectedType) + public static function invalidType(string $name, mixed $value, string|array $expectedType) { if (is_array($expectedType)) { $expectedType = self::expectedTypesToString($expectedType); diff --git a/src/Exception/ResumeTokenException.php b/src/Exception/ResumeTokenException.php index ce7a35c71..fc8808271 100644 --- a/src/Exception/ResumeTokenException.php +++ b/src/Exception/ResumeTokenException.php @@ -28,7 +28,7 @@ class ResumeTokenException extends RuntimeException * @param mixed $value Actual value (used to derive the type) * @return self */ - public static function invalidType($value) + public static function invalidType(mixed $value) { return new self(sprintf('Expected resume token to have type "array or object" but found "%s"', get_debug_type($value))); } diff --git a/src/Exception/UnsupportedValueException.php b/src/Exception/UnsupportedValueException.php index 33635d8a0..d744baa13 100644 --- a/src/Exception/UnsupportedValueException.php +++ b/src/Exception/UnsupportedValueException.php @@ -24,32 +24,24 @@ class UnsupportedValueException extends InvalidArgumentException implements Exception { - /** @var mixed */ - private $value; - /** @return mixed */ public function getValue() { return $this->value; } - /** @param mixed $value */ - public static function invalidDecodableValue($value): self + public static function invalidDecodableValue(mixed $value): self { return new self(sprintf('Could not decode value of type "%s".', get_debug_type($value)), $value); } - /** @param mixed $value */ - public static function invalidEncodableValue($value): self + public static function invalidEncodableValue(mixed $value): self { return new self(sprintf('Could not encode value of type "%s".', get_debug_type($value)), $value); } - /** @param mixed $value */ - private function __construct(string $message, $value) + private function __construct(string $message, private mixed $value) { parent::__construct($message); - - $this->value = $value; } } diff --git a/src/GridFS/Bucket.php b/src/GridFS/Bucket.php index 8a7ef25b5..56a4e26e2 100644 --- a/src/GridFS/Bucket.php +++ b/src/GridFS/Bucket.php @@ -86,10 +86,6 @@ class Bucket private CollectionWrapper $collectionWrapper; - private string $databaseName; - - private Manager $manager; - private string $bucketName; private bool $disableMD5; @@ -131,7 +127,7 @@ class Bucket * @param array $options Bucket options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(Manager $manager, string $databaseName, array $options = []) + public function __construct(private Manager $manager, private string $databaseName, array $options = []) { if (isset($options['disableMD5']) && $options['disableMD5'] === false) { @trigger_error('Setting GridFS "disableMD5" option to "false" is deprecated since mongodb/mongodb 1.18 and will not be supported in version 2.0.', E_USER_DEPRECATED); @@ -183,8 +179,6 @@ public function __construct(Manager $manager, string $databaseName, array $optio throw InvalidArgumentException::cannotCombineCodecAndTypeMap(); } - $this->manager = $manager; - $this->databaseName = $databaseName; $this->bucketName = $options['bucketName']; $this->chunkSizeBytes = $options['chunkSizeBytes']; $this->codec = $options['codec'] ?? null; @@ -238,7 +232,7 @@ public function __debugInfo() * @throws FileNotFoundException if no file could be selected * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function delete($id) + public function delete(mixed $id) { $file = $this->collectionWrapper->findFileById($id); $this->collectionWrapper->deleteFileAndChunksById($id); @@ -259,7 +253,7 @@ public function delete($id) * @throws StreamException if the file could not be uploaded * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function downloadToStream($id, $destination) + public function downloadToStream(mixed $id, $destination) { if (! is_resource($destination) || get_resource_type($destination) != 'stream') { throw InvalidArgumentException::invalidType('$destination', $destination, 'resource'); @@ -335,7 +329,7 @@ public function drop() * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function find($filter = [], array $options = []) + public function find(array|object $filter = [], array $options = []) { if ($this->codec && ! array_key_exists('codec', $options)) { $options['codec'] = $this->codec; @@ -356,7 +350,7 @@ public function find($filter = [], array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function findOne($filter = [], array $options = []) + public function findOne(array|object $filter = [], array $options = []) { if ($this->codec && ! array_key_exists('codec', $options)) { $options['codec'] = $this->codec; @@ -512,7 +506,7 @@ public function getWriteConcern() * @throws FileNotFoundException if no file could be selected * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function openDownloadStream($id) + public function openDownloadStream(mixed $id) { $file = $this->collectionWrapper->findFileById($id); @@ -632,7 +626,7 @@ public function registerGlobalStreamWrapperAlias(string $alias): void * @throws FileNotFoundException if no file could be selected * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function rename($id, string $newFilename) + public function rename(mixed $id, string $newFilename) { $updateResult = $this->collectionWrapper->updateFilenameForId($id, $newFilename); diff --git a/src/GridFS/CollectionWrapper.php b/src/GridFS/CollectionWrapper.php index 25e8b32a7..0c6b9b96c 100644 --- a/src/GridFS/CollectionWrapper.php +++ b/src/GridFS/CollectionWrapper.php @@ -41,12 +41,8 @@ */ class CollectionWrapper { - private string $bucketName; - private Collection $chunksCollection; - private string $databaseName; - private bool $checkedIndexes = false; private Collection $filesCollection; @@ -61,21 +57,16 @@ class CollectionWrapper * @param array $collectionOptions Collection options * @throws InvalidArgumentException */ - public function __construct(Manager $manager, string $databaseName, string $bucketName, array $collectionOptions = []) + public function __construct(Manager $manager, private string $databaseName, private string $bucketName, array $collectionOptions = []) { - $this->databaseName = $databaseName; - $this->bucketName = $bucketName; - $this->filesCollection = new Collection($manager, $databaseName, sprintf('%s.files', $bucketName), $collectionOptions); $this->chunksCollection = new Collection($manager, $databaseName, sprintf('%s.chunks', $bucketName), $collectionOptions); } /** * Deletes all GridFS chunks for a given file ID. - * - * @param mixed $id */ - public function deleteChunksByFilesId($id): void + public function deleteChunksByFilesId(mixed $id): void { $this->chunksCollection->deleteMany(['files_id' => $id]); } @@ -105,10 +96,8 @@ public function deleteFileAndChunksByFilename(string $filename): ?int /** * Deletes a GridFS file and related chunks by ID. - * - * @param mixed $id */ - public function deleteFileAndChunksById($id): void + public function deleteFileAndChunksById(mixed $id): void { $this->filesCollection->deleteOne(['_id' => $id]); $this->chunksCollection->deleteMany(['files_id' => $id]); @@ -130,7 +119,7 @@ public function dropCollections(): void * @param integer $fromChunk Starting chunk (inclusive) * @return CursorInterface&Iterator */ - public function findChunksByFileId($id, int $fromChunk = 0) + public function findChunksByFileId(mixed $id, int $fromChunk = 0) { return $this->chunksCollection->find( [ @@ -187,10 +176,8 @@ public function findFileByFilenameAndRevision(string $filename, int $revision): /** * Finds a GridFS file document for a given ID. - * - * @param mixed $id */ - public function findFileById($id): ?object + public function findFileById(mixed $id): ?object { $file = $this->filesCollection->findOne( ['_id' => $id], @@ -209,7 +196,7 @@ public function findFileById($id): ?object * @param array $options Additional options * @return CursorInterface&Iterator */ - public function findFiles($filter, array $options = []) + public function findFiles(array|object $filter, array $options = []) { return $this->filesCollection->find($filter, $options); } @@ -221,7 +208,7 @@ public function findFiles($filter, array $options = []) * @param array $options Additional options * @return array|object|null */ - public function findOneFile($filter, array $options = []) + public function findOneFile(array|object $filter, array $options = []) { return $this->filesCollection->findOne($filter, $options); } @@ -251,7 +238,7 @@ public function getFilesCollection(): Collection * * @param array|object $chunk Chunk document */ - public function insertChunk($chunk): void + public function insertChunk(array|object $chunk): void { if (! $this->checkedIndexes) { $this->ensureIndexes(); @@ -267,7 +254,7 @@ public function insertChunk($chunk): void * * @param array|object $file File document */ - public function insertFile($file): void + public function insertFile(array|object $file): void { if (! $this->checkedIndexes) { $this->ensureIndexes(); @@ -289,10 +276,8 @@ public function updateFilenameForFilename(string $filename, string $newFilename) /** * Updates the filename field in the file document for a given ID. - * - * @param mixed $id */ - public function updateFilenameForId($id, string $filename): UpdateResult + public function updateFilenameForId(mixed $id, string $filename): UpdateResult { return $this->filesCollection->updateOne( ['_id' => $id], diff --git a/src/GridFS/Exception/FileNotFoundException.php b/src/GridFS/Exception/FileNotFoundException.php index 9243db6f4..99c6d854a 100644 --- a/src/GridFS/Exception/FileNotFoundException.php +++ b/src/GridFS/Exception/FileNotFoundException.php @@ -55,7 +55,7 @@ public static function byFilenameAndRevision(string $filename, int $revision, st * @param string $namespace Namespace for the files collection * @return self */ - public static function byId($id, string $namespace) + public static function byId(mixed $id, string $namespace) { $json = Document::fromPHP(['_id' => $id])->toRelaxedExtendedJSON(); diff --git a/src/GridFS/Exception/LogicException.php b/src/GridFS/Exception/LogicException.php index a2b6378c7..a6835423c 100644 --- a/src/GridFS/Exception/LogicException.php +++ b/src/GridFS/Exception/LogicException.php @@ -39,10 +39,9 @@ public static function bucketAliasNotRegistered(string $alias): self /** * Throw when an invalid "gridfs" context option is provided. * - * @param mixed $context * @internal */ - public static function invalidContext($context): self + public static function invalidContext(mixed $context): self { return new self(sprintf('Expected "gridfs" stream context to have type "array" but found "%s"', get_debug_type($context))); } @@ -50,10 +49,9 @@ public static function invalidContext($context): self /** * Thrown when a context is provided with an incorrect collection wrapper. * - * @param mixed $object * @internal */ - public static function invalidContextCollectionWrapper($object): self + public static function invalidContextCollectionWrapper(mixed $object): self { return new self(sprintf('Expected "collectionWrapper" in "gridfs" stream context to have type "%s" but found "%s"', CollectionWrapper::class, get_debug_type($object))); } diff --git a/src/GridFS/Exception/StreamException.php b/src/GridFS/Exception/StreamException.php index 8dab6d668..880182588 100644 --- a/src/GridFS/Exception/StreamException.php +++ b/src/GridFS/Exception/StreamException.php @@ -23,11 +23,10 @@ public static function downloadFromFilenameFailed(string $filename, $source, $de } /** - * @param mixed $id * @param resource $source * @param resource $destination */ - public static function downloadFromIdFailed($id, $source, $destination): self + public static function downloadFromIdFailed(mixed $id, $source, $destination): self { $idString = Document::fromPHP(['_id' => $id])->toRelaxedExtendedJSON(); $sourceMetadata = stream_get_meta_data($source); diff --git a/src/GridFS/ReadableStream.php b/src/GridFS/ReadableStream.php index f0e433140..980e3e19f 100644 --- a/src/GridFS/ReadableStream.php +++ b/src/GridFS/ReadableStream.php @@ -51,12 +51,8 @@ class ReadableStream /** @var (CursorInterface&Iterator)|null */ private ?Iterator $chunksIterator = null; - private CollectionWrapper $collectionWrapper; - private int $expectedLastChunkSize = 0; - private object $file; - private int $length; private int $numChunks = 0; @@ -68,7 +64,7 @@ class ReadableStream * @param object $file GridFS file document * @throws CorruptFileException */ - public function __construct(CollectionWrapper $collectionWrapper, object $file) + public function __construct(private CollectionWrapper $collectionWrapper, private object $file) { if (! isset($file->chunkSize) || ! is_integer($file->chunkSize) || $file->chunkSize < 1) { throw new CorruptFileException('file.chunkSize is not an integer >= 1'); @@ -82,12 +78,9 @@ public function __construct(CollectionWrapper $collectionWrapper, object $file) throw new CorruptFileException('file._id does not exist'); } - $this->file = $file; $this->chunkSize = $file->chunkSize; $this->length = $file->length; - $this->collectionWrapper = $collectionWrapper; - if ($this->length > 0) { $this->numChunks = (integer) ceil($this->length / $this->chunkSize); $this->expectedLastChunkSize = $this->length - (($this->numChunks - 1) * $this->chunkSize); diff --git a/src/GridFS/StreamWrapper.php b/src/GridFS/StreamWrapper.php index b433c8dd6..cc1d89fc9 100644 --- a/src/GridFS/StreamWrapper.php +++ b/src/GridFS/StreamWrapper.php @@ -54,8 +54,7 @@ class StreamWrapper /** @var resource|null Stream context (set by PHP) */ public $context; - /** @var ReadableStream|WritableStream|null */ - private $stream; + private ReadableStream|WritableStream|null $stream = null; /** @var array */ private static array $contextResolvers = []; @@ -323,7 +322,7 @@ public function url_stat(string $path, int $flags) try { $this->stream_open($path, 'r', 0, $openedPath); - } catch (FileNotFoundException $e) { + } catch (FileNotFoundException) { return false; } diff --git a/src/GridFS/WritableStream.php b/src/GridFS/WritableStream.php index 096968bb6..65b35de90 100644 --- a/src/GridFS/WritableStream.php +++ b/src/GridFS/WritableStream.php @@ -54,8 +54,6 @@ class WritableStream private bool $disableMD5; - private CollectionWrapper $collectionWrapper; - private array $file; private ?HashContext $hashCtx = null; @@ -92,7 +90,7 @@ class WritableStream * @param array $options Upload options * @throws InvalidArgumentException */ - public function __construct(CollectionWrapper $collectionWrapper, string $filename, array $options = []) + public function __construct(private CollectionWrapper $collectionWrapper, string $filename, array $options = []) { $options += [ '_id' => new ObjectId(), @@ -125,7 +123,6 @@ public function __construct(CollectionWrapper $collectionWrapper, string $filena } $this->chunkSize = $options['chunkSizeBytes']; - $this->collectionWrapper = $collectionWrapper; $this->disableMD5 = $options['disableMD5']; if (! $this->disableMD5) { @@ -239,7 +236,7 @@ private function abort(): void { try { $this->collectionWrapper->deleteChunksByFilesId($this->file['_id']); - } catch (DriverRuntimeException $e) { + } catch (DriverRuntimeException) { // We are already handling an error if abort() is called, so suppress this } diff --git a/src/InsertManyResult.php b/src/InsertManyResult.php index f8c33ad0b..6ad2a9595 100644 --- a/src/InsertManyResult.php +++ b/src/InsertManyResult.php @@ -25,16 +25,10 @@ */ class InsertManyResult { - private WriteResult $writeResult; - - private array $insertedIds; - private bool $isAcknowledged; - public function __construct(WriteResult $writeResult, array $insertedIds) + public function __construct(private WriteResult $writeResult, private array $insertedIds) { - $this->writeResult = $writeResult; - $this->insertedIds = $insertedIds; $this->isAcknowledged = $writeResult->isAcknowledged(); } diff --git a/src/InsertOneResult.php b/src/InsertOneResult.php index da851d204..9c72775c8 100644 --- a/src/InsertOneResult.php +++ b/src/InsertOneResult.php @@ -25,18 +25,10 @@ */ class InsertOneResult { - private WriteResult $writeResult; - - /** @var mixed */ - private $insertedId; - private bool $isAcknowledged; - /** @param mixed $insertedId */ - public function __construct(WriteResult $writeResult, $insertedId) + public function __construct(private WriteResult $writeResult, private mixed $insertedId) { - $this->writeResult = $writeResult; - $this->insertedId = $insertedId; $this->isAcknowledged = $writeResult->isAcknowledged(); } diff --git a/src/Model/BSONIterator.php b/src/Model/BSONIterator.php index 43dae2ed2..2ca257c7f 100644 --- a/src/Model/BSONIterator.php +++ b/src/Model/BSONIterator.php @@ -44,16 +44,12 @@ class BSONIterator implements Iterator private int $bufferLength; - /** @var array|object|null */ - private $current = null; + private array|object|null $current = null; private int $key = 0; private int $position = 0; - /** @var array{typeMap: array, ...} */ - private array $options; - /** * @see https://php.net/iterator.current * @return mixed @@ -119,19 +115,18 @@ public function valid(): bool * @param array $options Iterator options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $data, array $options = []) + public function __construct(string $data, private array $options = []) { if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); } if (! isset($options['typeMap'])) { - $options['typeMap'] = []; + $this->options['typeMap'] = []; } $this->buffer = $data; $this->bufferLength = strlen($data); - $this->options = $options; } private function advance(): void diff --git a/src/Model/ChangeStreamIterator.php b/src/Model/ChangeStreamIterator.php index d2a9b6ac8..aa635dda9 100644 --- a/src/Model/ChangeStreamIterator.php +++ b/src/Model/ChangeStreamIterator.php @@ -61,10 +61,7 @@ class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber private bool $isValid = false; - private ?object $postBatchResumeToken = null; - - /** @var array|object|null */ - private $resumeToken; + private array|object|null $resumeToken = null; private Server $server; @@ -176,10 +173,9 @@ public function valid(): bool /** * @internal - * @param array|object|null $initialResumeToken * @psalm-param CursorInterface&Iterator $cursor */ - public function __construct(CursorInterface $cursor, int $firstBatchSize, $initialResumeToken, ?object $postBatchResumeToken) + public function __construct(CursorInterface $cursor, int $firstBatchSize, array|object|null $initialResumeToken, private ?object $postBatchResumeToken = null) { if (! $cursor instanceof Iterator) { throw InvalidArgumentException::invalidType( @@ -197,7 +193,6 @@ public function __construct(CursorInterface $cursor, int $firstBatchSize, $initi $this->batchSize = $firstBatchSize; $this->isRewindNop = ($firstBatchSize === 0); - $this->postBatchResumeToken = $postBatchResumeToken; $this->resumeToken = $initialResumeToken; $this->server = $cursor->getServer(); } @@ -247,7 +242,7 @@ final public function commandSucceeded(CommandSucceededEvent $event): void * @throws InvalidArgumentException * @throws ResumeTokenException if the resume token is not found or invalid */ - private function extractResumeToken($document) + private function extractResumeToken(array|object $document) { if (! is_document($document)) { throw InvalidArgumentException::expectedDocumentType('$document', $document); diff --git a/src/Model/CodecCursor.php b/src/Model/CodecCursor.php index 556f76c0f..e6d1c4db3 100644 --- a/src/Model/CodecCursor.php +++ b/src/Model/CodecCursor.php @@ -44,11 +44,6 @@ class CodecCursor implements CursorInterface, Iterator { private const TYPEMAP = ['root' => 'bson']; - private Cursor $cursor; - - /** @var DocumentCodec */ - private DocumentCodec $codec; - /** @var TValue|null */ private ?object $current = null; @@ -140,9 +135,7 @@ public function valid(): bool } /** @param DocumentCodec $codec */ - private function __construct(Cursor $cursor, DocumentCodec $codec) + private function __construct(private Cursor $cursor, private DocumentCodec $codec) { - $this->cursor = $cursor; - $this->codec = $codec; } } diff --git a/src/Model/CollectionInfo.php b/src/Model/CollectionInfo.php index 20f72b14e..2850a4be4 100644 --- a/src/Model/CollectionInfo.php +++ b/src/Model/CollectionInfo.php @@ -36,12 +36,9 @@ */ class CollectionInfo implements ArrayAccess { - private array $info; - /** @param array $info Collection info */ - public function __construct(array $info) + public function __construct(private array $info) { - $this->info = $info; } /** @@ -147,12 +144,11 @@ public function isCapped() * Check whether a field exists in the collection information. * * @see https://php.net/arrayaccess.offsetexists - * @param mixed $offset * @return boolean * @psalm-param array-key $offset */ #[ReturnTypeWillChange] - public function offsetExists($offset) + public function offsetExists(mixed $offset) { return array_key_exists($offset, $this->info); } @@ -161,12 +157,11 @@ public function offsetExists($offset) * Return the field's value from the collection information. * * @see https://php.net/arrayaccess.offsetget - * @param mixed $offset * @return mixed * @psalm-param array-key $offset */ #[ReturnTypeWillChange] - public function offsetGet($offset) + public function offsetGet(mixed $offset) { return $this->info[$offset]; } @@ -175,13 +170,11 @@ public function offsetGet($offset) * Not supported. * * @see https://php.net/arrayaccess.offsetset - * @param mixed $offset - * @param mixed $value * @throws BadMethodCallException * @return void */ #[ReturnTypeWillChange] - public function offsetSet($offset, $value) + public function offsetSet(mixed $offset, mixed $value) { throw BadMethodCallException::classIsImmutable(self::class); } @@ -190,12 +183,11 @@ public function offsetSet($offset, $value) * Not supported. * * @see https://php.net/arrayaccess.offsetunset - * @param mixed $offset * @throws BadMethodCallException * @return void */ #[ReturnTypeWillChange] - public function offsetUnset($offset) + public function offsetUnset(mixed $offset) { throw BadMethodCallException::classIsImmutable(self::class); } diff --git a/src/Model/CollectionInfoCommandIterator.php b/src/Model/CollectionInfoCommandIterator.php index 9a70b4c1d..14ef39a02 100644 --- a/src/Model/CollectionInfoCommandIterator.php +++ b/src/Model/CollectionInfoCommandIterator.php @@ -34,14 +34,10 @@ */ class CollectionInfoCommandIterator extends IteratorIterator implements CollectionInfoIterator { - private ?string $databaseName = null; - /** @param Traversable $iterator */ - public function __construct(Traversable $iterator, ?string $databaseName = null) + public function __construct(Traversable $iterator, private ?string $databaseName = null) { parent::__construct($iterator); - - $this->databaseName = $databaseName; } /** diff --git a/src/Model/DatabaseInfo.php b/src/Model/DatabaseInfo.php index 05cb312b9..2803532f8 100644 --- a/src/Model/DatabaseInfo.php +++ b/src/Model/DatabaseInfo.php @@ -35,12 +35,9 @@ */ class DatabaseInfo implements ArrayAccess { - private array $info; - /** @param array $info Database info */ - public function __construct(array $info) + public function __construct(private array $info) { - $this->info = $info; } /** @@ -89,12 +86,11 @@ public function isEmpty() * Check whether a field exists in the database information. * * @see https://php.net/arrayaccess.offsetexists - * @param mixed $offset * @return boolean * @psalm-param array-key $offset */ #[ReturnTypeWillChange] - public function offsetExists($offset) + public function offsetExists(mixed $offset) { return array_key_exists($offset, $this->info); } @@ -103,12 +99,11 @@ public function offsetExists($offset) * Return the field's value from the database information. * * @see https://php.net/arrayaccess.offsetget - * @param mixed $offset * @return mixed * @psalm-param array-key $offset */ #[ReturnTypeWillChange] - public function offsetGet($offset) + public function offsetGet(mixed $offset) { return $this->info[$offset]; } @@ -117,13 +112,11 @@ public function offsetGet($offset) * Not supported. * * @see https://php.net/arrayaccess.offsetset - * @param mixed $offset - * @param mixed $value * @throws BadMethodCallException * @return void */ #[ReturnTypeWillChange] - public function offsetSet($offset, $value) + public function offsetSet(mixed $offset, mixed $value) { throw BadMethodCallException::classIsImmutable(self::class); } @@ -132,12 +125,11 @@ public function offsetSet($offset, $value) * Not supported. * * @see https://php.net/arrayaccess.offsetunset - * @param mixed $offset * @throws BadMethodCallException * @return void */ #[ReturnTypeWillChange] - public function offsetUnset($offset) + public function offsetUnset(mixed $offset) { throw BadMethodCallException::classIsImmutable(self::class); } diff --git a/src/Model/DatabaseInfoLegacyIterator.php b/src/Model/DatabaseInfoLegacyIterator.php index 4bbf46a42..da657acec 100644 --- a/src/Model/DatabaseInfoLegacyIterator.php +++ b/src/Model/DatabaseInfoLegacyIterator.php @@ -34,11 +34,8 @@ */ class DatabaseInfoLegacyIterator implements DatabaseInfoIterator { - private array $databases; - - public function __construct(array $databases) + public function __construct(private array $databases) { - $this->databases = $databases; } /** diff --git a/src/Model/IndexInfo.php b/src/Model/IndexInfo.php index a5df7111c..45eea5027 100644 --- a/src/Model/IndexInfo.php +++ b/src/Model/IndexInfo.php @@ -44,12 +44,9 @@ */ class IndexInfo implements ArrayAccess { - private array $info; - /** @param array $info Index info */ - public function __construct(array $info) + public function __construct(private array $info) { - $this->info = $info; } /** @@ -183,12 +180,11 @@ public function isUnique() * Check whether a field exists in the index information. * * @see https://php.net/arrayaccess.offsetexists - * @param mixed $offset * @return boolean * @psalm-param array-key $offset */ #[ReturnTypeWillChange] - public function offsetExists($offset) + public function offsetExists(mixed $offset) { return array_key_exists($offset, $this->info); } @@ -202,12 +198,11 @@ public function offsetExists($offset) * * @see https://php.net/arrayaccess.offsetget * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst#getting-full-index-information - * @param mixed $offset * @return mixed * @psalm-param array-key $offset */ #[ReturnTypeWillChange] - public function offsetGet($offset) + public function offsetGet(mixed $offset) { return $this->info[$offset]; } @@ -216,13 +211,11 @@ public function offsetGet($offset) * Not supported. * * @see https://php.net/arrayaccess.offsetset - * @param mixed $offset - * @param mixed $value * @throws BadMethodCallException * @return void */ #[ReturnTypeWillChange] - public function offsetSet($offset, $value) + public function offsetSet(mixed $offset, mixed $value) { throw BadMethodCallException::classIsImmutable(self::class); } @@ -231,12 +224,11 @@ public function offsetSet($offset, $value) * Not supported. * * @see https://php.net/arrayaccess.offsetunset - * @param mixed $offset * @throws BadMethodCallException * @return void */ #[ReturnTypeWillChange] - public function offsetUnset($offset) + public function offsetUnset(mixed $offset) { throw BadMethodCallException::classIsImmutable(self::class); } diff --git a/src/Model/IndexInfoIteratorIterator.php b/src/Model/IndexInfoIteratorIterator.php index 3bbbcd53f..a2fa85d6f 100644 --- a/src/Model/IndexInfoIteratorIterator.php +++ b/src/Model/IndexInfoIteratorIterator.php @@ -38,14 +38,10 @@ */ class IndexInfoIteratorIterator extends IteratorIterator implements IndexInfoIterator { - private ?string $ns = null; - /** @param Traversable $iterator */ - public function __construct(Traversable $iterator, ?string $ns = null) + public function __construct(Traversable $iterator, private ?string $ns = null) { parent::__construct($iterator); - - $this->ns = $ns; } /** diff --git a/src/Model/IndexInput.php b/src/Model/IndexInput.php index ae1726550..657c2b6da 100644 --- a/src/Model/IndexInput.php +++ b/src/Model/IndexInput.php @@ -40,13 +40,11 @@ */ class IndexInput implements Serializable { - private array $index; - /** * @param array $index Index specification * @throws InvalidArgumentException */ - public function __construct(array $index) + public function __construct(private array $index) { if (! isset($index['key'])) { throw new InvalidArgumentException('Required "key" document is missing from index specification'); @@ -63,14 +61,12 @@ public function __construct(array $index) } if (! isset($index['name'])) { - $index['name'] = $this->generateIndexName($index['key']); + $this->index['name'] = $this->generateIndexName($index['key']); } - if (! is_string($index['name'])) { - throw InvalidArgumentException::invalidType('"name" option', $index['name'], 'string'); + if (! is_string($this->index['name'])) { + throw InvalidArgumentException::invalidType('"name" option', $this->index['name'], 'string'); } - - $this->index = $index; } /** @@ -99,7 +95,7 @@ public function bsonSerialize(): stdClass * which denote order or an index type * @throws InvalidArgumentException if $document is not an array or object */ - private function generateIndexName($document): string + private function generateIndexName(array|object $document): string { $document = document_to_array($document); diff --git a/src/Model/SearchIndexInput.php b/src/Model/SearchIndexInput.php index e28aea9b0..1b8baf533 100644 --- a/src/Model/SearchIndexInput.php +++ b/src/Model/SearchIndexInput.php @@ -36,13 +36,11 @@ */ class SearchIndexInput implements Serializable { - private array $index; - /** * @param array{name?: string, definition: array|object} $index Search index specification * @throws InvalidArgumentException */ - public function __construct(array $index) + public function __construct(private array $index) { if (! isset($index['definition'])) { throw new InvalidArgumentException('Required "definition" document is missing from search index specification'); @@ -56,8 +54,6 @@ public function __construct(array $index) if (isset($index['name']) && ! is_string($index['name'])) { throw InvalidArgumentException::invalidType('"name" option', $index['name'], 'string'); } - - $this->index = $index; } /** diff --git a/src/Operation/Aggregate.php b/src/Operation/Aggregate.php index 0b49748de..ba6e1ea93 100644 --- a/src/Operation/Aggregate.php +++ b/src/Operation/Aggregate.php @@ -51,14 +51,6 @@ */ class Aggregate implements Executable, Explainable { - private string $databaseName; - - private ?string $collectionName = null; - - private array $pipeline; - - private array $options; - private bool $isWrite; /** @@ -126,102 +118,97 @@ class Aggregate implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, ?string $collectionName, array $pipeline, array $options = []) + public function __construct(private string $databaseName, private ?string $collectionName = null, private array $pipeline, private array $options = []) { if (! is_pipeline($pipeline, true /* allowEmpty */)) { throw new InvalidArgumentException('$pipeline is not a valid aggregation pipeline'); } - if (isset($options['allowDiskUse']) && ! is_bool($options['allowDiskUse'])) { - throw InvalidArgumentException::invalidType('"allowDiskUse" option', $options['allowDiskUse'], 'boolean'); + if (isset($this->options['allowDiskUse']) && ! is_bool($this->options['allowDiskUse'])) { + throw InvalidArgumentException::invalidType('"allowDiskUse" option', $this->options['allowDiskUse'], 'boolean'); } - if (isset($options['batchSize']) && ! is_integer($options['batchSize'])) { - throw InvalidArgumentException::invalidType('"batchSize" option', $options['batchSize'], 'integer'); + if (isset($this->options['batchSize']) && ! is_integer($this->options['batchSize'])) { + throw InvalidArgumentException::invalidType('"batchSize" option', $this->options['batchSize'], 'integer'); } - if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { - throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); + if (isset($this->options['bypassDocumentValidation']) && ! is_bool($this->options['bypassDocumentValidation'])) { + throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $this->options['bypassDocumentValidation'], 'boolean'); } - if (isset($options['codec']) && ! $options['codec'] instanceof DocumentCodec) { - throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class); + if (isset($this->options['codec']) && ! $this->options['codec'] instanceof DocumentCodec) { + throw InvalidArgumentException::invalidType('"codec" option', $this->options['codec'], DocumentCodec::class); } - if (isset($options['collation']) && ! is_document($options['collation'])) { - throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + if (isset($this->options['collation']) && ! is_document($this->options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $this->options['collation']); } - if (isset($options['explain']) && ! is_bool($options['explain'])) { - throw InvalidArgumentException::invalidType('"explain" option', $options['explain'], 'boolean'); + if (isset($this->options['explain']) && ! is_bool($this->options['explain'])) { + throw InvalidArgumentException::invalidType('"explain" option', $this->options['explain'], 'boolean'); } - if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) { - throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object'); + if (isset($this->options['hint']) && ! is_string($this->options['hint']) && ! is_array($this->options['hint']) && ! is_object($this->options['hint'])) { + throw InvalidArgumentException::invalidType('"hint" option', $this->options['hint'], 'string or array or object'); } - if (isset($options['let']) && ! is_document($options['let'])) { - throw InvalidArgumentException::expectedDocumentType('"let" option', $options['let']); + if (isset($this->options['let']) && ! is_document($this->options['let'])) { + throw InvalidArgumentException::expectedDocumentType('"let" option', $this->options['let']); } - if (isset($options['maxAwaitTimeMS']) && ! is_integer($options['maxAwaitTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxAwaitTimeMS" option', $options['maxAwaitTimeMS'], 'integer'); + if (isset($this->options['maxAwaitTimeMS']) && ! is_integer($this->options['maxAwaitTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxAwaitTimeMS" option', $this->options['maxAwaitTimeMS'], 'integer'); } - if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); } - if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); + if (isset($this->options['readConcern']) && ! $this->options['readConcern'] instanceof ReadConcern) { + throw InvalidArgumentException::invalidType('"readConcern" option', $this->options['readConcern'], ReadConcern::class); } - if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); + if (isset($this->options['readPreference']) && ! $this->options['readPreference'] instanceof ReadPreference) { + throw InvalidArgumentException::invalidType('"readPreference" option', $this->options['readPreference'], ReadPreference::class); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['bypassDocumentValidation']) && ! $options['bypassDocumentValidation']) { - unset($options['bypassDocumentValidation']); + if (isset($this->options['bypassDocumentValidation']) && ! $this->options['bypassDocumentValidation']) { + unset($this->options['bypassDocumentValidation']); } - if (isset($options['readConcern']) && $options['readConcern']->isDefault()) { - unset($options['readConcern']); + if (isset($this->options['readConcern']) && $this->options['readConcern']->isDefault()) { + unset($this->options['readConcern']); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } - if (isset($options['codec']) && isset($options['typeMap'])) { + if (isset($this->options['codec']) && isset($this->options['typeMap'])) { throw InvalidArgumentException::cannotCombineCodecAndTypeMap(); } - $this->isWrite = is_last_pipeline_operator_write($pipeline) && ! ($options['explain'] ?? false); + $this->isWrite = is_last_pipeline_operator_write($pipeline) && ! ($this->options['explain'] ?? false); if ($this->isWrite) { /* Ignore batchSize for writes, since no documents are returned and * a batchSize of zero could prevent the pipeline from executing. */ - unset($options['batchSize']); + unset($this->options['batchSize']); } else { - unset($options['writeConcern']); + unset($this->options['writeConcern']); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->pipeline = $pipeline; - $this->options = $options; } /** diff --git a/src/Operation/BulkWrite.php b/src/Operation/BulkWrite.php index 4457d9930..700ce3943 100644 --- a/src/Operation/BulkWrite.php +++ b/src/Operation/BulkWrite.php @@ -53,10 +53,6 @@ class BulkWrite implements Executable public const UPDATE_MANY = 'updateMany'; public const UPDATE_ONE = 'updateOne'; - private string $databaseName; - - private string $collectionName; - /** @var array[] */ private array $operations; @@ -129,7 +125,7 @@ class BulkWrite implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $operations, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, array $operations, array $options = []) { if (empty($operations)) { throw new InvalidArgumentException('$operations is empty'); @@ -173,8 +169,6 @@ public function __construct(string $databaseName, string $collectionName, array unset($options['writeConcern']); } - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; $this->operations = $this->validateOperations($operations, $options['codec'] ?? null); $this->options = $options; } diff --git a/src/Operation/Count.php b/src/Operation/Count.php index e8aaf8a21..b29aa826a 100644 --- a/src/Operation/Count.php +++ b/src/Operation/Count.php @@ -43,15 +43,6 @@ */ class Count implements Executable, Explainable { - private string $databaseName; - - private string $collectionName; - - /** @var array|object */ - private $filter; - - private array $options; - /** * Constructs a count command. * @@ -87,52 +78,47 @@ class Count implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter = [], array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private array|object $filter = [], private array $options = []) { if (! is_document($filter)) { throw InvalidArgumentException::expectedDocumentType('$filter', $filter); } - if (isset($options['collation']) && ! is_document($options['collation'])) { - throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + if (isset($this->options['collation']) && ! is_document($this->options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $this->options['collation']); } - if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) { - throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object'); + if (isset($this->options['hint']) && ! is_string($this->options['hint']) && ! is_array($this->options['hint']) && ! is_object($this->options['hint'])) { + throw InvalidArgumentException::invalidType('"hint" option', $this->options['hint'], 'string or array or object'); } - if (isset($options['limit']) && ! is_integer($options['limit'])) { - throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer'); + if (isset($this->options['limit']) && ! is_integer($this->options['limit'])) { + throw InvalidArgumentException::invalidType('"limit" option', $this->options['limit'], 'integer'); } - if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); } - if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); + if (isset($this->options['readConcern']) && ! $this->options['readConcern'] instanceof ReadConcern) { + throw InvalidArgumentException::invalidType('"readConcern" option', $this->options['readConcern'], ReadConcern::class); } - if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); + if (isset($this->options['readPreference']) && ! $this->options['readPreference'] instanceof ReadPreference) { + throw InvalidArgumentException::invalidType('"readPreference" option', $this->options['readPreference'], ReadPreference::class); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['skip']) && ! is_integer($options['skip'])) { - throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer'); + if (isset($this->options['skip']) && ! is_integer($this->options['skip'])) { + throw InvalidArgumentException::invalidType('"skip" option', $this->options['skip'], 'integer'); } - if (isset($options['readConcern']) && $options['readConcern']->isDefault()) { - unset($options['readConcern']); + if (isset($this->options['readConcern']) && $this->options['readConcern']->isDefault()) { + unset($this->options['readConcern']); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->filter = $filter; - $this->options = $options; } /** diff --git a/src/Operation/CountDocuments.php b/src/Operation/CountDocuments.php index fad2e94db..5d6502fca 100644 --- a/src/Operation/CountDocuments.php +++ b/src/Operation/CountDocuments.php @@ -39,13 +39,6 @@ */ class CountDocuments implements Executable { - private string $databaseName; - - private string $collectionName; - - /** @var array|object */ - private $filter; - private array $aggregateOptions; private array $countOptions; @@ -87,7 +80,7 @@ class CountDocuments implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private array|object $filter, array $options = []) { if (! is_document($filter)) { throw InvalidArgumentException::expectedDocumentType('$filter', $filter); @@ -101,10 +94,6 @@ public function __construct(string $databaseName, string $collectionName, $filte throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer'); } - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->filter = $filter; - $this->aggregateOptions = array_intersect_key($options, ['collation' => 1, 'comment' => 1, 'hint' => 1, 'maxTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1]); $this->countOptions = array_intersect_key($options, ['limit' => 1, 'skip' => 1]); diff --git a/src/Operation/CreateCollection.php b/src/Operation/CreateCollection.php index 470359542..3292e6fa7 100644 --- a/src/Operation/CreateCollection.php +++ b/src/Operation/CreateCollection.php @@ -46,12 +46,6 @@ class CreateCollection implements Executable public const USE_POWER_OF_2_SIZES = 1; public const NO_PADDING = 2; - private string $databaseName; - - private string $collectionName; - - private array $options = []; - /** * Constructs a create command. * @@ -139,111 +133,107 @@ class CreateCollection implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private array $options = []) { - if (isset($options['autoIndexId']) && ! is_bool($options['autoIndexId'])) { - throw InvalidArgumentException::invalidType('"autoIndexId" option', $options['autoIndexId'], 'boolean'); + if (isset($this->options['autoIndexId']) && ! is_bool($this->options['autoIndexId'])) { + throw InvalidArgumentException::invalidType('"autoIndexId" option', $this->options['autoIndexId'], 'boolean'); } - if (isset($options['capped']) && ! is_bool($options['capped'])) { - throw InvalidArgumentException::invalidType('"capped" option', $options['capped'], 'boolean'); + if (isset($this->options['capped']) && ! is_bool($this->options['capped'])) { + throw InvalidArgumentException::invalidType('"capped" option', $this->options['capped'], 'boolean'); } - if (isset($options['changeStreamPreAndPostImages']) && ! is_document($options['changeStreamPreAndPostImages'])) { - throw InvalidArgumentException::expectedDocumentType('"changeStreamPreAndPostImages" option', $options['changeStreamPreAndPostImages']); + if (isset($this->options['changeStreamPreAndPostImages']) && ! is_document($this->options['changeStreamPreAndPostImages'])) { + throw InvalidArgumentException::expectedDocumentType('"changeStreamPreAndPostImages" option', $this->options['changeStreamPreAndPostImages']); } - if (isset($options['clusteredIndex']) && ! is_document($options['clusteredIndex'])) { - throw InvalidArgumentException::expectedDocumentType('"clusteredIndex" option', $options['clusteredIndex']); + if (isset($this->options['clusteredIndex']) && ! is_document($this->options['clusteredIndex'])) { + throw InvalidArgumentException::expectedDocumentType('"clusteredIndex" option', $this->options['clusteredIndex']); } - if (isset($options['collation']) && ! is_document($options['collation'])) { - throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + if (isset($this->options['collation']) && ! is_document($this->options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $this->options['collation']); } - if (isset($options['encryptedFields']) && ! is_document($options['encryptedFields'])) { - throw InvalidArgumentException::expectedDocumentType('"encryptedFields" option', $options['encryptedFields']); + if (isset($this->options['encryptedFields']) && ! is_document($this->options['encryptedFields'])) { + throw InvalidArgumentException::expectedDocumentType('"encryptedFields" option', $this->options['encryptedFields']); } - if (isset($options['expireAfterSeconds']) && ! is_integer($options['expireAfterSeconds'])) { - throw InvalidArgumentException::invalidType('"expireAfterSeconds" option', $options['expireAfterSeconds'], 'integer'); + if (isset($this->options['expireAfterSeconds']) && ! is_integer($this->options['expireAfterSeconds'])) { + throw InvalidArgumentException::invalidType('"expireAfterSeconds" option', $this->options['expireAfterSeconds'], 'integer'); } - if (isset($options['flags']) && ! is_integer($options['flags'])) { - throw InvalidArgumentException::invalidType('"flags" option', $options['flags'], 'integer'); + if (isset($this->options['flags']) && ! is_integer($this->options['flags'])) { + throw InvalidArgumentException::invalidType('"flags" option', $this->options['flags'], 'integer'); } - if (isset($options['indexOptionDefaults']) && ! is_document($options['indexOptionDefaults'])) { - throw InvalidArgumentException::expectedDocumentType('"indexOptionDefaults" option', $options['indexOptionDefaults']); + if (isset($this->options['indexOptionDefaults']) && ! is_document($this->options['indexOptionDefaults'])) { + throw InvalidArgumentException::expectedDocumentType('"indexOptionDefaults" option', $this->options['indexOptionDefaults']); } - if (isset($options['max']) && ! is_integer($options['max'])) { - throw InvalidArgumentException::invalidType('"max" option', $options['max'], 'integer'); + if (isset($this->options['max']) && ! is_integer($this->options['max'])) { + throw InvalidArgumentException::invalidType('"max" option', $this->options['max'], 'integer'); } - if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); } - if (isset($options['pipeline']) && ! is_array($options['pipeline'])) { - throw InvalidArgumentException::invalidType('"pipeline" option', $options['pipeline'], 'array'); + if (isset($this->options['pipeline']) && ! is_array($this->options['pipeline'])) { + throw InvalidArgumentException::invalidType('"pipeline" option', $this->options['pipeline'], 'array'); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['size']) && ! is_integer($options['size'])) { - throw InvalidArgumentException::invalidType('"size" option', $options['size'], 'integer'); + if (isset($this->options['size']) && ! is_integer($this->options['size'])) { + throw InvalidArgumentException::invalidType('"size" option', $this->options['size'], 'integer'); } - if (isset($options['storageEngine']) && ! is_document($options['storageEngine'])) { - throw InvalidArgumentException::expectedDocumentType('"storageEngine" option', $options['storageEngine']); + if (isset($this->options['storageEngine']) && ! is_document($this->options['storageEngine'])) { + throw InvalidArgumentException::expectedDocumentType('"storageEngine" option', $this->options['storageEngine']); } - if (isset($options['timeseries']) && ! is_document($options['timeseries'])) { - throw InvalidArgumentException::expectedDocumentType('"timeseries" option', $options['timeseries']); + if (isset($this->options['timeseries']) && ! is_document($this->options['timeseries'])) { + throw InvalidArgumentException::expectedDocumentType('"timeseries" option', $this->options['timeseries']); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['validationAction']) && ! is_string($options['validationAction'])) { - throw InvalidArgumentException::invalidType('"validationAction" option', $options['validationAction'], 'string'); + if (isset($this->options['validationAction']) && ! is_string($this->options['validationAction'])) { + throw InvalidArgumentException::invalidType('"validationAction" option', $this->options['validationAction'], 'string'); } - if (isset($options['validationLevel']) && ! is_string($options['validationLevel'])) { - throw InvalidArgumentException::invalidType('"validationLevel" option', $options['validationLevel'], 'string'); + if (isset($this->options['validationLevel']) && ! is_string($this->options['validationLevel'])) { + throw InvalidArgumentException::invalidType('"validationLevel" option', $this->options['validationLevel'], 'string'); } - if (isset($options['validator']) && ! is_document($options['validator'])) { - throw InvalidArgumentException::expectedDocumentType('"validator" option', $options['validator']); + if (isset($this->options['validator']) && ! is_document($this->options['validator'])) { + throw InvalidArgumentException::expectedDocumentType('"validator" option', $this->options['validator']); } - if (isset($options['viewOn']) && ! is_string($options['viewOn'])) { - throw InvalidArgumentException::invalidType('"viewOn" option', $options['viewOn'], 'string'); + if (isset($this->options['viewOn']) && ! is_string($this->options['viewOn'])) { + throw InvalidArgumentException::invalidType('"viewOn" option', $this->options['viewOn'], 'string'); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } - if (isset($options['autoIndexId'])) { + if (isset($this->options['autoIndexId'])) { trigger_error('The "autoIndexId" option is deprecated and will be removed in a future release', E_USER_DEPRECATED); } - if (isset($options['pipeline']) && ! is_pipeline($options['pipeline'], true /* allowEmpty */)) { + if (isset($this->options['pipeline']) && ! is_pipeline($this->options['pipeline'], true /* allowEmpty */)) { throw new InvalidArgumentException('"pipeline" option is not a valid aggregation pipeline'); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->options = $options; } /** diff --git a/src/Operation/CreateEncryptedCollection.php b/src/Operation/CreateEncryptedCollection.php index 113b83171..a5cde6514 100644 --- a/src/Operation/CreateEncryptedCollection.php +++ b/src/Operation/CreateEncryptedCollection.php @@ -59,12 +59,6 @@ class CreateEncryptedCollection implements Executable private CreateIndexes $createSafeContentIndex; - private string $databaseName; - - private string $collectionName; - - private array $options; - /** * @see CreateCollection::__construct() for supported options * @param string $databaseName Database name @@ -72,20 +66,20 @@ class CreateEncryptedCollection implements Executable * @param array $options CreateCollection options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $options) + public function __construct(private string $databaseName, private string $collectionName, private array $options) { - if (! isset($options['encryptedFields'])) { + if (! isset($this->options['encryptedFields'])) { throw new InvalidArgumentException('"encryptedFields" option is required'); } - if (! is_document($options['encryptedFields'])) { - throw InvalidArgumentException::expectedDocumentType('"encryptedFields" option', $options['encryptedFields']); + if (! is_document($this->options['encryptedFields'])) { + throw InvalidArgumentException::expectedDocumentType('"encryptedFields" option', $this->options['encryptedFields']); } - $this->createCollection = new CreateCollection($databaseName, $collectionName, $options); + $this->createCollection = new CreateCollection($databaseName, $collectionName, $this->options); /** @psalm-var array{ecocCollection?: ?string, escCollection?: ?string} */ - $encryptedFields = document_to_array($options['encryptedFields']); + $encryptedFields = document_to_array($this->options['encryptedFields']); $enxcolOptions = ['clusteredIndex' => ['key' => ['_id' => 1], 'unique' => true]]; $this->createMetadataCollections = [ @@ -94,10 +88,6 @@ public function __construct(string $databaseName, string $collectionName, array ]; $this->createSafeContentIndex = new CreateIndexes($databaseName, $collectionName, [['key' => ['__safeContent__' => 1]]]); - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->options = $options; } /** diff --git a/src/Operation/CreateIndexes.php b/src/Operation/CreateIndexes.php index e5fc9b73c..396cc8163 100644 --- a/src/Operation/CreateIndexes.php +++ b/src/Operation/CreateIndexes.php @@ -45,15 +45,9 @@ class CreateIndexes implements Executable { private const WIRE_VERSION_FOR_COMMIT_QUORUM = 9; - private string $databaseName; - - private string $collectionName; - /** @var list */ private array $indexes = []; - private array $options = []; - /** * Constructs a createIndexes command. * @@ -80,7 +74,7 @@ class CreateIndexes implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $indexes, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, array $indexes, private array $options = []) { if (empty($indexes)) { throw new InvalidArgumentException('$indexes is empty'); @@ -98,29 +92,25 @@ public function __construct(string $databaseName, string $collectionName, array $this->indexes[] = new IndexInput($index); } - if (isset($options['commitQuorum']) && ! is_string($options['commitQuorum']) && ! is_integer($options['commitQuorum'])) { - throw InvalidArgumentException::invalidType('"commitQuorum" option', $options['commitQuorum'], ['integer', 'string']); + if (isset($this->options['commitQuorum']) && ! is_string($this->options['commitQuorum']) && ! is_integer($this->options['commitQuorum'])) { + throw InvalidArgumentException::invalidType('"commitQuorum" option', $this->options['commitQuorum'], ['integer', 'string']); } - if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->options = $options; } /** diff --git a/src/Operation/CreateSearchIndexes.php b/src/Operation/CreateSearchIndexes.php index 96e529b08..fd27b043c 100644 --- a/src/Operation/CreateSearchIndexes.php +++ b/src/Operation/CreateSearchIndexes.php @@ -39,10 +39,7 @@ */ class CreateSearchIndexes implements Executable { - private string $databaseName; - private string $collectionName; private array $indexes = []; - private array $options; /** * Constructs a createSearchIndexes command. @@ -53,7 +50,7 @@ class CreateSearchIndexes implements Executable * @param array{comment?: mixed} $options Command options * @throws InvalidArgumentException for parameter parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $indexes, array $options) + public function __construct(private string $databaseName, private string $collectionName, array $indexes, private array $options) { if (! array_is_list($indexes)) { throw new InvalidArgumentException('$indexes is not a list'); @@ -66,10 +63,6 @@ public function __construct(string $databaseName, string $collectionName, array $this->indexes[] = new SearchIndexInput($index); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->options = $options; } /** diff --git a/src/Operation/DatabaseCommand.php b/src/Operation/DatabaseCommand.php index adcccca37..4ae8214f1 100644 --- a/src/Operation/DatabaseCommand.php +++ b/src/Operation/DatabaseCommand.php @@ -34,12 +34,8 @@ */ class DatabaseCommand implements Executable { - private string $databaseName; - private Command $command; - private array $options; - /** * Constructs a command. * @@ -61,27 +57,25 @@ class DatabaseCommand implements Executable * @param array $options Options for command execution * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, $command, array $options = []) + public function __construct(private string $databaseName, array|object $command, private array $options = []) { if (! is_document($command)) { throw InvalidArgumentException::expectedDocumentType('$command', $command); } - if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); + if (isset($this->options['readPreference']) && ! $this->options['readPreference'] instanceof ReadPreference) { + throw InvalidArgumentException::invalidType('"readPreference" option', $this->options['readPreference'], ReadPreference::class); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - $this->databaseName = $databaseName; $this->command = $command instanceof Command ? $command : new Command($command); - $this->options = $options; } /** diff --git a/src/Operation/Delete.php b/src/Operation/Delete.php index b23e1eac9..477cade91 100644 --- a/src/Operation/Delete.php +++ b/src/Operation/Delete.php @@ -46,17 +46,6 @@ class Delete implements Executable, Explainable { private const WIRE_VERSION_FOR_HINT = 9; - private string $databaseName; - - private string $collectionName; - - /** @var array|object */ - private $filter; - - private int $limit; - - private array $options; - /** * Constructs a delete command. * @@ -93,7 +82,7 @@ class Delete implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, int $limit, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private array|object $filter, private int $limit, private array $options = []) { if (! is_document($filter)) { throw InvalidArgumentException::expectedDocumentType('$filter', $filter); @@ -103,35 +92,29 @@ public function __construct(string $databaseName, string $collectionName, $filte throw new InvalidArgumentException('$limit must be 0 or 1'); } - if (isset($options['collation']) && ! is_document($options['collation'])) { - throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + if (isset($this->options['collation']) && ! is_document($this->options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $this->options['collation']); } - if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) { - throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], ['string', 'array', 'object']); + if (isset($this->options['hint']) && ! is_string($this->options['hint']) && ! is_array($this->options['hint']) && ! is_object($this->options['hint'])) { + throw InvalidArgumentException::invalidType('"hint" option', $this->options['hint'], ['string', 'array', 'object']); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['let']) && ! is_document($options['let'])) { - throw InvalidArgumentException::expectedDocumentType('"let" option', $options['let']); + if (isset($this->options['let']) && ! is_document($this->options['let'])) { + throw InvalidArgumentException::expectedDocumentType('"let" option', $this->options['let']); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->filter = $filter; - $this->limit = $limit; - $this->options = $options; } /** diff --git a/src/Operation/DeleteMany.php b/src/Operation/DeleteMany.php index 9f580133e..20ca38d1d 100644 --- a/src/Operation/DeleteMany.php +++ b/src/Operation/DeleteMany.php @@ -66,7 +66,7 @@ class DeleteMany implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, array $options = []) + public function __construct(string $databaseName, string $collectionName, array|object $filter, array $options = []) { $this->delete = new Delete($databaseName, $collectionName, $filter, 0, $options); } diff --git a/src/Operation/DeleteOne.php b/src/Operation/DeleteOne.php index 3e2c3e089..1aaad5d27 100644 --- a/src/Operation/DeleteOne.php +++ b/src/Operation/DeleteOne.php @@ -66,7 +66,7 @@ class DeleteOne implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, array $options = []) + public function __construct(string $databaseName, string $collectionName, array|object $filter, array $options = []) { $this->delete = new Delete($databaseName, $collectionName, $filter, 1, $options); } diff --git a/src/Operation/Distinct.php b/src/Operation/Distinct.php index 6397c300e..1483012f3 100644 --- a/src/Operation/Distinct.php +++ b/src/Operation/Distinct.php @@ -42,17 +42,6 @@ */ class Distinct implements Executable, Explainable { - private string $databaseName; - - private string $collectionName; - - private string $fieldName; - - /** @var array|object */ - private $filter; - - private array $options; - /** * Constructs a distinct command. * @@ -82,45 +71,39 @@ class Distinct implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, string $fieldName, $filter = [], array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private string $fieldName, private array|object $filter = [], private array $options = []) { if (! is_document($filter)) { throw InvalidArgumentException::expectedDocumentType('$filter', $filter); } - if (isset($options['collation']) && ! is_document($options['collation'])) { - throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + if (isset($this->options['collation']) && ! is_document($this->options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $this->options['collation']); } - if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); } - if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); + if (isset($this->options['readConcern']) && ! $this->options['readConcern'] instanceof ReadConcern) { + throw InvalidArgumentException::invalidType('"readConcern" option', $this->options['readConcern'], ReadConcern::class); } - if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); + if (isset($this->options['readPreference']) && ! $this->options['readPreference'] instanceof ReadPreference) { + throw InvalidArgumentException::invalidType('"readPreference" option', $this->options['readPreference'], ReadPreference::class); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['readConcern']) && $options['readConcern']->isDefault()) { - unset($options['readConcern']); + if (isset($this->options['readConcern']) && $this->options['readConcern']->isDefault()) { + unset($this->options['readConcern']); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->fieldName = $fieldName; - $this->filter = $filter; - $this->options = $options; } /** diff --git a/src/Operation/DropCollection.php b/src/Operation/DropCollection.php index d24f9fd88..a467a7391 100644 --- a/src/Operation/DropCollection.php +++ b/src/Operation/DropCollection.php @@ -40,12 +40,6 @@ class DropCollection implements Executable { private const ERROR_CODE_NAMESPACE_NOT_FOUND = 26; - private string $databaseName; - - private string $collectionName; - - private array $options; - /** * Constructs a drop command. * @@ -67,27 +61,23 @@ class DropCollection implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private array $options = []) { - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->options = $options; } /** diff --git a/src/Operation/DropDatabase.php b/src/Operation/DropDatabase.php index 76a3d772f..15cb0c34e 100644 --- a/src/Operation/DropDatabase.php +++ b/src/Operation/DropDatabase.php @@ -36,10 +36,6 @@ */ class DropDatabase implements Executable { - private string $databaseName; - - private array $options; - /** * Constructs a dropDatabase command. * @@ -60,26 +56,23 @@ class DropDatabase implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, array $options = []) + public function __construct(private string $databaseName, private array $options = []) { - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } - - $this->databaseName = $databaseName; - $this->options = $options; } /** diff --git a/src/Operation/DropIndexes.php b/src/Operation/DropIndexes.php index 0bbd08247..c5302e250 100644 --- a/src/Operation/DropIndexes.php +++ b/src/Operation/DropIndexes.php @@ -37,14 +37,6 @@ */ class DropIndexes implements Executable { - private string $databaseName; - - private string $collectionName; - - private string $indexName; - - private array $options; - /** * Constructs a dropIndexes command. * @@ -70,36 +62,31 @@ class DropIndexes implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, string $indexName, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private string $indexName, private array $options = []) { if ($indexName === '') { throw new InvalidArgumentException('$indexName cannot be empty'); } - if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->indexName = $indexName; - $this->options = $options; } /** diff --git a/src/Operation/DropSearchIndex.php b/src/Operation/DropSearchIndex.php index a8a6ff1b7..42fc58baa 100644 --- a/src/Operation/DropSearchIndex.php +++ b/src/Operation/DropSearchIndex.php @@ -34,11 +34,6 @@ class DropSearchIndex implements Executable { private const ERROR_CODE_NAMESPACE_NOT_FOUND = 26; - private string $databaseName; - private string $collectionName; - private string $name; - private array $options; - /** * Constructs a dropSearchIndex command. * @@ -48,16 +43,11 @@ class DropSearchIndex implements Executable * @param array{comment?: mixed} $options Command options * @throws InvalidArgumentException for parameter parsing errors */ - public function __construct(string $databaseName, string $collectionName, string $name, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private string $name, private array $options = []) { if ($name === '') { throw new InvalidArgumentException('Index name cannot be empty'); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->name = $name; - $this->options = $options; } /** diff --git a/src/Operation/EstimatedDocumentCount.php b/src/Operation/EstimatedDocumentCount.php index 718a0142c..e3c240c78 100644 --- a/src/Operation/EstimatedDocumentCount.php +++ b/src/Operation/EstimatedDocumentCount.php @@ -37,10 +37,6 @@ */ class EstimatedDocumentCount implements Executable, Explainable { - private string $databaseName; - - private string $collectionName; - private array $options; /** @@ -67,11 +63,8 @@ class EstimatedDocumentCount implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, array $options = []) { - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); } diff --git a/src/Operation/Explain.php b/src/Operation/Explain.php index e90278a73..1c132b2f5 100644 --- a/src/Operation/Explain.php +++ b/src/Operation/Explain.php @@ -41,12 +41,6 @@ class Explain implements Executable public const VERBOSITY_EXEC_STATS = 'executionStats'; public const VERBOSITY_QUERY = 'queryPlanner'; - private string $databaseName; - - private Explainable $explainable; - - private array $options; - /** * Constructs an explain command for explainable operations. * @@ -70,27 +64,23 @@ class Explain implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, Explainable $explainable, array $options = []) + public function __construct(private string $databaseName, private Explainable $explainable, private array $options = []) { - if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); + if (isset($this->options['readPreference']) && ! $this->options['readPreference'] instanceof ReadPreference) { + throw InvalidArgumentException::invalidType('"readPreference" option', $this->options['readPreference'], ReadPreference::class); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['verbosity']) && ! is_string($options['verbosity'])) { - throw InvalidArgumentException::invalidType('"verbosity" option', $options['verbosity'], 'string'); + if (isset($this->options['verbosity']) && ! is_string($this->options['verbosity'])) { + throw InvalidArgumentException::invalidType('"verbosity" option', $this->options['verbosity'], 'string'); } - - $this->databaseName = $databaseName; - $this->explainable = $explainable; - $this->options = $options; } /** diff --git a/src/Operation/Find.php b/src/Operation/Find.php index 23b33d482..13944a682 100644 --- a/src/Operation/Find.php +++ b/src/Operation/Find.php @@ -54,15 +54,6 @@ class Find implements Executable, Explainable public const TAILABLE = 2; public const TAILABLE_AWAIT = 3; - private string $databaseName; - - private string $collectionName; - - /** @var array|object */ - private $filter; - - private array $options; - /** * Constructs a find command. * @@ -164,150 +155,145 @@ class Find implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private array|object $filter, private array $options = []) { if (! is_document($filter)) { throw InvalidArgumentException::expectedDocumentType('$filter', $filter); } - if (isset($options['allowDiskUse']) && ! is_bool($options['allowDiskUse'])) { - throw InvalidArgumentException::invalidType('"allowDiskUse" option', $options['allowDiskUse'], 'boolean'); + if (isset($this->options['allowDiskUse']) && ! is_bool($this->options['allowDiskUse'])) { + throw InvalidArgumentException::invalidType('"allowDiskUse" option', $this->options['allowDiskUse'], 'boolean'); } - if (isset($options['allowPartialResults']) && ! is_bool($options['allowPartialResults'])) { - throw InvalidArgumentException::invalidType('"allowPartialResults" option', $options['allowPartialResults'], 'boolean'); + if (isset($this->options['allowPartialResults']) && ! is_bool($this->options['allowPartialResults'])) { + throw InvalidArgumentException::invalidType('"allowPartialResults" option', $this->options['allowPartialResults'], 'boolean'); } - if (isset($options['batchSize']) && ! is_integer($options['batchSize'])) { - throw InvalidArgumentException::invalidType('"batchSize" option', $options['batchSize'], 'integer'); + if (isset($this->options['batchSize']) && ! is_integer($this->options['batchSize'])) { + throw InvalidArgumentException::invalidType('"batchSize" option', $this->options['batchSize'], 'integer'); } - if (isset($options['codec']) && ! $options['codec'] instanceof DocumentCodec) { - throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class); + if (isset($this->options['codec']) && ! $this->options['codec'] instanceof DocumentCodec) { + throw InvalidArgumentException::invalidType('"codec" option', $this->options['codec'], DocumentCodec::class); } - if (isset($options['collation']) && ! is_document($options['collation'])) { - throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + if (isset($this->options['collation']) && ! is_document($this->options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $this->options['collation']); } - if (isset($options['cursorType'])) { - if (! is_integer($options['cursorType'])) { - throw InvalidArgumentException::invalidType('"cursorType" option', $options['cursorType'], 'integer'); + if (isset($this->options['cursorType'])) { + if (! is_integer($this->options['cursorType'])) { + throw InvalidArgumentException::invalidType('"cursorType" option', $this->options['cursorType'], 'integer'); } if ( - $options['cursorType'] !== self::NON_TAILABLE && - $options['cursorType'] !== self::TAILABLE && - $options['cursorType'] !== self::TAILABLE_AWAIT + $this->options['cursorType'] !== self::NON_TAILABLE && + $this->options['cursorType'] !== self::TAILABLE && + $this->options['cursorType'] !== self::TAILABLE_AWAIT ) { - throw new InvalidArgumentException('Invalid value for "cursorType" option: ' . $options['cursorType']); + throw new InvalidArgumentException('Invalid value for "cursorType" option: ' . $this->options['cursorType']); } } - if (isset($options['hint']) && ! is_string($options['hint']) && ! is_array($options['hint']) && ! is_object($options['hint'])) { - throw InvalidArgumentException::invalidType('"hint" option', $options['hint'], 'string or array or object'); + if (isset($this->options['hint']) && ! is_string($this->options['hint']) && ! is_array($this->options['hint']) && ! is_object($this->options['hint'])) { + throw InvalidArgumentException::invalidType('"hint" option', $this->options['hint'], 'string or array or object'); } - if (isset($options['limit']) && ! is_integer($options['limit'])) { - throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer'); + if (isset($this->options['limit']) && ! is_integer($this->options['limit'])) { + throw InvalidArgumentException::invalidType('"limit" option', $this->options['limit'], 'integer'); } - if (isset($options['max']) && ! is_document($options['max'])) { - throw InvalidArgumentException::expectedDocumentType('"max" option', $options['max']); + if (isset($this->options['max']) && ! is_document($this->options['max'])) { + throw InvalidArgumentException::expectedDocumentType('"max" option', $this->options['max']); } - if (isset($options['maxAwaitTimeMS']) && ! is_integer($options['maxAwaitTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxAwaitTimeMS" option', $options['maxAwaitTimeMS'], 'integer'); + if (isset($this->options['maxAwaitTimeMS']) && ! is_integer($this->options['maxAwaitTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxAwaitTimeMS" option', $this->options['maxAwaitTimeMS'], 'integer'); } - if (isset($options['maxScan']) && ! is_integer($options['maxScan'])) { - throw InvalidArgumentException::invalidType('"maxScan" option', $options['maxScan'], 'integer'); + if (isset($this->options['maxScan']) && ! is_integer($this->options['maxScan'])) { + throw InvalidArgumentException::invalidType('"maxScan" option', $this->options['maxScan'], 'integer'); } - if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); } - if (isset($options['min']) && ! is_document($options['min'])) { - throw InvalidArgumentException::expectedDocumentType('"min" option', $options['min']); + if (isset($this->options['min']) && ! is_document($this->options['min'])) { + throw InvalidArgumentException::expectedDocumentType('"min" option', $this->options['min']); } - if (isset($options['modifiers']) && ! is_document($options['modifiers'])) { - throw InvalidArgumentException::expectedDocumentType('"modifiers" option', $options['modifiers']); + if (isset($this->options['modifiers']) && ! is_document($this->options['modifiers'])) { + throw InvalidArgumentException::expectedDocumentType('"modifiers" option', $this->options['modifiers']); } - if (isset($options['noCursorTimeout']) && ! is_bool($options['noCursorTimeout'])) { - throw InvalidArgumentException::invalidType('"noCursorTimeout" option', $options['noCursorTimeout'], 'boolean'); + if (isset($this->options['noCursorTimeout']) && ! is_bool($this->options['noCursorTimeout'])) { + throw InvalidArgumentException::invalidType('"noCursorTimeout" option', $this->options['noCursorTimeout'], 'boolean'); } - if (isset($options['oplogReplay']) && ! is_bool($options['oplogReplay'])) { - throw InvalidArgumentException::invalidType('"oplogReplay" option', $options['oplogReplay'], 'boolean'); + if (isset($this->options['oplogReplay']) && ! is_bool($this->options['oplogReplay'])) { + throw InvalidArgumentException::invalidType('"oplogReplay" option', $this->options['oplogReplay'], 'boolean'); } - if (isset($options['projection']) && ! is_document($options['projection'])) { - throw InvalidArgumentException::expectedDocumentType('"projection" option', $options['projection']); + if (isset($this->options['projection']) && ! is_document($this->options['projection'])) { + throw InvalidArgumentException::expectedDocumentType('"projection" option', $this->options['projection']); } - if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); + if (isset($this->options['readConcern']) && ! $this->options['readConcern'] instanceof ReadConcern) { + throw InvalidArgumentException::invalidType('"readConcern" option', $this->options['readConcern'], ReadConcern::class); } - if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); + if (isset($this->options['readPreference']) && ! $this->options['readPreference'] instanceof ReadPreference) { + throw InvalidArgumentException::invalidType('"readPreference" option', $this->options['readPreference'], ReadPreference::class); } - if (isset($options['returnKey']) && ! is_bool($options['returnKey'])) { - throw InvalidArgumentException::invalidType('"returnKey" option', $options['returnKey'], 'boolean'); + if (isset($this->options['returnKey']) && ! is_bool($this->options['returnKey'])) { + throw InvalidArgumentException::invalidType('"returnKey" option', $this->options['returnKey'], 'boolean'); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['showRecordId']) && ! is_bool($options['showRecordId'])) { - throw InvalidArgumentException::invalidType('"showRecordId" option', $options['showRecordId'], 'boolean'); + if (isset($this->options['showRecordId']) && ! is_bool($this->options['showRecordId'])) { + throw InvalidArgumentException::invalidType('"showRecordId" option', $this->options['showRecordId'], 'boolean'); } - if (isset($options['skip']) && ! is_integer($options['skip'])) { - throw InvalidArgumentException::invalidType('"skip" option', $options['skip'], 'integer'); + if (isset($this->options['skip']) && ! is_integer($this->options['skip'])) { + throw InvalidArgumentException::invalidType('"skip" option', $this->options['skip'], 'integer'); } - if (isset($options['snapshot']) && ! is_bool($options['snapshot'])) { - throw InvalidArgumentException::invalidType('"snapshot" option', $options['snapshot'], 'boolean'); + if (isset($this->options['snapshot']) && ! is_bool($this->options['snapshot'])) { + throw InvalidArgumentException::invalidType('"snapshot" option', $this->options['snapshot'], 'boolean'); } - if (isset($options['sort']) && ! is_document($options['sort'])) { - throw InvalidArgumentException::expectedDocumentType('"sort" option', $options['sort']); + if (isset($this->options['sort']) && ! is_document($this->options['sort'])) { + throw InvalidArgumentException::expectedDocumentType('"sort" option', $this->options['sort']); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['let']) && ! is_document($options['let'])) { - throw InvalidArgumentException::expectedDocumentType('"let" option', $options['let']); + if (isset($this->options['let']) && ! is_document($this->options['let'])) { + throw InvalidArgumentException::expectedDocumentType('"let" option', $this->options['let']); } - if (isset($options['readConcern']) && $options['readConcern']->isDefault()) { - unset($options['readConcern']); + if (isset($this->options['readConcern']) && $this->options['readConcern']->isDefault()) { + unset($this->options['readConcern']); } - if (isset($options['snapshot'])) { + if (isset($this->options['snapshot'])) { trigger_error('The "snapshot" option is deprecated and will be removed in a future release', E_USER_DEPRECATED); } - if (isset($options['maxScan'])) { + if (isset($this->options['maxScan'])) { trigger_error('The "maxScan" option is deprecated and will be removed in a future release', E_USER_DEPRECATED); } - if (isset($options['codec']) && isset($options['typeMap'])) { + if (isset($this->options['codec']) && isset($this->options['typeMap'])) { throw InvalidArgumentException::cannotCombineCodecAndTypeMap(); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->filter = $filter; - $this->options = $options; } /** diff --git a/src/Operation/FindAndModify.php b/src/Operation/FindAndModify.php index 300f4e97a..e67413b6c 100644 --- a/src/Operation/FindAndModify.php +++ b/src/Operation/FindAndModify.php @@ -57,10 +57,6 @@ class FindAndModify implements Executable, Explainable private const WIRE_VERSION_FOR_UNSUPPORTED_OPTION_SERVER_SIDE_ERROR = 8; - private string $databaseName; - - private string $collectionName; - private array $options; /** @@ -131,7 +127,7 @@ class FindAndModify implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $options) + public function __construct(private string $databaseName, private string $collectionName, array $options) { $options += ['remove' => false]; @@ -219,8 +215,6 @@ public function __construct(string $databaseName, string $collectionName, array throw InvalidArgumentException::cannotCombineCodecAndTypeMap(); } - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; $this->options = $options; } diff --git a/src/Operation/FindOne.php b/src/Operation/FindOne.php index 48f931fab..6fdff0a29 100644 --- a/src/Operation/FindOne.php +++ b/src/Operation/FindOne.php @@ -104,7 +104,7 @@ class FindOne implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, array $options = []) + public function __construct(string $databaseName, string $collectionName, array|object $filter, array $options = []) { $this->find = new Find( $databaseName, diff --git a/src/Operation/FindOneAndDelete.php b/src/Operation/FindOneAndDelete.php index cae2a1788..ae5148872 100644 --- a/src/Operation/FindOneAndDelete.php +++ b/src/Operation/FindOneAndDelete.php @@ -81,7 +81,7 @@ class FindOneAndDelete implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, array $options = []) + public function __construct(string $databaseName, string $collectionName, array|object $filter, array $options = []) { if (! is_document($filter)) { throw InvalidArgumentException::expectedDocumentType('$filter', $filter); diff --git a/src/Operation/FindOneAndReplace.php b/src/Operation/FindOneAndReplace.php index 20412d645..6f4409406 100644 --- a/src/Operation/FindOneAndReplace.php +++ b/src/Operation/FindOneAndReplace.php @@ -102,7 +102,7 @@ class FindOneAndReplace implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, $replacement, array $options = []) + public function __construct(string $databaseName, string $collectionName, array|object $filter, array|object $replacement, array $options = []) { if (! is_document($filter)) { throw InvalidArgumentException::expectedDocumentType('$filter', $filter); @@ -171,11 +171,8 @@ public function getCommandDocument() return $this->findAndModify->getCommandDocument(); } - /** - * @param array|object $replacement - * @return array|object - */ - private function validateReplacement($replacement, ?DocumentCodec $codec) + /** @return array|object */ + private function validateReplacement(array|object $replacement, ?DocumentCodec $codec) { if (isset($codec)) { $replacement = $codec->encode($replacement); diff --git a/src/Operation/FindOneAndUpdate.php b/src/Operation/FindOneAndUpdate.php index 783888b1f..0fbe127d0 100644 --- a/src/Operation/FindOneAndUpdate.php +++ b/src/Operation/FindOneAndUpdate.php @@ -23,9 +23,7 @@ use MongoDB\Exception\UnsupportedException; use function array_key_exists; -use function is_array; use function is_integer; -use function is_object; use function MongoDB\is_document; use function MongoDB\is_first_key_operator; use function MongoDB\is_pipeline; @@ -106,16 +104,12 @@ class FindOneAndUpdate implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, $update, array $options = []) + public function __construct(string $databaseName, string $collectionName, array|object $filter, array|object $update, array $options = []) { if (! is_document($filter)) { throw InvalidArgumentException::expectedDocumentType('$filter', $filter); } - if (! is_array($update) && ! is_object($update)) { - throw InvalidArgumentException::invalidType('$update', $update, 'array or object'); - } - if (! is_first_key_operator($update) && ! is_pipeline($update)) { throw new InvalidArgumentException('Expected update operator(s) or non-empty pipeline for $update'); } diff --git a/src/Operation/InsertMany.php b/src/Operation/InsertMany.php index 42b3a344f..01640da2f 100644 --- a/src/Operation/InsertMany.php +++ b/src/Operation/InsertMany.php @@ -40,10 +40,6 @@ */ class InsertMany implements Executable { - private string $databaseName; - - private string $collectionName; - /** @var list */ private array $documents; @@ -79,7 +75,7 @@ class InsertMany implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $documents, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, array $documents, array $options = []) { $options += ['ordered' => true]; @@ -111,8 +107,6 @@ public function __construct(string $databaseName, string $collectionName, array unset($options['writeConcern']); } - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; $this->documents = $this->validateDocuments($documents, $options['codec'] ?? null); $this->options = $options; } diff --git a/src/Operation/InsertOne.php b/src/Operation/InsertOne.php index f745664d7..8626364f8 100644 --- a/src/Operation/InsertOne.php +++ b/src/Operation/InsertOne.php @@ -38,14 +38,7 @@ */ class InsertOne implements Executable { - private string $databaseName; - - private string $collectionName; - - /** @var array|object */ - private $document; - - private array $options; + private array|object $document; /** * Constructs an insert command. @@ -72,36 +65,33 @@ class InsertOne implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $document, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, array|object $document, private array $options = []) { - if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { - throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); + if (isset($this->options['bypassDocumentValidation']) && ! is_bool($this->options['bypassDocumentValidation'])) { + throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $this->options['bypassDocumentValidation'], 'boolean'); } - if (isset($options['codec']) && ! $options['codec'] instanceof DocumentCodec) { - throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class); + if (isset($this->options['codec']) && ! $this->options['codec'] instanceof DocumentCodec) { + throw InvalidArgumentException::invalidType('"codec" option', $this->options['codec'], DocumentCodec::class); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['bypassDocumentValidation']) && ! $options['bypassDocumentValidation']) { - unset($options['bypassDocumentValidation']); + if (isset($this->options['bypassDocumentValidation']) && ! $this->options['bypassDocumentValidation']) { + unset($this->options['bypassDocumentValidation']); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->document = $this->validateDocument($document, $options['codec'] ?? null); - $this->options = $options; + $this->document = $this->validateDocument($document, $this->options['codec'] ?? null); } /** @@ -166,11 +156,8 @@ private function createExecuteOptions(): array return $options; } - /** - * @param array|object $document - * @return array|object - */ - private function validateDocument($document, ?DocumentCodec $codec) + /** @return array|object */ + private function validateDocument(array|object $document, ?DocumentCodec $codec) { if ($codec) { $document = $codec->encode($document); diff --git a/src/Operation/ListCollections.php b/src/Operation/ListCollections.php index 36d0bc446..340c11017 100644 --- a/src/Operation/ListCollections.php +++ b/src/Operation/ListCollections.php @@ -32,8 +32,6 @@ */ class ListCollections implements Executable { - private string $databaseName; - private ListCollectionsCommand $listCollections; /** @@ -61,9 +59,8 @@ class ListCollections implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, array $options = []) + public function __construct(private string $databaseName, array $options = []) { - $this->databaseName = $databaseName; $this->listCollections = new ListCollectionsCommand($databaseName, ['nameOnly' => false] + $options); } diff --git a/src/Operation/ListIndexes.php b/src/Operation/ListIndexes.php index 1a5723d70..384a5f498 100644 --- a/src/Operation/ListIndexes.php +++ b/src/Operation/ListIndexes.php @@ -41,12 +41,6 @@ class ListIndexes implements Executable private const ERROR_CODE_DATABASE_NOT_FOUND = 60; private const ERROR_CODE_NAMESPACE_NOT_FOUND = 26; - private string $databaseName; - - private string $collectionName; - - private array $options; - /** * Constructs a listIndexes command. * @@ -66,19 +60,15 @@ class ListIndexes implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private array $options = []) { - if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->options = $options; } /** diff --git a/src/Operation/ListSearchIndexes.php b/src/Operation/ListSearchIndexes.php index 8cf7f1e56..2dbb3688b 100644 --- a/src/Operation/ListSearchIndexes.php +++ b/src/Operation/ListSearchIndexes.php @@ -37,8 +37,6 @@ */ class ListSearchIndexes implements Executable { - private string $databaseName; - private string $collectionName; private array $listSearchIndexesOptions; private array $aggregateOptions; private Aggregate $aggregate; @@ -50,7 +48,7 @@ class ListSearchIndexes implements Executable * @param string $collectionName Collection name * @param array{name?: string} $options Command options */ - public function __construct(string $databaseName, string $collectionName, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, array $options = []) { if (isset($options['name']) && ! is_string($options['name'])) { throw InvalidArgumentException::invalidType('"name" option', $options['name'], 'string'); @@ -60,8 +58,6 @@ public function __construct(string $databaseName, string $collectionName, array throw new InvalidArgumentException('"name" option cannot be empty'); } - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; $this->listSearchIndexesOptions = array_intersect_key($options, ['name' => 1]); $this->aggregateOptions = array_intersect_key($options, ['batchSize' => 1, 'codec' => 1, 'collation' => 1, 'comment' => 1, 'maxTimeMS' => 1, 'readConcern' => 1, 'readPreference' => 1, 'session' => 1, 'typeMap' => 1]); diff --git a/src/Operation/MapReduce.php b/src/Operation/MapReduce.php index 211b20176..faf91019c 100644 --- a/src/Operation/MapReduce.php +++ b/src/Operation/MapReduce.php @@ -56,18 +56,7 @@ */ class MapReduce implements Executable { - private string $databaseName; - - private string $collectionName; - - private JavascriptInterface $map; - - private JavascriptInterface $reduce; - - /** @var array|object|string */ - private $out; - - private array $options; + private array|object|string $out; /** * Constructs a mapReduce command. @@ -156,82 +145,78 @@ class MapReduce implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, JavascriptInterface $map, JavascriptInterface $reduce, $out, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private JavascriptInterface $map, private JavascriptInterface $reduce, string|array|object $out, private array $options = []) { - if (! is_string($out) && ! is_array($out) && ! is_object($out)) { - throw InvalidArgumentException::invalidType('$out', $out, 'string or array or object'); - } - - if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { - throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); + if (isset($this->options['bypassDocumentValidation']) && ! is_bool($this->options['bypassDocumentValidation'])) { + throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $this->options['bypassDocumentValidation'], 'boolean'); } - if (isset($options['collation']) && ! is_document($options['collation'])) { - throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + if (isset($this->options['collation']) && ! is_document($this->options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $this->options['collation']); } - if (isset($options['finalize']) && ! $options['finalize'] instanceof JavascriptInterface) { - throw InvalidArgumentException::invalidType('"finalize" option', $options['finalize'], JavascriptInterface::class); + if (isset($this->options['finalize']) && ! $this->options['finalize'] instanceof JavascriptInterface) { + throw InvalidArgumentException::invalidType('"finalize" option', $this->options['finalize'], JavascriptInterface::class); } - if (isset($options['jsMode']) && ! is_bool($options['jsMode'])) { - throw InvalidArgumentException::invalidType('"jsMode" option', $options['jsMode'], 'boolean'); + if (isset($this->options['jsMode']) && ! is_bool($this->options['jsMode'])) { + throw InvalidArgumentException::invalidType('"jsMode" option', $this->options['jsMode'], 'boolean'); } - if (isset($options['limit']) && ! is_integer($options['limit'])) { - throw InvalidArgumentException::invalidType('"limit" option', $options['limit'], 'integer'); + if (isset($this->options['limit']) && ! is_integer($this->options['limit'])) { + throw InvalidArgumentException::invalidType('"limit" option', $this->options['limit'], 'integer'); } - if (isset($options['maxTimeMS']) && ! is_integer($options['maxTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxTimeMS" option', $options['maxTimeMS'], 'integer'); + if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { + throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); } - if (isset($options['query']) && ! is_document($options['query'])) { - throw InvalidArgumentException::expectedDocumentType('"query" option', $options['query']); + if (isset($this->options['query']) && ! is_document($this->options['query'])) { + throw InvalidArgumentException::expectedDocumentType('"query" option', $this->options['query']); } - if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); + if (isset($this->options['readConcern']) && ! $this->options['readConcern'] instanceof ReadConcern) { + throw InvalidArgumentException::invalidType('"readConcern" option', $this->options['readConcern'], ReadConcern::class); } - if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $options['readPreference'], ReadPreference::class); + if (isset($this->options['readPreference']) && ! $this->options['readPreference'] instanceof ReadPreference) { + throw InvalidArgumentException::invalidType('"readPreference" option', $this->options['readPreference'], ReadPreference::class); } - if (isset($options['scope']) && ! is_document($options['scope'])) { - throw InvalidArgumentException::expectedDocumentType('"scope" option', $options['scope']); + if (isset($this->options['scope']) && ! is_document($this->options['scope'])) { + throw InvalidArgumentException::expectedDocumentType('"scope" option', $this->options['scope']); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['sort']) && ! is_document($options['sort'])) { - throw InvalidArgumentException::expectedDocumentType('"sort" option', $options['sort']); + if (isset($this->options['sort']) && ! is_document($this->options['sort'])) { + throw InvalidArgumentException::expectedDocumentType('"sort" option', $this->options['sort']); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['verbose']) && ! is_bool($options['verbose'])) { - throw InvalidArgumentException::invalidType('"verbose" option', $options['verbose'], 'boolean'); + if (isset($this->options['verbose']) && ! is_bool($this->options['verbose'])) { + throw InvalidArgumentException::invalidType('"verbose" option', $this->options['verbose'], 'boolean'); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['bypassDocumentValidation']) && ! $options['bypassDocumentValidation']) { - unset($options['bypassDocumentValidation']); + if (isset($this->options['bypassDocumentValidation']) && ! $this->options['bypassDocumentValidation']) { + unset($this->options['bypassDocumentValidation']); } - if (isset($options['readConcern']) && $options['readConcern']->isDefault()) { - unset($options['readConcern']); + if (isset($this->options['readConcern']) && $this->options['readConcern']->isDefault()) { + unset($this->options['readConcern']); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } // Handle deprecation of CodeWScope @@ -243,18 +228,13 @@ public function __construct(string $databaseName, string $collectionName, Javasc @trigger_error('Use of Javascript with scope in "$reduce" argument for MapReduce is deprecated. Put all scope variables in the "scope" option of the MapReduce operation.', E_USER_DEPRECATED); } - if (isset($options['finalize']) && $options['finalize']->getScope() !== null) { + if (isset($this->options['finalize']) && $this->options['finalize']->getScope() !== null) { @trigger_error('Use of Javascript with scope in "finalize" option for MapReduce is deprecated. Put all scope variables in the "scope" option of the MapReduce operation.', E_USER_DEPRECATED); } $this->checkOutDeprecations($out); - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->map = $map; - $this->reduce = $reduce; $this->out = $out; - $this->options = $options; } /** @@ -306,8 +286,7 @@ public function execute(Server $server) return new MapReduceResult($getIterator, $result); } - /** @param string|array|object $out */ - private function checkOutDeprecations($out): void + private function checkOutDeprecations(string|array|object $out): void { if (is_string($out)) { return; diff --git a/src/Operation/ModifyCollection.php b/src/Operation/ModifyCollection.php index a10bce5e8..53eaa9e09 100644 --- a/src/Operation/ModifyCollection.php +++ b/src/Operation/ModifyCollection.php @@ -35,14 +35,6 @@ */ class ModifyCollection implements Executable { - private string $databaseName; - - private string $collectionName; - - private array $collectionOptions; - - private array $options; - /** * Constructs a collMod command. * @@ -65,32 +57,27 @@ class ModifyCollection implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, array $collectionOptions, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private array $collectionOptions, private array $options = []) { if (empty($collectionOptions)) { throw new InvalidArgumentException('$collectionOptions is empty'); } - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } - - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->collectionOptions = $collectionOptions; - $this->options = $options; } /** diff --git a/src/Operation/RenameCollection.php b/src/Operation/RenameCollection.php index 5be7d4dff..5b69cce41 100644 --- a/src/Operation/RenameCollection.php +++ b/src/Operation/RenameCollection.php @@ -42,8 +42,6 @@ class RenameCollection implements Executable private string $toNamespace; - private array $options; - /** * Constructs a renameCollection command. * @@ -70,31 +68,30 @@ class RenameCollection implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $fromDatabaseName, string $fromCollectionName, string $toDatabaseName, string $toCollectionName, array $options = []) + public function __construct(string $fromDatabaseName, string $fromCollectionName, string $toDatabaseName, string $toCollectionName, private array $options = []) { - if (isset($options['session']) && ! $options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $options['session'], Session::class); + if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($options['typeMap']) && ! is_array($options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $options['typeMap'], 'array'); + if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { + throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); } - if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } - if (isset($options['writeConcern']) && $options['writeConcern']->isDefault()) { - unset($options['writeConcern']); + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); } - if (isset($options['dropTarget']) && ! is_bool($options['dropTarget'])) { - throw InvalidArgumentException::invalidType('"dropTarget" option', $options['dropTarget'], 'boolean'); + if (isset($this->options['dropTarget']) && ! is_bool($this->options['dropTarget'])) { + throw InvalidArgumentException::invalidType('"dropTarget" option', $this->options['dropTarget'], 'boolean'); } $this->fromNamespace = $fromDatabaseName . '.' . $fromCollectionName; $this->toNamespace = $toDatabaseName . '.' . $toCollectionName; - $this->options = $options; } /** diff --git a/src/Operation/ReplaceOne.php b/src/Operation/ReplaceOne.php index 943206761..32bf5a06d 100644 --- a/src/Operation/ReplaceOne.php +++ b/src/Operation/ReplaceOne.php @@ -81,7 +81,7 @@ class ReplaceOne implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, $replacement, array $options = []) + public function __construct(string $databaseName, string $collectionName, array|object $filter, array|object $replacement, array $options = []) { if (isset($options['codec']) && ! $options['codec'] instanceof DocumentCodec) { throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class); @@ -113,11 +113,8 @@ public function execute(Server $server) return $this->update->execute($server); } - /** - * @param array|object $replacement - * @return array|object - */ - private function validateReplacement($replacement, ?DocumentCodec $codec) + /** @return array|object */ + private function validateReplacement(array|object $replacement, ?DocumentCodec $codec) { if ($codec) { $replacement = $codec->encode($replacement); diff --git a/src/Operation/Update.php b/src/Operation/Update.php index a026ec7e4..0b47b1cc1 100644 --- a/src/Operation/Update.php +++ b/src/Operation/Update.php @@ -49,16 +49,6 @@ class Update implements Executable, Explainable { private const WIRE_VERSION_FOR_HINT = 8; - private string $databaseName; - - private string $collectionName; - - /** @var array|object */ - private $filter; - - /** @var array|object */ - private $update; - private array $options; /** @@ -109,16 +99,12 @@ class Update implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, $update, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private array|object $filter, private array|object $update, array $options = []) { if (! is_document($filter)) { throw InvalidArgumentException::expectedDocumentType('$filter', $filter); } - if (! is_array($update) && ! is_object($update)) { - throw InvalidArgumentException::invalidType('$update', $filter, 'array or object'); - } - $options += [ 'multi' => false, 'upsert' => false, @@ -172,10 +158,6 @@ public function __construct(string $databaseName, string $collectionName, $filte unset($options['writeConcern']); } - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->filter = $filter; - $this->update = $update; $this->options = $options; } diff --git a/src/Operation/UpdateMany.php b/src/Operation/UpdateMany.php index d836a7c83..e61731622 100644 --- a/src/Operation/UpdateMany.php +++ b/src/Operation/UpdateMany.php @@ -23,8 +23,6 @@ use MongoDB\Exception\UnsupportedException; use MongoDB\UpdateResult; -use function is_array; -use function is_object; use function MongoDB\is_first_key_operator; use function MongoDB\is_pipeline; @@ -81,12 +79,8 @@ class UpdateMany implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, $update, array $options = []) + public function __construct(string $databaseName, string $collectionName, array|object $filter, array|object $update, array $options = []) { - if (! is_array($update) && ! is_object($update)) { - throw InvalidArgumentException::invalidType('$update', $update, 'array or object'); - } - if (! is_first_key_operator($update) && ! is_pipeline($update)) { throw new InvalidArgumentException('Expected update operator(s) or non-empty pipeline for $update'); } diff --git a/src/Operation/UpdateOne.php b/src/Operation/UpdateOne.php index ff0c53f7a..40d71addf 100644 --- a/src/Operation/UpdateOne.php +++ b/src/Operation/UpdateOne.php @@ -23,8 +23,6 @@ use MongoDB\Exception\UnsupportedException; use MongoDB\UpdateResult; -use function is_array; -use function is_object; use function MongoDB\is_first_key_operator; use function MongoDB\is_pipeline; @@ -81,12 +79,8 @@ class UpdateOne implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(string $databaseName, string $collectionName, $filter, $update, array $options = []) + public function __construct(string $databaseName, string $collectionName, array|object $filter, array|object $update, array $options = []) { - if (! is_array($update) && ! is_object($update)) { - throw InvalidArgumentException::invalidType('$update', $update, 'array or object'); - } - if (! is_first_key_operator($update) && ! is_pipeline($update)) { throw new InvalidArgumentException('Expected update operator(s) or non-empty pipeline for $update'); } diff --git a/src/Operation/UpdateSearchIndex.php b/src/Operation/UpdateSearchIndex.php index a44bc4103..31d86ca8e 100644 --- a/src/Operation/UpdateSearchIndex.php +++ b/src/Operation/UpdateSearchIndex.php @@ -33,11 +33,7 @@ */ class UpdateSearchIndex implements Executable { - private string $databaseName; - private string $collectionName; - private string $name; private object $definition; - private array $options = []; /** * Constructs a createSearchIndexes command. @@ -49,7 +45,7 @@ class UpdateSearchIndex implements Executable * @param array{comment?: mixed} $options Command options * @throws InvalidArgumentException for parameter parsing errors */ - public function __construct(string $databaseName, string $collectionName, string $name, $definition, array $options = []) + public function __construct(private string $databaseName, private string $collectionName, private string $name, array|object $definition, private array $options = []) { if ($name === '') { throw new InvalidArgumentException('Index name cannot be empty'); @@ -59,11 +55,7 @@ public function __construct(string $databaseName, string $collectionName, string throw InvalidArgumentException::expectedDocumentType('$definition', $definition); } - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->name = $name; $this->definition = (object) $definition; - $this->options = $options; } /** diff --git a/src/Operation/Watch.php b/src/Operation/Watch.php index 99e0f0380..1ae781d11 100644 --- a/src/Operation/Watch.php +++ b/src/Operation/Watch.php @@ -74,20 +74,14 @@ class Watch implements Executable, /* @internal */ CommandSubscriber private array $changeStreamOptions; - private ?string $collectionName = null; - private string $databaseName; private int $firstBatchSize = 0; private bool $hasResumed = false; - private Manager $manager; - private ?TimestampInterface $operationTime = null; - private array $pipeline; - private ?object $postBatchResumeToken = null; private ?DocumentCodec $codec; @@ -193,7 +187,7 @@ class Watch implements Executable, /* @internal */ CommandSubscriber * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(Manager $manager, ?string $databaseName, ?string $collectionName, array $pipeline, array $options = []) + public function __construct(private Manager $manager, ?string $databaseName, private ?string $collectionName = null, private array $pipeline, array $options = []) { if (isset($collectionName) && ! isset($databaseName)) { throw new InvalidArgumentException('$collectionName should also be null if $databaseName is null'); @@ -247,7 +241,7 @@ public function __construct(Manager $manager, ?string $databaseName, ?string $co if (! isset($options['session'])) { try { $options['session'] = $manager->startSession(['causalConsistency' => false]); - } catch (RuntimeException $e) { + } catch (RuntimeException) { /* We can ignore the exception, as libmongoc likely cannot * create its own session and there is no risk of a mismatch. */ } @@ -262,10 +256,7 @@ public function __construct(Manager $manager, ?string $databaseName, ?string $co $this->changeStreamOptions['allChangesForCluster'] = true; } - $this->manager = $manager; $this->databaseName = $databaseName; - $this->collectionName = $collectionName; - $this->pipeline = $pipeline; $this->codec = $options['codec'] ?? null; $this->aggregate = $this->createAggregate(); @@ -403,15 +394,10 @@ private function getInitialResumeToken() * Resumes a change stream. * * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resume-process - * @param array|object|null $resumeToken * @throws InvalidArgumentException */ - private function resume($resumeToken = null, bool $hasAdvanced = false): ChangeStreamIterator + private function resume(array|object|null $resumeToken = null, bool $hasAdvanced = false): ChangeStreamIterator { - if (isset($resumeToken) && ! is_array($resumeToken) && ! is_object($resumeToken)) { - throw InvalidArgumentException::invalidType('$resumeToken', $resumeToken, 'array or object'); - } - $this->hasResumed = true; /* Select a new server using the original read preference. While watch diff --git a/src/Operation/WithTransaction.php b/src/Operation/WithTransaction.php index de5644073..f0b5b0ec7 100644 --- a/src/Operation/WithTransaction.php +++ b/src/Operation/WithTransaction.php @@ -16,18 +16,15 @@ class WithTransaction /** @var callable */ private $callback; - private array $transactionOptions; - /** * @see Session::startTransaction for supported transaction options * * @param callable $callback A callback that will be invoked within the transaction * @param array $transactionOptions Additional options that are passed to Session::startTransaction */ - public function __construct(callable $callback, array $transactionOptions = []) + public function __construct(callable $callback, private array $transactionOptions = []) { $this->callback = $callback; - $this->transactionOptions = $transactionOptions; } /** diff --git a/src/UpdateResult.php b/src/UpdateResult.php index 76f042ae1..58a73a890 100644 --- a/src/UpdateResult.php +++ b/src/UpdateResult.php @@ -25,13 +25,10 @@ */ class UpdateResult { - private WriteResult $writeResult; - private bool $isAcknowledged; - public function __construct(WriteResult $writeResult) + public function __construct(private WriteResult $writeResult) { - $this->writeResult = $writeResult; $this->isAcknowledged = $writeResult->isAcknowledged(); } diff --git a/src/functions.php b/src/functions.php index b3b5167e7..bf3a736ec 100644 --- a/src/functions.php +++ b/src/functions.php @@ -107,7 +107,7 @@ function all_servers_support_write_stage_on_secondary(array $servers): bool * @return array|object * @throws InvalidArgumentException */ -function apply_type_map_to_document($document, array $typeMap) +function apply_type_map_to_document(array|object $document, array $typeMap) { if (! is_document($document)) { throw InvalidArgumentException::expectedDocumentType('$document', $document); @@ -127,10 +127,9 @@ function apply_type_map_to_document($document, array $typeMap) * encode as BSON arrays. * * @internal - * @param array|object $document * @throws InvalidArgumentException if $document is not an array or object */ -function document_to_array($document): array +function document_to_array(array|object $document): array { if ($document instanceof Document || $document instanceof PackedArray) { /* Nested documents and arrays are intentionally left as BSON. We avoid @@ -154,10 +153,6 @@ function document_to_array($document): array $document = get_object_vars($document); } - if (! is_array($document)) { - throw InvalidArgumentException::expectedDocumentType('$document', $document); - } - return $document; } @@ -215,9 +210,8 @@ function get_encrypted_fields_from_server(string $databaseName, string $collecti * BSON PackedArray instances * * @internal - * @param mixed $document */ -function is_document($document): bool +function is_document(mixed $document): bool { return is_array($document) || (is_object($document) && ! $document instanceof PackedArray); } @@ -231,10 +225,9 @@ function is_document($document): bool * $document has an unexpected type instead of returning false. * * @internal - * @param array|object $document * @throws InvalidArgumentException if $document is not an array or object */ -function is_first_key_operator($document): bool +function is_first_key_operator(array|object $document): bool { if ($document instanceof PackedArray) { return false; @@ -274,10 +267,9 @@ function is_first_key_operator($document): bool * returns a non-array, non-object value from its bsonSerialize() method. * * @internal - * @param array|object $pipeline * @throws InvalidArgumentException */ -function is_pipeline($pipeline, bool $allowEmpty = false): bool +function is_pipeline(array|object $pipeline, bool $allowEmpty = false): bool { if ($pipeline instanceof PackedArray) { /* Nested documents and arrays are intentionally left as BSON. We avoid @@ -368,7 +360,7 @@ function is_last_pipeline_operator_write(array $pipeline): bool * @see https://mongodb.com/docs/manual/reference/command/mapReduce/#output-inline * @param string|array|object $out Output specification */ -function is_mapreduce_output_inline($out): bool +function is_mapreduce_output_inline(string|array|object $out): bool { if (! is_array($out) && ! is_object($out)) { return false; @@ -414,9 +406,8 @@ function server_supports_feature(Server $server, int $feature): bool * Return whether the input is an array of strings. * * @internal - * @param mixed $input */ -function is_string_array($input): bool +function is_string_array(mixed $input): bool { if (! is_array($input)) { return false; @@ -442,7 +433,7 @@ function is_string_array($input): bool * @return mixed * @throws ReflectionException */ -function recursive_copy($element) +function recursive_copy(mixed $element) { if (is_array($element)) { foreach ($element as $key => $value) { diff --git a/tests/Database/DatabaseFunctionalTest.php b/tests/Database/DatabaseFunctionalTest.php index c5854e882..c5f337bfc 100644 --- a/tests/Database/DatabaseFunctionalTest.php +++ b/tests/Database/DatabaseFunctionalTest.php @@ -2,6 +2,7 @@ namespace MongoDB\Tests\Database; +use MongoDB\BSON\PackedArray; use MongoDB\Collection; use MongoDB\Database; use MongoDB\Driver\BulkWrite; @@ -124,7 +125,7 @@ public function testCommandAppliesTypeMapToCursor(): void /** @dataProvider provideInvalidDocumentValues */ public function testCommandCommandArgumentTypeCheck($command): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($command instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); $this->database->command($command); } diff --git a/tests/DocumentationExamplesTest.php b/tests/DocumentationExamplesTest.php index 8d4062c57..e325b4914 100644 --- a/tests/DocumentationExamplesTest.php +++ b/tests/DocumentationExamplesTest.php @@ -1501,7 +1501,7 @@ private function doUpdateEmployeeInfo(\MongoDB\Client $client): void try { $this->runTransactionWithRetry3([$this, 'updateEmployeeInfo3'], $client, $session); - } catch (\MongoDB\Driver\Exception\Exception $error) { + } catch (\MongoDB\Driver\Exception\Exception) { // Do something with error } } @@ -1547,7 +1547,7 @@ public function testCausalConsistency(): void * around this, we run a query on a secondary and rely on an * exception to let us know that no secondary is available. */ $items->countDocuments([], ['readPreference' => new ReadPreference(ReadPreference::SECONDARY)]); - } catch (Exception $e) { + } catch (Exception) { $this->markTestSkipped('Secondary is not available'); } diff --git a/tests/ExamplesTest.php b/tests/ExamplesTest.php index aa3e8580e..7946622f4 100644 --- a/tests/ExamplesTest.php +++ b/tests/ExamplesTest.php @@ -144,7 +144,6 @@ public static function provideExamples(): Generator [oid] => %s ) - [name] => alcaeus [emails] => Array ( [0] => MongoDB\Examples\Persistable\PersistableEmail Object @@ -161,6 +160,7 @@ public static function provideExamples(): Generator ) + [name] => alcaeus ) OUTPUT; diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index 53d1c3558..cdff7ca48 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -213,7 +213,7 @@ protected function assertSameObjectId($expectedObjectId, $actualObjectId): void * @param array|stdClass $command configureFailPoint command document * @throws InvalidArgumentException if $command is not a configureFailPoint command */ - public function configureFailPoint($command, ?Server $server = null): void + public function configureFailPoint(array|stdClass $command, ?Server $server = null): void { if (! $this->isFailCommandSupported()) { $this->markTestSkipped('failCommand is only supported on mongod >= 4.0.0 and mongos >= 4.1.5.'); @@ -393,7 +393,7 @@ protected function isApiVersionRequired(): bool ); $document = current($cursor->toArray()); - } catch (CommandException $e) { + } catch (CommandException) { return false; } @@ -710,7 +710,7 @@ private function isFailCommandEnabled(): bool ); $document = current($cursor->toArray()); - } catch (CommandException $e) { + } catch (CommandException) { return false; } diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 428028d0c..7098cb24a 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -5,9 +5,9 @@ use MongoDB\BSON\Document; use MongoDB\BSON\PackedArray; use MongoDB\Driver\WriteConcern; -use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONArray; use MongoDB\Model\BSONDocument; +use TypeError; use function MongoDB\apply_type_map_to_document; use function MongoDB\create_field_path_type_map; @@ -115,8 +115,7 @@ public function provideDocumentsAndExpectedArrays(): array /** @dataProvider provideInvalidDocumentValuesForChecks */ public function testDocumentToArrayArgumentTypeCheck($document): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected $document to have type "document" (array or object)'); + $this->expectException(TypeError::class); document_to_array($document); } @@ -154,7 +153,7 @@ public function testIsFirstKeyOperator(callable $cast): void /** @dataProvider provideInvalidDocumentValuesForChecks */ public function testIsFirstKeyOperatorArgumentTypeCheck($document): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException(TypeError::class); is_first_key_operator($document); } diff --git a/tests/GridFS/BucketFunctionalTest.php b/tests/GridFS/BucketFunctionalTest.php index 46c0c9294..795764900 100644 --- a/tests/GridFS/BucketFunctionalTest.php +++ b/tests/GridFS/BucketFunctionalTest.php @@ -152,7 +152,7 @@ public function testDeleteStillRemovesChunksIfFileDoesNotExist($input, $expected try { $this->bucket->delete($id); $this->fail('FileNotFoundException was not thrown'); - } catch (FileNotFoundException $e) { + } catch (FileNotFoundException) { } $this->assertCollectionCount($this->chunksCollection, 0); diff --git a/tests/Model/CallbackIteratorTest.php b/tests/Model/CallbackIteratorTest.php index 6705e79f5..9f71415f7 100644 --- a/tests/Model/CallbackIteratorTest.php +++ b/tests/Model/CallbackIteratorTest.php @@ -28,11 +28,8 @@ public static function provideTests(): Generator $iteratorAggregate = new class ($listIterator) implements IteratorAggregate { - private Iterator $iterator; - - public function __construct(Iterator $iterator) + public function __construct(private Iterator $iterator) { - $this->iterator = $iterator; } public function getIterator(): Iterator diff --git a/tests/Model/ChangeStreamIteratorTest.php b/tests/Model/ChangeStreamIteratorTest.php index bdc062be6..890870445 100644 --- a/tests/Model/ChangeStreamIteratorTest.php +++ b/tests/Model/ChangeStreamIteratorTest.php @@ -7,6 +7,7 @@ namespace MongoDB\Tests\Model; +use MongoDB\BSON\PackedArray; use MongoDB\Collection; use MongoDB\Driver\Exception\LogicException; use MongoDB\Exception\InvalidArgumentException; @@ -52,7 +53,7 @@ public function testInitialResumeToken(): void /** @dataProvider provideInvalidDocumentValues */ public function testInitialResumeTokenArgumentTypeCheck($initialResumeToken): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($initialResumeToken instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new ChangeStreamIterator($this->collection->find(), 0, $initialResumeToken, null); } diff --git a/tests/Model/IndexInputTest.php b/tests/Model/IndexInputTest.php index 3f5e814e5..bc0539b51 100644 --- a/tests/Model/IndexInputTest.php +++ b/tests/Model/IndexInputTest.php @@ -48,11 +48,8 @@ public function testConstructorShouldRequireNameToBeString($name): void new IndexInput(['key' => ['x' => 1], 'name' => $name]); } - /** - * @dataProvider provideExpectedNameAndKey - * @param array|object $key - */ - public function testNameGeneration($expectedName, $key): void + /** @dataProvider provideExpectedNameAndKey */ + public function testNameGeneration($expectedName, array|object $key): void { $this->assertSame($expectedName, (string) new IndexInput(['key' => $key])); } diff --git a/tests/Operation/BulkWriteTest.php b/tests/Operation/BulkWriteTest.php index 2c255bec5..e0783ad38 100644 --- a/tests/Operation/BulkWriteTest.php +++ b/tests/Operation/BulkWriteTest.php @@ -2,10 +2,12 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedValueException; use MongoDB\Operation\BulkWrite; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; +use TypeError; class BulkWriteTest extends TestCase { @@ -287,8 +289,7 @@ public function testUpdateManyUpdateArgumentMissing(): void /** @dataProvider provideInvalidDocumentValues */ public function testUpdateManyUpdateArgumentTypeCheck($update): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected update operator(s) or non-empty pipeline for $operations[0]["updateMany"][1]'); + $this->expectException($update instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [ [BulkWrite::UPDATE_MANY => [['x' => 1], $update]], ]); @@ -380,8 +381,7 @@ public function testUpdateOneUpdateArgumentMissing(): void /** @dataProvider provideInvalidDocumentValues */ public function testUpdateOneUpdateArgumentTypeCheck($update): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected update operator(s) or non-empty pipeline for $operations[0]["updateOne"][1]'); + $this->expectException($update instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [ [BulkWrite::UPDATE_ONE => [['x' => 1], $update]], ]); diff --git a/tests/Operation/CountDocumentsTest.php b/tests/Operation/CountDocumentsTest.php index a7a048be1..0a16e4399 100644 --- a/tests/Operation/CountDocumentsTest.php +++ b/tests/Operation/CountDocumentsTest.php @@ -2,15 +2,17 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\CountDocuments; +use TypeError; class CountDocumentsTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new CountDocuments($this->getDatabaseName(), $this->getCollectionName(), $filter); } diff --git a/tests/Operation/CountTest.php b/tests/Operation/CountTest.php index 7a26341f6..a096859ae 100644 --- a/tests/Operation/CountTest.php +++ b/tests/Operation/CountTest.php @@ -2,16 +2,18 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Driver\ReadConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Count; +use TypeError; class CountTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new Count($this->getDatabaseName(), $this->getCollectionName(), $filter); } diff --git a/tests/Operation/DatabaseCommandTest.php b/tests/Operation/DatabaseCommandTest.php index c592aa5e4..74b3d8343 100644 --- a/tests/Operation/DatabaseCommandTest.php +++ b/tests/Operation/DatabaseCommandTest.php @@ -2,15 +2,17 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\DatabaseCommand; +use TypeError; class DatabaseCommandTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorCommandArgumentTypeCheck($command): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($command instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new DatabaseCommand($this->getDatabaseName(), $command); } diff --git a/tests/Operation/DeleteTest.php b/tests/Operation/DeleteTest.php index 143e7bb16..25d3b5ccf 100644 --- a/tests/Operation/DeleteTest.php +++ b/tests/Operation/DeleteTest.php @@ -7,6 +7,7 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Delete; @@ -17,7 +18,7 @@ class DeleteTest extends TestCase /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new Delete($this->getDatabaseName(), $this->getCollectionName(), $filter, 0); } diff --git a/tests/Operation/DistinctTest.php b/tests/Operation/DistinctTest.php index f04d244ca..77380f249 100644 --- a/tests/Operation/DistinctTest.php +++ b/tests/Operation/DistinctTest.php @@ -2,17 +2,19 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Driver\ReadConcern; use MongoDB\Driver\ReadPreference; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Distinct; +use TypeError; class DistinctTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', $filter); } diff --git a/tests/Operation/FindOneAndDeleteTest.php b/tests/Operation/FindOneAndDeleteTest.php index 042c3a279..c4cbda6a8 100644 --- a/tests/Operation/FindOneAndDeleteTest.php +++ b/tests/Operation/FindOneAndDeleteTest.php @@ -2,16 +2,18 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\FindOneAndDelete; +use TypeError; class FindOneAndDeleteTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new FindOneAndDelete($this->getDatabaseName(), $this->getCollectionName(), $filter); } diff --git a/tests/Operation/FindOneAndReplaceTest.php b/tests/Operation/FindOneAndReplaceTest.php index 94075b89d..33681f7d4 100644 --- a/tests/Operation/FindOneAndReplaceTest.php +++ b/tests/Operation/FindOneAndReplaceTest.php @@ -2,23 +2,25 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\FindOneAndReplace; +use TypeError; class FindOneAndReplaceTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new FindOneAndReplace($this->getDatabaseName(), $this->getCollectionName(), $filter, []); } /** @dataProvider provideInvalidDocumentValues */ public function testConstructorReplacementArgumentTypeCheck($replacement): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($replacement instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new FindOneAndReplace($this->getDatabaseName(), $this->getCollectionName(), [], $replacement); } diff --git a/tests/Operation/FindOneAndUpdateTest.php b/tests/Operation/FindOneAndUpdateTest.php index 99d49128a..f0917a856 100644 --- a/tests/Operation/FindOneAndUpdateTest.php +++ b/tests/Operation/FindOneAndUpdateTest.php @@ -2,23 +2,25 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\FindOneAndUpdate; +use TypeError; class FindOneAndUpdateTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new FindOneAndUpdate($this->getDatabaseName(), $this->getCollectionName(), $filter, []); } /** @dataProvider provideInvalidDocumentValues */ public function testConstructorUpdateArgumentTypeCheck($update): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($update instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new FindOneAndUpdate($this->getDatabaseName(), $this->getCollectionName(), [], $update); } diff --git a/tests/Operation/FindTest.php b/tests/Operation/FindTest.php index 68341d2ac..806b883ce 100644 --- a/tests/Operation/FindTest.php +++ b/tests/Operation/FindTest.php @@ -2,17 +2,19 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Driver\ReadConcern; use MongoDB\Driver\ReadPreference; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Find; +use TypeError; class FindTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new Find($this->getDatabaseName(), $this->getCollectionName(), $filter); } diff --git a/tests/Operation/InsertOneTest.php b/tests/Operation/InsertOneTest.php index ad7212c7c..ad325cd0b 100644 --- a/tests/Operation/InsertOneTest.php +++ b/tests/Operation/InsertOneTest.php @@ -2,17 +2,19 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedValueException; use MongoDB\Operation\InsertOne; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; +use TypeError; class InsertOneTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorDocumentArgumentTypeCheck($document): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($document instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new InsertOne($this->getDatabaseName(), $this->getCollectionName(), $document); } diff --git a/tests/Operation/MapReduceTest.php b/tests/Operation/MapReduceTest.php index 04b7a58d0..92e6881c1 100644 --- a/tests/Operation/MapReduceTest.php +++ b/tests/Operation/MapReduceTest.php @@ -1,5 +1,7 @@ expectException(InvalidArgumentException::class); + $this->expectException(TypeError::class); new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out); } diff --git a/tests/Operation/ReplaceOneTest.php b/tests/Operation/ReplaceOneTest.php index 404cdae78..73f659b85 100644 --- a/tests/Operation/ReplaceOneTest.php +++ b/tests/Operation/ReplaceOneTest.php @@ -2,24 +2,26 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedValueException; use MongoDB\Operation\ReplaceOne; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; +use TypeError; class ReplaceOneTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), $filter, ['y' => 1]); } /** @dataProvider provideInvalidDocumentValues */ public function testConstructorReplacementArgumentTypeCheck($replacement): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($replacement instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $replacement); } diff --git a/tests/Operation/UpdateManyTest.php b/tests/Operation/UpdateManyTest.php index 7f12207c8..99acecf33 100644 --- a/tests/Operation/UpdateManyTest.php +++ b/tests/Operation/UpdateManyTest.php @@ -2,22 +2,24 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\UpdateMany; +use TypeError; class UpdateManyTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new UpdateMany($this->getDatabaseName(), $this->getCollectionName(), $filter, ['$set' => ['x' => 1]]); } /** @dataProvider provideInvalidDocumentValues */ public function testConstructorUpdateArgumentTypeCheck($update): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($update instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new UpdateMany($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $update); } diff --git a/tests/Operation/UpdateOneTest.php b/tests/Operation/UpdateOneTest.php index 657f3d650..e0c4086a0 100644 --- a/tests/Operation/UpdateOneTest.php +++ b/tests/Operation/UpdateOneTest.php @@ -2,22 +2,24 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\UpdateOne; +use TypeError; class UpdateOneTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new UpdateOne($this->getDatabaseName(), $this->getCollectionName(), $filter, ['$set' => ['x' => 1]]); } /** @dataProvider provideInvalidDocumentValues */ public function testConstructorUpdateArgumentTypeCheck($update): void { - $this->expectException(InvalidArgumentException::class); + $this->expectException($update instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new UpdateOne($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $update); } diff --git a/tests/Operation/UpdateSearchIndexTest.php b/tests/Operation/UpdateSearchIndexTest.php index 90c623fc3..befe07eb1 100644 --- a/tests/Operation/UpdateSearchIndexTest.php +++ b/tests/Operation/UpdateSearchIndexTest.php @@ -2,8 +2,10 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\UpdateSearchIndex; +use TypeError; class UpdateSearchIndexTest extends TestCase { @@ -16,8 +18,7 @@ public function testConstructorIndexNameMustNotBeEmpty(): void /** @dataProvider provideInvalidDocumentValues */ public function testConstructorIndexDefinitionMustBeADocument($definition): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Expected $definition to have type "document"'); + $this->expectException($definition instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new UpdateSearchIndex($this->getDatabaseName(), $this->getCollectionName(), 'index name', $definition); } } diff --git a/tests/Operation/UpdateTest.php b/tests/Operation/UpdateTest.php index 85d00ef88..bf8d993ca 100644 --- a/tests/Operation/UpdateTest.php +++ b/tests/Operation/UpdateTest.php @@ -2,25 +2,25 @@ namespace MongoDB\Tests\Operation; +use MongoDB\BSON\PackedArray; use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Update; +use TypeError; class UpdateTest extends TestCase { /** @dataProvider provideInvalidDocumentValues */ public function testConstructorFilterArgumentTypeCheck($filter): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/Expected \$filter to have type "document" \(array or object\) but found ".+"/'); + $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new Update($this->getDatabaseName(), $this->getCollectionName(), $filter, ['$set' => ['x' => 1]]); } /** @dataProvider provideInvalidUpdateValues */ public function testConstructorUpdateArgumentTypeCheck($update): void { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessageMatches('/Expected \$update to have type "array or object" but found "[\w ]+"/'); + $this->expectException(TypeError::class); new Update($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $update); } diff --git a/tests/Operation/WatchFunctionalTest.php b/tests/Operation/WatchFunctionalTest.php index 58b8c844c..a7e0ae534 100644 --- a/tests/Operation/WatchFunctionalTest.php +++ b/tests/Operation/WatchFunctionalTest.php @@ -1135,9 +1135,9 @@ public function testResumeTokenNotFoundDoesNotAdvanceKey(): void try { $this->advanceCursorUntilValid($changeStream); $this->fail('Exception for missing resume token was not thrown'); - } catch (ResumeTokenException $e) { + } catch (ResumeTokenException) { /* On server versions < 4.1.8, a client-side error is thrown. */ - } catch (ServerException $e) { + } catch (ServerException) { /* On server versions >= 4.1.8, the error is thrown server-side. */ } @@ -1147,7 +1147,7 @@ public function testResumeTokenNotFoundDoesNotAdvanceKey(): void try { $changeStream->next(); $this->fail('Exception for missing resume token was not thrown'); - } catch (ResumeTokenException | ServerException $e) { + } catch (ResumeTokenException | ServerException) { } $this->assertFalse($changeStream->valid()); @@ -1365,7 +1365,7 @@ public function testOriginalReadPreferenceIsPreservedOnResume(): void try { $secondary = $this->manager->selectServer($readPreference); - } catch (ConnectionTimeoutException $e) { + } catch (ConnectionTimeoutException) { $this->markTestSkipped('Secondary is not available'); } diff --git a/tests/PHPUnit/Functions.php b/tests/PHPUnit/Functions.php index 64a4eee19..6f445f410 100644 --- a/tests/PHPUnit/Functions.php +++ b/tests/PHPUnit/Functions.php @@ -83,14 +83,11 @@ * * @see Assert::assertArrayHasKey * - * @param int|string $key - * @param array|ArrayAccess $array - * * @throws ExpectationFailedException * @throws InvalidArgumentException * @throws Exception */ - function assertArrayHasKey($key, $array, string $message = ''): void + function assertArrayHasKey(int|string $key, array|ArrayAccess $array, string $message = ''): void { Assert::assertArrayHasKey(...func_get_args()); } @@ -102,14 +99,11 @@ function assertArrayHasKey($key, $array, string $message = ''): void * * @see Assert::assertArrayNotHasKey * - * @param int|string $key - * @param array|ArrayAccess $array - * * @throws ExpectationFailedException * @throws InvalidArgumentException * @throws Exception */ - function assertArrayNotHasKey($key, $array, string $message = ''): void + function assertArrayNotHasKey(int|string $key, array|ArrayAccess $array, string $message = ''): void { Assert::assertArrayNotHasKey(...func_get_args()); } @@ -212,13 +206,11 @@ function assertNotContainsOnly(string $type, $haystack, ?bool $isNativeType = nu * * @see Assert::assertCount * - * @param Countable|iterable $haystack - * * @throws ExpectationFailedException * @throws InvalidArgumentException * @throws Exception */ - function assertCount(int $expectedCount, $haystack, string $message = ''): void + function assertCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void { Assert::assertCount(...func_get_args()); } @@ -230,13 +222,11 @@ function assertCount(int $expectedCount, $haystack, string $message = ''): void * * @see Assert::assertNotCount * - * @param Countable|iterable $haystack - * * @throws ExpectationFailedException * @throws InvalidArgumentException * @throws Exception */ - function assertNotCount(int $expectedCount, $haystack, string $message = ''): void + function assertNotCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void { Assert::assertNotCount(...func_get_args()); } @@ -1834,14 +1824,11 @@ function assertNotRegExp(string $pattern, string $string, string $message = ''): * * @see Assert::assertSameSize * - * @param Countable|iterable $expected - * @param Countable|iterable $actual - * * @throws ExpectationFailedException * @throws InvalidArgumentException * @throws Exception */ - function assertSameSize($expected, $actual, string $message = ''): void + function assertSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void { Assert::assertSameSize(...func_get_args()); } @@ -1854,14 +1841,11 @@ function assertSameSize($expected, $actual, string $message = ''): void * * @see Assert::assertNotSameSize * - * @param Countable|iterable $expected - * @param Countable|iterable $actual - * * @throws ExpectationFailedException * @throws InvalidArgumentException * @throws Exception */ - function assertNotSameSize($expected, $actual, string $message = ''): void + function assertNotSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void { Assert::assertNotSameSize(...func_get_args()); } @@ -2077,13 +2061,11 @@ function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, * * @see Assert::assertXmlStringEqualsXmlFile * - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException * @throws InvalidArgumentException * @throws XmlException */ - function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + function assertXmlStringEqualsXmlFile(string $expectedFile, DOMDocument|string $actualXml, string $message = ''): void { Assert::assertXmlStringEqualsXmlFile(...func_get_args()); } @@ -2095,13 +2077,11 @@ function assertXmlStringEqualsXmlFile(string $expectedFile, $actualXml, string $ * * @see Assert::assertXmlStringNotEqualsXmlFile * - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException * @throws InvalidArgumentException * @throws XmlException */ - function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, string $message = ''): void + function assertXmlStringNotEqualsXmlFile(string $expectedFile, DOMDocument|string $actualXml, string $message = ''): void { Assert::assertXmlStringNotEqualsXmlFile(...func_get_args()); } @@ -2113,14 +2093,11 @@ function assertXmlStringNotEqualsXmlFile(string $expectedFile, $actualXml, strin * * @see Assert::assertXmlStringEqualsXmlString * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException * @throws InvalidArgumentException * @throws XmlException */ - function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $message = ''): void + function assertXmlStringEqualsXmlString(DOMDocument|string $expectedXml, DOMDocument|string $actualXml, string $message = ''): void { Assert::assertXmlStringEqualsXmlString(...func_get_args()); } @@ -2132,14 +2109,11 @@ function assertXmlStringEqualsXmlString($expectedXml, $actualXml, string $messag * * @see Assert::assertXmlStringNotEqualsXmlString * - * @param DOMDocument|string $expectedXml - * @param DOMDocument|string $actualXml - * * @throws ExpectationFailedException * @throws InvalidArgumentException * @throws XmlException */ - function assertXmlStringNotEqualsXmlString($expectedXml, $actualXml, string $message = ''): void + function assertXmlStringNotEqualsXmlString(DOMDocument|string $expectedXml, DOMDocument|string $actualXml, string $message = ''): void { Assert::assertXmlStringNotEqualsXmlString(...func_get_args()); } diff --git a/tests/SpecTests/ClientSideEncryptionSpecTest.php b/tests/SpecTests/ClientSideEncryptionSpecTest.php index 5f00cb553..574cf4f23 100644 --- a/tests/SpecTests/ClientSideEncryptionSpecTest.php +++ b/tests/SpecTests/ClientSideEncryptionSpecTest.php @@ -1698,7 +1698,7 @@ static function (self $test, Client $setupClient, ClientEncryption $clientEncryp try { $encryptedClient->selectCollection('db', 'decryption_events')->aggregate([]); $test->fail('Expected exception to be thrown'); - } catch (ConnectionTimeoutException $e) { + } catch (ConnectionTimeoutException) { $test->addToAssertionCount(1); } @@ -1942,7 +1942,7 @@ private function encryptCorpusValue(string $fieldName, stdClass $data, ClientEnc try { $clientEncryption->encrypt($data->value, $encryptionOptions); $this->fail('Expected exception to be thrown'); - } catch (RuntimeException $e) { + } catch (RuntimeException) { } return $data->value; diff --git a/tests/SpecTests/CommandExpectations.php b/tests/SpecTests/CommandExpectations.php index 76a3a0a0a..7c9a946f0 100644 --- a/tests/SpecTests/CommandExpectations.php +++ b/tests/SpecTests/CommandExpectations.php @@ -37,12 +37,8 @@ class CommandExpectations implements CommandSubscriber /** @var list */ private array $ignoredCommandNames = []; - private Client $observedClient; - - private function __construct(Client $observedClient, array $events) + private function __construct(private Client $observedClient, array $events) { - $this->observedClient = $observedClient; - foreach ($events as $event) { switch (key((array) $event)) { case 'command_failed_event': diff --git a/tests/SpecTests/Context.php b/tests/SpecTests/Context.php index 7731f65db..1341e65da 100644 --- a/tests/SpecTests/Context.php +++ b/tests/SpecTests/Context.php @@ -30,10 +30,6 @@ final class Context private ?Client $client = null; - public ?string $collectionName = null; - - public string $databaseName; - public array $defaultWriteOptions = []; public array $outcomeReadOptions = []; @@ -54,10 +50,8 @@ final class Context private ?Client $encryptedClient = null; - private function __construct(string $databaseName, ?string $collectionName) + private function __construct(public string $databaseName, public ?string $collectionName = null) { - $this->databaseName = $databaseName; - $this->collectionName = $collectionName; $this->outcomeCollectionName = $collectionName; $this->internalClient = FunctionalTestCase::createTestClient(); } diff --git a/tests/SpecTests/DocumentsMatchConstraint.php b/tests/SpecTests/DocumentsMatchConstraint.php index 1b3680d3f..16e7914a6 100644 --- a/tests/SpecTests/DocumentsMatchConstraint.php +++ b/tests/SpecTests/DocumentsMatchConstraint.php @@ -16,7 +16,6 @@ use Symfony\Bridge\PhpUnit\ConstraintTrait; use function array_values; -use function get_class; use function get_debug_type; use function is_array; use function is_float; @@ -39,10 +38,6 @@ class DocumentsMatchConstraint extends Constraint { use ConstraintTrait; - private bool $ignoreExtraKeysInRoot = false; - - private bool $ignoreExtraKeysInEmbedded = false; - /** * TODO: This is not currently used, but was preserved from the design of * TestCase::assertMatchesDocument(), which would sort keys and then compare @@ -52,8 +47,7 @@ class DocumentsMatchConstraint extends Constraint */ private bool $sortKeys = false; - /** @var BSONArray|BSONDocument */ - private $value; + private BSONArray|BSONDocument $value; private ?ComparisonFailure $lastFailure = null; @@ -62,15 +56,12 @@ class DocumentsMatchConstraint extends Constraint /** * Creates a new constraint. * - * @param array|object $value - * @param boolean $ignoreExtraKeysInRoot If true, ignore extra keys within the root document - * @param boolean $ignoreExtraKeysInEmbedded If true, ignore extra keys within embedded documents + * @param boolean $ignoreExtraKeysInRoot If true, ignore extra keys within the root document + * @param boolean $ignoreExtraKeysInEmbedded If true, ignore extra keys within embedded documents */ - public function __construct($value, bool $ignoreExtraKeysInRoot = false, bool $ignoreExtraKeysInEmbedded = false) + public function __construct(array|object $value, private bool $ignoreExtraKeysInRoot = false, private bool $ignoreExtraKeysInEmbedded = false) { $this->value = $this->prepareBSON($value, true, $this->sortKeys); - $this->ignoreExtraKeysInRoot = $ignoreExtraKeysInRoot; - $this->ignoreExtraKeysInEmbedded = $ignoreExtraKeysInEmbedded; $this->comparatorFactory = Factory::getInstance(); } @@ -111,11 +102,8 @@ private function doEvaluate($other, $description = '', $returnResult = false) } } - /** - * @param string|string[] $expectedType - * @param mixed $actualValue - */ - private function assertBSONType($expectedType, $actualValue): void + /** @param string|BSONArray[] $expectedType */ + private function assertBSONType(string|BSONArray $expectedType, mixed $actualValue): void { assertThat( $expectedType, @@ -133,11 +121,11 @@ private function assertBSONType($expectedType, $actualValue): void */ private function assertEquals(ArrayObject $expected, ArrayObject $actual, bool $ignoreExtraKeys, string $keyPrefix = ''): void { - if (get_class($expected) !== get_class($actual)) { + if ($expected::class !== $actual::class) { throw new RuntimeException(sprintf( '%s is not instance of expected class "%s"', $this->exporter()->shortenedExport($actual), - get_class($expected), + $expected::class, )); } @@ -237,7 +225,7 @@ private function doMatches($other) try { $this->assertEquals($this->value, $other, $this->ignoreExtraKeysInRoot); - } catch (RuntimeException $e) { + } catch (RuntimeException) { return false; } @@ -261,12 +249,10 @@ private static function isNumeric($value): bool * its type and keys. Keys within documents will optionally be sorted. Each * value within the array or document will then be prepared recursively. * - * @param array|object $bson - * @param boolean $isRoot If true, ensure an array value is converted to a document - * @return BSONDocument|BSONArray + * @param boolean $isRoot If true, ensure an array value is converted to a document * @throws InvalidArgumentException if $bson is not an array or object */ - private function prepareBSON($bson, bool $isRoot, bool $sortKeys = false) + private function prepareBSON(array|object $bson, bool $isRoot, bool $sortKeys = false): BSONDocument|BSONArray { if (! is_array($bson) && ! is_object($bson)) { throw new InvalidArgumentException('$bson is not an array or object'); diff --git a/tests/SpecTests/ErrorExpectation.php b/tests/SpecTests/ErrorExpectation.php index df13dcf2a..82511b4d6 100644 --- a/tests/SpecTests/ErrorExpectation.php +++ b/tests/SpecTests/ErrorExpectation.php @@ -12,7 +12,6 @@ use stdClass; use Throwable; -use function get_class; use function is_array; use function is_string; use function sprintf; @@ -105,7 +104,7 @@ public function assert(TestCase $test, ?Throwable $actual = null): void { if (! $this->isExpected) { if ($actual !== null) { - $test->fail(sprintf("Operation threw unexpected %s: %s\n%s", get_class($actual), $actual->getMessage(), $actual->getTraceAsString())); + $test->fail(sprintf("Operation threw unexpected %s: %s\n%s", $actual::class, $actual->getMessage(), $actual->getTraceAsString())); } $test->addToAssertionCount(1); diff --git a/tests/SpecTests/FunctionalTestCase.php b/tests/SpecTests/FunctionalTestCase.php index 16fc999db..cd02f5908 100644 --- a/tests/SpecTests/FunctionalTestCase.php +++ b/tests/SpecTests/FunctionalTestCase.php @@ -84,11 +84,8 @@ public static function assertCommandReplyMatches(stdClass $expected, stdClass $a * Asserts that two given documents match. * * Extra keys in the actual value's document(s) will be ignored. - * - * @param array|object $expectedDocument - * @param array|object $actualDocument */ - public static function assertDocumentsMatch($expectedDocument, $actualDocument, string $message = ''): void + public static function assertDocumentsMatch(array|object $expectedDocument, array|object $actualDocument, string $message = ''): void { $constraint = new DocumentsMatchConstraint($expectedDocument, true, true); @@ -173,10 +170,8 @@ protected function checkServerRequirements(array $runOn): void * * This decodes the file through the driver's extended JSON parser to ensure * proper handling of special types. - * - * @return array|object */ - protected function decodeJson(string $json) + protected function decodeJson(string $json): array|object { return Document::fromJSON($json)->toPHP(); } diff --git a/tests/SpecTests/Operation.php b/tests/SpecTests/Operation.php index f7d437542..3672314cf 100644 --- a/tests/SpecTests/Operation.php +++ b/tests/SpecTests/Operation.php @@ -213,10 +213,9 @@ public function assert(FunctionalTestCase $test, Context $context, bool $bubbleE /** * Executes the operation with a given context. * - * @return mixed * @throws LogicException if the operation is unsupported */ - private function execute(FunctionalTestCase $test, Context $context) + private function execute(FunctionalTestCase $test, Context $context): mixed { switch ($this->object) { case self::OBJECT_CLIENT: @@ -266,10 +265,9 @@ private function execute(FunctionalTestCase $test, Context $context) /** * Executes the client operation and return its result. * - * @return mixed * @throws LogicException if the collection operation is unsupported */ - private function executeForClient(Client $client, Context $context) + private function executeForClient(Client $client, Context $context): mixed { $args = $context->prepareOptions($this->arguments); $context->replaceArgumentSessionPlaceholder($args); @@ -295,10 +293,9 @@ private function executeForClient(Client $client, Context $context) /** * Executes the collection operation and return its result. * - * @return mixed * @throws LogicException if the collection operation is unsupported */ - private function executeForCollection(Collection $collection, Context $context) + private function executeForCollection(Collection $collection, Context $context): mixed { $args = $context->prepareOptions($this->arguments); $context->replaceArgumentSessionPlaceholder($args); @@ -435,10 +432,9 @@ private function executeForCollection(Collection $collection, Context $context) /** * Executes the database operation and return its result. * - * @return mixed * @throws LogicException if the database operation is unsupported */ - private function executeForDatabase(Database $database, Context $context) + private function executeForDatabase(Database $database, Context $context): mixed { $args = $context->prepareOptions($this->arguments); $context->replaceArgumentSessionPlaceholder($args); @@ -488,10 +484,9 @@ private function executeForDatabase(Database $database, Context $context) /** * Executes the GridFS bucket operation and return its result. * - * @return mixed * @throws LogicException if the database operation is unsupported */ - private function executeForGridFSBucket(Bucket $bucket, Context $context) + private function executeForGridFSBucket(Bucket $bucket, Context $context): mixed { $args = $context->prepareOptions($this->arguments); $context->replaceArgumentSessionPlaceholder($args); @@ -529,10 +524,9 @@ private function executeForGridFSBucket(Bucket $bucket, Context $context) /** * Executes the session operation and return its result. * - * @return mixed * @throws LogicException if the session operation is unsupported */ - private function executeForSession(Session $session, FunctionalTestCase $test, Context $context) + private function executeForSession(Session $session, FunctionalTestCase $test, Context $context): mixed { switch ($this->name) { case 'abortTransaction': diff --git a/tests/SpecTests/PrimaryStepDownSpecTest.php b/tests/SpecTests/PrimaryStepDownSpecTest.php index 7fd0be6e1..2de6d4c44 100644 --- a/tests/SpecTests/PrimaryStepDownSpecTest.php +++ b/tests/SpecTests/PrimaryStepDownSpecTest.php @@ -215,7 +215,7 @@ public function testGetMoreIteration(): void $attempts++; $primary->executeCommand('admin', new Command(['replSetStepDown' => 5, 'force' => true])); $success = true; - } catch (DriverException $e) { + } catch (DriverException) { if ($attempts == 10) { $this->fail(sprintf('Could not successfully execute replSetStepDown within %d attempts', $attempts)); } @@ -289,7 +289,7 @@ private function waitForPrimaryReelection(): void $this->insertDocuments(1); return; - } catch (DriverException $e) { + } catch (DriverException) { $this->client->getManager()->selectServer(); return; diff --git a/tests/SpecTests/ResultExpectation.php b/tests/SpecTests/ResultExpectation.php index 580bdfd0a..1a28ba123 100644 --- a/tests/SpecTests/ResultExpectation.php +++ b/tests/SpecTests/ResultExpectation.php @@ -36,15 +36,12 @@ final class ResultExpectation public const ASSERT_CALLABLE = 11; public const ASSERT_DOCUMENTS_MATCH = 12; - private int $assertionType = self::ASSERT_NOTHING; - - /** @var mixed */ - private $expectedValue; + private mixed $expectedValue; /** @var callable */ private $assertionCallable; - private function __construct(int $assertionType, $expectedValue) + private function __construct(private int $assertionType, $expectedValue) { switch ($assertionType) { case self::ASSERT_BULKWRITE: @@ -66,7 +63,6 @@ private function __construct(int $assertionType, $expectedValue) break; } - $this->assertionType = $assertionType; $this->expectedValue = $expectedValue; } @@ -333,9 +329,8 @@ private static function isArrayOfObjects($array) * Determines whether the result is actually an error expectation. * * @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.rst#test-format - * @param mixed $result */ - private static function isErrorResult($result): bool + private static function isErrorResult(mixed $result): bool { if (! is_object($result)) { return false; diff --git a/tests/SpecTests/RetryableWrites/Prose3_ReturnOriginalErrorTest.php b/tests/SpecTests/RetryableWrites/Prose3_ReturnOriginalErrorTest.php index 0b80b6af9..7e573fc8c 100644 --- a/tests/SpecTests/RetryableWrites/Prose3_ReturnOriginalErrorTest.php +++ b/tests/SpecTests/RetryableWrites/Prose3_ReturnOriginalErrorTest.php @@ -44,11 +44,8 @@ public function testNoWritesPerformedErrorReturnsOriginalError(): void ]); $subscriber = new class ($this) implements CommandSubscriber { - private FunctionalTestCase $testCase; - - public function __construct(FunctionalTestCase $testCase) + public function __construct(private FunctionalTestCase $testCase) { - $this->testCase = $testCase; } public function commandStarted(CommandStartedEvent $event): void diff --git a/tests/TestCase.php b/tests/TestCase.php index b037c2908..287dc0b59 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -91,11 +91,8 @@ protected static function getDatabaseName(): string * * Only fields in the expected document will be checked. The actual document * may contain additional fields. - * - * @param array|object $expectedDocument - * @param array|object $actualDocument */ - public function assertMatchesDocument($expectedDocument, $actualDocument): void + public function assertMatchesDocument(array|object $expectedDocument, array|object $actualDocument): void { (new DocumentsMatchConstraint($expectedDocument, true, true))->evaluate($actualDocument); } @@ -105,11 +102,8 @@ public function assertMatchesDocument($expectedDocument, $actualDocument): void * * The actual document will be compared directly with the expected document * and may not contain extra fields. - * - * @param array|object $expectedDocument - * @param array|object $actualDocument */ - public function assertSameDocument($expectedDocument, $actualDocument): void + public function assertSameDocument(array|object $expectedDocument, array|object $actualDocument): void { $this->assertEquals( Document::fromPHP($this->normalizeBSON($expectedDocument))->toRelaxedExtendedJSON(), @@ -369,11 +363,9 @@ protected function wrapValuesForDataProvider(array $values): array * its type and keys. Document fields will be sorted alphabetically. Each * value within the array or document will then be normalized recursively. * - * @param array|object $bson - * @return BSONDocument|BSONArray * @throws InvalidArgumentException if $bson is not an array or object */ - private function normalizeBSON($bson) + private function normalizeBSON(array|object $bson): BSONDocument|BSONArray { if (! is_array($bson) && ! is_object($bson)) { throw new InvalidArgumentException('$bson is not an array or object'); diff --git a/tests/UnifiedSpecTests/Constraint/IsBsonType.php b/tests/UnifiedSpecTests/Constraint/IsBsonType.php index da88aca39..e2f73e807 100644 --- a/tests/UnifiedSpecTests/Constraint/IsBsonType.php +++ b/tests/UnifiedSpecTests/Constraint/IsBsonType.php @@ -67,15 +67,11 @@ final class IsBsonType extends Constraint 'number', ]; - private string $type; - - public function __construct(string $type) + public function __construct(private string $type) { if (! in_array($type, self::$types)) { throw new RuntimeException(sprintf('Type specified for %s <%s> is not a valid type', self::class, $type)); } - - $this->type = $type; } public static function any(): LogicalOr diff --git a/tests/UnifiedSpecTests/Constraint/Matches.php b/tests/UnifiedSpecTests/Constraint/Matches.php index caf391f40..2a80357fd 100644 --- a/tests/UnifiedSpecTests/Constraint/Matches.php +++ b/tests/UnifiedSpecTests/Constraint/Matches.php @@ -51,10 +51,7 @@ class Matches extends Constraint { use ConstraintTrait; - private ?EntityMap $entityMap = null; - - /** @var mixed */ - private $value; + private mixed $value; private bool $allowExtraRootKeys; @@ -64,10 +61,9 @@ class Matches extends Constraint private Factory $comparatorFactory; - public function __construct($value, ?EntityMap $entityMap = null, $allowExtraRootKeys = true, $allowOperators = true) + public function __construct($value, private ?EntityMap $entityMap = null, $allowExtraRootKeys = true, $allowOperators = true) { $this->value = self::prepare($value); - $this->entityMap = $entityMap; $this->allowExtraRootKeys = $allowExtraRootKeys; $this->allowOperators = $allowOperators; $this->comparatorFactory = Factory::getInstance(); @@ -348,7 +344,7 @@ private function doMatches($other) try { $this->assertMatches($this->value, $other); - } catch (RuntimeException $e) { + } catch (RuntimeException) { return false; } @@ -408,11 +404,8 @@ private static function isOperator(BSONDocument $document): bool * converted to a BSONDocument; otherwise, it will be converted to a * BSONArray or BSONDocument based on its keys. Each value within an array * or document will then be prepared recursively. - * - * @param mixed $bson - * @return mixed */ - private static function prepare($bson) + private static function prepare(mixed $bson): mixed { if (! is_array($bson) && ! is_object($bson)) { return $bson; diff --git a/tests/UnifiedSpecTests/Context.php b/tests/UnifiedSpecTests/Context.php index 18e2c3a39..2210cad75 100644 --- a/tests/UnifiedSpecTests/Context.php +++ b/tests/UnifiedSpecTests/Context.php @@ -48,23 +48,17 @@ final class Context /** @var array */ private array $eventObserversByClient = []; - private Client $internalClient; - private bool $inLoop = false; - private string $uri; - private string $singleMongosUri; private string $multiMongosUri; private ?object $advanceClusterTime = null; - public function __construct(Client $internalClient, string $uri) + public function __construct(private Client $internalClient, private string $uri) { $this->entityMap = new EntityMap(); - $this->internalClient = $internalClient; - $this->uri = $uri; /* TODO: Consider leaving these unset, although that might require * redundant topology/serverless checks in Context::createClient(). */ @@ -232,8 +226,7 @@ public function stopEventCollectors(): void } } - /** @param string|array $readPreferenceTags */ - private function convertReadPreferenceTags($readPreferenceTags): array + private function convertReadPreferenceTags(string|array $readPreferenceTags): array { return array_map( static function (string $readPreferenceTagSet): array { diff --git a/tests/UnifiedSpecTests/EntityMap.php b/tests/UnifiedSpecTests/EntityMap.php index 00c7f9d3f..32b99ffd0 100644 --- a/tests/UnifiedSpecTests/EntityMap.php +++ b/tests/UnifiedSpecTests/EntityMap.php @@ -63,12 +63,9 @@ public function offsetExists($id): bool return array_key_exists($id, $this->map); } - /** - * @see https://php.net/arrayaccess.offsetget - * @return mixed - */ + /** @see https://php.net/arrayaccess.offsetget */ #[ReturnTypeWillChange] - public function offsetGet($id) + public function offsetGet($id): mixed { assertIsString($id); assertArrayHasKey($id, $this->map, sprintf('No entity is defined for "%s"', $id)); @@ -100,16 +97,11 @@ public function set(string $id, $value, ?string $parentId = null): void $parent = $parentId === null ? null : $this->map[$parentId]; $this->map[$id] = new class ($id, $value, $parent) { - public string $id; - /** @var mixed */ - public $value; - public ?self $parent; + public mixed $value; - public function __construct(string $id, $value, ?self $parent = null) + public function __construct(public string $id, $value, public ?self $parent = null) { - $this->id = $id; $this->value = $value; - $this->parent = $parent; } public function getRoot(): self diff --git a/tests/UnifiedSpecTests/EventCollector.php b/tests/UnifiedSpecTests/EventCollector.php index 0a688e41a..74a9284cc 100644 --- a/tests/UnifiedSpecTests/EventCollector.php +++ b/tests/UnifiedSpecTests/EventCollector.php @@ -10,7 +10,6 @@ use function array_filter; use function array_flip; -use function get_class; use function microtime; use function MongoDB\Driver\Monitoring\addSubscriber; use function MongoDB\Driver\Monitoring\removeSubscriber; @@ -46,15 +45,9 @@ final class EventCollector implements CommandSubscriber 'CommandFailedEvent' => CommandFailedEvent::class, ]; - private string $clientId; - - private Context $context; - private array $collectEvents = []; - private BSONArray $eventList; - - public function __construct(BSONArray $eventList, array $collectEvents, string $clientId, Context $context) + public function __construct(private BSONArray $eventList, array $collectEvents, private string $clientId, private Context $context) { assertNotEmpty($collectEvents); @@ -69,10 +62,6 @@ public function __construct(BSONArray $eventList, array $collectEvents, string $ $this->collectEvents[self::$supportedEvents[$event]] = 1; } } - - $this->clientId = $clientId; - $this->context = $context; - $this->eventList = $eventList; } /** @see https://php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandfailed.php */ @@ -103,8 +92,7 @@ public function stop(): void removeSubscriber($this); } - /** @param CommandStartedEvent|CommandSucceededEvent|CommandFailedEvent $event */ - private function handleCommandMonitoringEvent($event): void + private function handleCommandMonitoringEvent(CommandStartedEvent|CommandSucceededEvent|CommandFailedEvent $event): void { assertIsObject($event); @@ -112,7 +100,7 @@ private function handleCommandMonitoringEvent($event): void return; } - if (! isset($this->collectEvents[get_class($event)])) { + if (! isset($this->collectEvents[$event::class])) { return; } @@ -144,8 +132,7 @@ private function handleCommandMonitoringEvent($event): void $this->eventList[] = $log; } - /** @param CommandStartedEvent|CommandSucceededEvent|CommandFailedEvent $event */ - private static function getConnectionId($event): string + private static function getConnectionId(CommandStartedEvent|CommandSucceededEvent|CommandFailedEvent $event): string { $server = $event->getServer(); @@ -160,6 +147,6 @@ private static function getEventName(object $event): string $eventNamesByClass = array_flip(array_filter(self::$supportedEvents)); } - return $eventNamesByClass[get_class($event)]; + return $eventNamesByClass[$event::class]; } } diff --git a/tests/UnifiedSpecTests/EventObserver.php b/tests/UnifiedSpecTests/EventObserver.php index 1f8af6648..cd2f44d24 100644 --- a/tests/UnifiedSpecTests/EventObserver.php +++ b/tests/UnifiedSpecTests/EventObserver.php @@ -15,7 +15,6 @@ use function array_reverse; use function count; use function current; -use function get_class; use function is_object; use function key; use function MongoDB\Driver\Monitoring\addSubscriber; @@ -95,10 +94,6 @@ final class EventObserver implements CommandSubscriber private array $actualEvents = []; - private string $clientId; - - private Context $context; - /** * The configureFailPoint command (used by failPoint and targetedFailPoint * operations) is always ignored. @@ -107,9 +102,7 @@ final class EventObserver implements CommandSubscriber private array $observeEvents = []; - private bool $observeSensitiveCommands; - - public function __construct(array $observeEvents, array $ignoreCommands, bool $observeSensitiveCommands, string $clientId, Context $context) + public function __construct(array $observeEvents, array $ignoreCommands, private bool $observeSensitiveCommands, private string $clientId, private Context $context) { assertNotEmpty($observeEvents); @@ -132,10 +125,6 @@ public function __construct(array $observeEvents, array $ignoreCommands, bool $o assertIsString($command); $this->ignoreCommands[$command] = 1; } - - $this->observeSensitiveCommands = $observeSensitiveCommands; - $this->clientId = $clientId; - $this->context = $context; } /** @see https://php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandfailed.php */ @@ -223,23 +212,16 @@ public function assert(array $expectedEvents, bool $ignoreExtraEvents): void } } - private function assertEvent($actual, stdClass $expected, string $message) + private function assertEvent($actual, stdClass $expected, string $message): void { assertIsObject($actual); - switch (get_class($actual)) { - case CommandStartedEvent::class: - return $this->assertCommandStartedEvent($actual, $expected, $message); - - case CommandSucceededEvent::class: - return $this->assertCommandSucceededEvent($actual, $expected, $message); - - case CommandFailedEvent::class: - return $this->assertCommandFailedEvent($actual, $expected, $message); - - default: - Assert::fail($message . ': Unsupported event type: ' . get_class($actual)); - } + match ($actual::class) { + CommandStartedEvent::class => $this->assertCommandStartedEvent($actual, $expected, $message), + CommandSucceededEvent::class => $this->assertCommandSucceededEvent($actual, $expected, $message), + CommandFailedEvent::class => $this->assertCommandFailedEvent($actual, $expected, $message), + default => Assert::fail($message . ': Unsupported event type: ' . $actual::class) + }; } private function assertCommandStartedEvent(CommandStartedEvent $actual, stdClass $expected, string $message): void @@ -329,8 +311,7 @@ private function assertCommandFailedEvent(CommandFailedEvent $actual, stdClass $ } } - /** @param CommandStartedEvent|CommandSucceededEvent|CommandFailedEvent $event */ - private function handleEvent($event): void + private function handleEvent(CommandStartedEvent|CommandSucceededEvent|CommandFailedEvent $event): void { if (! $this->context->isActiveClient($this->clientId)) { return; @@ -340,7 +321,7 @@ private function handleEvent($event): void return; } - if (! isset($this->observeEvents[get_class($event)])) { + if (! isset($this->observeEvents[$event::class])) { return; } @@ -355,8 +336,7 @@ private function handleEvent($event): void $this->actualEvents[] = $event; } - /** @param CommandStartedEvent|CommandSucceededEvent|CommandFailedEvent $event */ - private function isSensitiveCommand($event): bool + private function isSensitiveCommand(CommandStartedEvent|CommandSucceededEvent|CommandFailedEvent $event): bool { if (isset(self::$sensitiveCommands[$event->getCommandName()])) { return true; diff --git a/tests/UnifiedSpecTests/ExpectedError.php b/tests/UnifiedSpecTests/ExpectedError.php index af28b0ece..52fc1f91c 100644 --- a/tests/UnifiedSpecTests/ExpectedError.php +++ b/tests/UnifiedSpecTests/ExpectedError.php @@ -13,7 +13,6 @@ use stdClass; use Throwable; -use function get_class; use function PHPUnit\Framework\assertArrayHasKey; use function PHPUnit\Framework\assertContainsOnly; use function PHPUnit\Framework\assertCount; @@ -128,7 +127,7 @@ public function __construct(?stdClass $o, EntityMap $entityMap) public function assert(?Throwable $e = null): void { if (! $this->isError && $e !== null) { - Assert::fail(sprintf("Operation threw unexpected %s: %s\n%s", get_class($e), $e->getMessage(), $e->getTraceAsString())); + Assert::fail(sprintf("Operation threw unexpected %s: %s\n%s", $e::class, $e->getMessage(), $e->getTraceAsString())); } if (! $this->isError) { diff --git a/tests/UnifiedSpecTests/ExpectedResult.php b/tests/UnifiedSpecTests/ExpectedResult.php index 88166eea5..5edc6e3ce 100644 --- a/tests/UnifiedSpecTests/ExpectedResult.php +++ b/tests/UnifiedSpecTests/ExpectedResult.php @@ -19,21 +19,18 @@ final class ExpectedResult { private ?Matches $constraint = null; - private EntityMap $entityMap; - /** * ID of the entity yielding the result. This is mainly used to associate * entities with a root client for collation of observed events. */ private ?string $yieldingEntityId = null; - public function __construct(stdClass $o, EntityMap $entityMap, ?string $yieldingEntityId = null) + public function __construct(stdClass $o, private EntityMap $entityMap, ?string $yieldingEntityId = null) { if (property_exists($o, 'expectResult')) { $this->constraint = new Matches($o->expectResult, $entityMap); } - $this->entityMap = $entityMap; $this->yieldingEntityId = $yieldingEntityId; } diff --git a/tests/UnifiedSpecTests/Loop.php b/tests/UnifiedSpecTests/Loop.php index 69c0b9a79..62848596e 100644 --- a/tests/UnifiedSpecTests/Loop.php +++ b/tests/UnifiedSpecTests/Loop.php @@ -26,10 +26,6 @@ final class Loop private static int $sleepUsecBetweenIterations = 0; - private Context $context; - - private array $operations = []; - private ?BSONArray $errorList = null; private ?BSONArray $failureList = null; @@ -38,13 +34,10 @@ final class Loop private string $numIterationsEntityId; - public function __construct(array $operations, Context $context, array $options = []) + public function __construct(private array $operations, private Context $context, array $options = []) { assertContainsOnly(Operation::class, $operations); - $this->operations = $operations; - $this->context = $context; - foreach (['storeErrorsAsEntity', 'storeFailuresAsEntity', 'storeSuccessesAsEntity', 'storeIterationsAsEntity'] as $option) { if (array_key_exists($option, $options)) { assertIsString($options[$option]); diff --git a/tests/UnifiedSpecTests/Operation.php b/tests/UnifiedSpecTests/Operation.php index 83c6a4924..1287e56c2 100644 --- a/tests/UnifiedSpecTests/Operation.php +++ b/tests/UnifiedSpecTests/Operation.php @@ -32,7 +32,6 @@ use function current; use function fopen; use function fwrite; -use function get_class; use function hex2bin; use function iterator_to_array; use function key; @@ -75,8 +74,6 @@ final class Operation private array $arguments = []; - private Context $context; - private EntityMap $entityMap; private ExpectedError $expectError; @@ -87,9 +84,8 @@ final class Operation private ?string $saveResultAsEntity = null; - public function __construct(stdClass $o, Context $context) + public function __construct(stdClass $o, private Context $context) { - $this->context = $context; $this->entityMap = $context->getEntityMap(); assertIsString($o->name); @@ -173,7 +169,7 @@ private function execute() $this->context->setActiveClient($this->entityMap->getRootClientIdOf($this->object)); - switch (get_class($object)) { + switch ($object::class) { case Client::class: $result = $this->executeForClient($object); break; @@ -199,7 +195,7 @@ private function execute() $result = $this->executeForBucket($object); break; default: - Assert::fail('Unsupported entity type: ' . get_class($object)); + Assert::fail('Unsupported entity type: ' . $object::class); } return $result; diff --git a/tests/UnifiedSpecTests/ServerParameterHelper.php b/tests/UnifiedSpecTests/ServerParameterHelper.php index 8f716fa67..b0afd5684 100644 --- a/tests/UnifiedSpecTests/ServerParameterHelper.php +++ b/tests/UnifiedSpecTests/ServerParameterHelper.php @@ -10,8 +10,6 @@ final class ServerParameterHelper { - private Client $client; - /** @var array */ private array $parameters = []; @@ -19,13 +17,11 @@ final class ServerParameterHelper private bool $allParametersFetched = false; - public function __construct(Client $client) + public function __construct(private Client $client) { - $this->client = $client; } - /** @return mixed */ - public function __get(string $parameter) + public function __get(string $parameter): mixed { if (! array_key_exists($parameter, $this->parameters)) { $this->fetchParameter($parameter); @@ -68,7 +64,7 @@ private function fetchAllParameters(): void $this->parameters = $cursor->toArray()[0]; $this->allParametersFetched = true; - } catch (CommandException $e) { + } catch (CommandException) { $this->fetchAllParametersFailed = true; } } diff --git a/tests/UnifiedSpecTests/UnifiedTestCase.php b/tests/UnifiedSpecTests/UnifiedTestCase.php index 3df7e842e..435400fb5 100644 --- a/tests/UnifiedSpecTests/UnifiedTestCase.php +++ b/tests/UnifiedSpecTests/UnifiedTestCase.php @@ -24,23 +24,8 @@ */ final class UnifiedTestCase implements IteratorAggregate { - private stdClass $test; - - private string $schemaVersion; - - private ?array $runOnRequirements = null; - - private ?array $createEntities = null; - - private ?array $initialData = null; - - private function __construct(stdClass $test, string $schemaVersion, ?array $runOnRequirements = null, ?array $createEntities = null, ?array $initialData = null) + private function __construct(private stdClass $test, private string $schemaVersion, private ?array $runOnRequirements = null, private ?array $createEntities = null, private ?array $initialData = null) { - $this->test = $test; - $this->schemaVersion = $schemaVersion; - $this->runOnRequirements = $runOnRequirements; - $this->createEntities = $createEntities; - $this->initialData = $initialData; } /** diff --git a/tests/UnifiedSpecTests/UnifiedTestRunner.php b/tests/UnifiedSpecTests/UnifiedTestRunner.php index 644cbcaaa..605eb2cac 100644 --- a/tests/UnifiedSpecTests/UnifiedTestRunner.php +++ b/tests/UnifiedSpecTests/UnifiedTestRunner.php @@ -68,8 +68,6 @@ final class UnifiedTestRunner private Client $internalClient; - private string $internalClientUri; - private bool $allowKillAllSessions = true; private ?EntityMap $entityMap = null; @@ -81,10 +79,9 @@ final class UnifiedTestRunner private ServerParameterHelper $serverParameterHelper; - public function __construct(string $internalClientUri) + public function __construct(private string $internalClientUri) { $this->internalClient = FunctionalTestCase::createTestClient($internalClientUri); - $this->internalClientUri = $internalClientUri; /* Atlas prohibits killAllSessions. Inspect the connection string to * determine if we should avoid calling killAllSessions(). This does From bc7618d8cdb0b0d0d146db0dfc0032cfcef28eb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 9 Sep 2024 12:58:34 +0200 Subject: [PATCH 68/95] Remove composer constraints already imposed by the main package --- generator/README.md | 2 +- generator/composer.json | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/generator/README.md b/generator/README.md index a58cbeb9c..5b6449543 100644 --- a/generator/README.md +++ b/generator/README.md @@ -1,7 +1,7 @@ # Code Generator for MongoDB PHP Library This subproject is used to generate the code that is committed to the repository. -The `generator` directory is not included in `mongodb/builder` package and is not installed by Composer. +The `generator` directory is not included in `mongodb/mongodb` package and is not installed by Composer. ## Contributing diff --git a/generator/composer.json b/generator/composer.json index 355e8803f..2deb4ae95 100644 --- a/generator/composer.json +++ b/generator/composer.json @@ -13,14 +13,12 @@ "symfony/polyfill-php81": "*" }, "require": { - "php": ">=8.1", - "ext-mongodb": "^1.17.0", "mongodb/mongodb": "@dev", "nette/php-generator": "^4.1.5", "nikic/php-parser": "^5", - "symfony/console": "^6.3|^7.0", - "symfony/finder": "^6.3|^7.0", - "symfony/yaml": "^6.3|^7.0" + "symfony/console": "^7", + "symfony/finder": "^7", + "symfony/yaml": "^7" }, "license": "Apache-2.0", "autoload": { From eafad0580dc4638000c195e8c0f231964616beeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 9 Sep 2024 13:20:09 +0200 Subject: [PATCH 69/95] Skip Pedentry method sort for generated files --- src/Builder/Variable.php | 80 ++++++++++++++++++++-------------------- tests/PedantryTest.php | 11 ++++++ 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/Builder/Variable.php b/src/Builder/Variable.php index 5a9473061..6299ffd44 100644 --- a/src/Builder/Variable.php +++ b/src/Builder/Variable.php @@ -18,18 +18,6 @@ */ final class Variable { - /** - * A variable that returns the current datetime value. - * NOW returns the same value for all members of the deployment and remains the same throughout all stages of the - * aggregation pipeline. - * - * New in MongoDB 4.2. - */ - public static function now(): ResolvesToDate - { - return new Expression\Variable('NOW'); - } - /** * A variable that returns the current timestamp value. * CLUSTER_TIME is only available on replica sets and sharded clusters. @@ -43,15 +31,6 @@ public static function clusterTime(): ResolvesToTimestamp return new Expression\Variable('CLUSTER_TIME'); } - /** - * References the root document, i.e. the top-level document, currently being processed in the aggregation pipeline - * stage. - */ - public static function root(): ResolvesToObject - { - return new Expression\Variable('ROOT'); - } - /** * References the start of the field path being processed in the aggregation pipeline stage. * Unless documented otherwise, all stages start with CURRENT the same as ROOT. @@ -64,29 +43,43 @@ public static function current(string $fieldPath = ''): ResolvesToAny } /** - * A variable which evaluates to the missing value. Allows for the conditional exclusion of fields. In a $project, - * a field set to the variable REMOVE is excluded from the output. - * Can be used with $cond operator for conditionally exclude fields. + * One of the allowed results of a $redact expression. * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#std-label-remove-example + * $redact returns the fields at the current document level, excluding embedded documents. To include embedded + * documents and embedded documents within arrays, apply the $cond expression to the embedded documents to determine + * access for these embedded documents. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/#mongodb-pipeline-pipe.-redact */ - public static function remove(): ResolvesToAny + public static function descend(): ExpressionInterface { - return new Expression\Variable('REMOVE'); + return new Expression\Variable('DESCEND'); } /** * One of the allowed results of a $redact expression. * - * $redact returns the fields at the current document level, excluding embedded documents. To include embedded - * documents and embedded documents within arrays, apply the $cond expression to the embedded documents to determine - * access for these embedded documents. + * $redact returns or keeps all fields at this current document/embedded document level, without further inspection + * of the fields at this level. This applies even if the included field contains embedded documents that may have + * different access levels. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/#mongodb-pipeline-pipe.-redact */ - public static function descend(): ExpressionInterface + public static function keep(): ExpressionInterface { - return new Expression\Variable('DESCEND'); + return new Expression\Variable('KEEP'); + } + + /** + * A variable that returns the current datetime value. + * NOW returns the same value for all members of the deployment and remains the same throughout all stages of the + * aggregation pipeline. + * + * New in MongoDB 4.2. + */ + public static function now(): ResolvesToDate + { + return new Expression\Variable('NOW'); } /** @@ -104,17 +97,24 @@ public static function prune(): ExpressionInterface } /** - * One of the allowed results of a $redact expression. - * - * $redact returns or keeps all fields at this current document/embedded document level, without further inspection - * of the fields at this level. This applies even if the included field contains embedded documents that may have - * different access levels. + * A variable which evaluates to the missing value. Allows for the conditional exclusion of fields. In a $project, + * a field set to the variable REMOVE is excluded from the output. + * Can be used with $cond operator for conditionally exclude fields. * - * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/redact/#mongodb-pipeline-pipe.-redact + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/project/#std-label-remove-example */ - public static function keep(): ExpressionInterface + public static function remove(): ResolvesToAny { - return new Expression\Variable('KEEP'); + return new Expression\Variable('REMOVE'); + } + + /** + * References the root document, i.e. the top-level document, currently being processed in the aggregation pipeline + * stage. + */ + public static function root(): ResolvesToObject + { + return new Expression\Variable('ROOT'); } /** diff --git a/tests/PedantryTest.php b/tests/PedantryTest.php index 29df17a19..0faf67c54 100644 --- a/tests/PedantryTest.php +++ b/tests/PedantryTest.php @@ -2,6 +2,7 @@ namespace MongoDB\Tests; +use MongoDB; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use ReflectionClass; @@ -10,6 +11,7 @@ use function array_filter; use function array_map; +use function in_array; use function realpath; use function str_contains; use function str_replace; @@ -25,6 +27,11 @@ */ class PedantryTest extends TestCase { + private const SKIPPED_CLASSES = [ + // Generated + MongoDB\Builder\Stage\FluentFactoryTrait::class, + ]; + /** @dataProvider provideProjectClassNames */ public function testMethodsAreOrderedAlphabeticallyByVisibility($className): void { @@ -74,6 +81,10 @@ public function provideProjectClassNames() } $className = 'MongoDB\\' . str_replace(DIRECTORY_SEPARATOR, '\\', substr($file->getRealPath(), strlen($srcDir) + 1, -4)); + if (in_array($className, self::SKIPPED_CLASSES)) { + continue; + } + $classNames[$className][] = $className; } From 4e66bbcd28f30decca5563e08de5e0fae2c3a538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 9 Sep 2024 14:57:30 +0200 Subject: [PATCH 70/95] Replace composer package mongodb/builder --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index a6dd35b5a..9f2f2020f 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,9 @@ "symfony/phpunit-bridge": "^5.2", "vimeo/psalm": "^5.13" }, + "replace": { + "mongodb/builder": "*" + }, "autoload": { "psr-4": { "MongoDB\\": "src/" }, "files": [ "src/functions.php" ] From 7a19e9c6a9e4f045e11145578db3db2fe903095d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 9 Sep 2024 14:59:42 +0200 Subject: [PATCH 71/95] Check generated files are up-to-date --- .github/actions/setup/action.yml | 4 +++ .github/workflows/generator.yml | 42 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 .github/workflows/generator.yml diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index ba987c7e5..da58ba8a5 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -11,6 +11,9 @@ inputs: description: "INI values to pass along to setup-php action" required: false default: "" + working-directory: + description: "The directory where composer.json is located, if it is not in the repository root." + required: false runs: using: composite @@ -49,3 +52,4 @@ runs: # Revert when psalm supports PHP 8.4 # composer-options: "--no-suggest" composer-options: "--no-suggest ${{ inputs.php-version == '8.4' && '--ignore-platform-req=php+' || '' }}" + working-directory: "${{ inputs.working-directory }}" diff --git a/.github/workflows/generator.yml b/.github/workflows/generator.yml new file mode 100644 index 000000000..59d8eb186 --- /dev/null +++ b/.github/workflows/generator.yml @@ -0,0 +1,42 @@ +name: "Generator" + +on: + merge_group: + pull_request: + branches: + - "v*.*" + - "master" + - "feature/*" + push: + branches: + - "v*.*" + - "master" + - "feature/*" + +env: + PHP_VERSION: "8.2" + # TODO: change to "stable" once 1.20.0 is released + # DRIVER_VERSION: "stable" + DRIVER_VERSION: "mongodb/mongo-php-driver@v1.20" + +jobs: + psalm: + name: "Diff check" + runs-on: "ubuntu-22.04" + + steps: + - name: "Checkout" + uses: "actions/checkout@v4" + + - name: "Setup" + uses: "./.github/actions/setup" + with: + php-version: ${{ env.PHP_VERSION }} + driver-version: ${{ env.DRIVER_VERSION }} + working-directory: "generator" + + - name: "Run Generator" + run: "generator/generate" + + - name: "Check file diff" + run: git add . -N && git diff --exit-code From 17d2ba89c4d225fcdf20172b8ca8fa93c9bc089f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 9 Sep 2024 15:09:32 +0200 Subject: [PATCH 72/95] Exclude rector.php from the artifact --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index c208d24ba..7f54ebe53 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,6 +15,7 @@ phpunit.evergreen.xml export-ignore phpunit.xml.dist export-ignore psalm.xml.dist export-ignore psalm-baseline.xml export-ignore +rector.php export-ignore # Prevent generated build files from showing diffs in pull requests .evergreen/config/generated/** linguist-generated=true From be2df01eb8fa3d3071d4dc1889ea75a14ede93bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 10 Sep 2024 17:47:07 +0200 Subject: [PATCH 73/95] PHPLIB-1505 Add driver option "builderEncoder" (#1382) --- psalm-baseline.xml | 15 ++- src/Client.php | 24 +++- src/Collection.php | 18 +++ src/Database.php | 18 +++ tests/ClientTest.php | 22 +++- tests/Collection/CollectionFunctionalTest.php | 4 + tests/TestCase.php | 114 +++++++++--------- 7 files changed, 151 insertions(+), 64 deletions(-) diff --git a/psalm-baseline.xml b/psalm-baseline.xml index deb61756f..00c11f728 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,5 +1,5 @@ - + @@ -195,6 +195,9 @@ + + + @@ -216,6 +219,11 @@ + + + + + @@ -228,6 +236,11 @@ + + + + + diff --git a/src/Client.php b/src/Client.php index 0b9282cf0..5b02aa44c 100644 --- a/src/Client.php +++ b/src/Client.php @@ -19,6 +19,10 @@ use Composer\InstalledVersions; use Iterator; +use MongoDB\BSON\Document; +use MongoDB\BSON\PackedArray; +use MongoDB\Builder\BuilderEncoder; +use MongoDB\Codec\Encoder; use MongoDB\Driver\ClientEncryption; use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; @@ -38,8 +42,10 @@ use MongoDB\Operation\ListDatabaseNames; use MongoDB\Operation\ListDatabases; use MongoDB\Operation\Watch; +use stdClass; use Throwable; +use function array_diff_key; use function is_array; use function is_string; @@ -67,6 +73,9 @@ class Client private array $typeMap; + /** @psalm-var Encoder */ + private readonly Encoder $builderEncoder; + private WriteConcern $writeConcern; /** @@ -78,6 +87,9 @@ class Client * * Supported driver-specific options: * + * * builderEncoder (MongoDB\Builder\Encoder): Encoder for query and + * aggregation builders. If not given, the default encoder will be used. + * * * typeMap (array): Default type map for cursors and BSON documents. * * Other options are documented in MongoDB\Driver\Manager::__construct(). @@ -108,12 +120,17 @@ public function __construct(?string $uri = null, array $uriOptions = [], array $ } } + if (isset($driverOptions['builderEncoder']) && ! $driverOptions['builderEncoder'] instanceof Encoder) { + throw InvalidArgumentException::invalidType('"builderEncoder" option', $driverOptions['builderEncoder'], Encoder::class); + } + $driverOptions['driver'] = $this->mergeDriverInfo($driverOptions['driver'] ?? []); $this->uri = $uri ?? self::DEFAULT_URI; + $this->builderEncoder = $driverOptions['builderEncoder'] ?? new BuilderEncoder(); $this->typeMap = $driverOptions['typeMap']; - unset($driverOptions['typeMap']); + $driverOptions = array_diff_key($driverOptions, ['builderEncoder' => 1, 'typeMap' => 1]); $this->manager = new Manager($uri, $uriOptions, $driverOptions); $this->readConcern = $this->manager->getReadConcern(); @@ -133,6 +150,7 @@ public function __debugInfo() 'manager' => $this->manager, 'uri' => $this->uri, 'typeMap' => $this->typeMap, + 'builderEncoder' => $this->builderEncoder, 'writeConcern' => $this->writeConcern, ]; } @@ -329,7 +347,7 @@ final public function removeSubscriber(Subscriber $subscriber): void */ public function selectCollection(string $databaseName, string $collectionName, array $options = []) { - $options += ['typeMap' => $this->typeMap]; + $options += ['typeMap' => $this->typeMap, 'builderEncoder' => $this->builderEncoder]; return new Collection($this->manager, $databaseName, $collectionName, $options); } @@ -345,7 +363,7 @@ public function selectCollection(string $databaseName, string $collectionName, a */ public function selectDatabase(string $databaseName, array $options = []) { - $options += ['typeMap' => $this->typeMap]; + $options += ['typeMap' => $this->typeMap, 'builderEncoder' => $this->builderEncoder]; return new Database($this->manager, $databaseName, $options); } diff --git a/src/Collection.php b/src/Collection.php index 0118bc335..28133ff55 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -19,8 +19,12 @@ use Countable; use Iterator; +use MongoDB\BSON\Document; use MongoDB\BSON\JavascriptInterface; +use MongoDB\BSON\PackedArray; +use MongoDB\Builder\BuilderEncoder; use MongoDB\Codec\DocumentCodec; +use MongoDB\Codec\Encoder; use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Manager; @@ -66,6 +70,7 @@ use MongoDB\Operation\UpdateOne; use MongoDB\Operation\UpdateSearchIndex; use MongoDB\Operation\Watch; +use stdClass; use function array_diff_key; use function array_intersect_key; @@ -84,6 +89,9 @@ class Collection private const WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE = 8; + /** @psalm-var Encoder */ + private readonly Encoder $builderEncoder; + private ?DocumentCodec $codec = null; private ReadConcern $readConcern; @@ -102,6 +110,9 @@ class Collection * * Supported options: * + * * builderEncoder (MongoDB\Builder\Encoder): Encoder for query and + * aggregation builders. If not given, the default encoder will be used. + * * * codec (MongoDB\Codec\DocumentCodec): Codec used to decode documents * from BSON to PHP objects. * @@ -134,6 +145,10 @@ public function __construct(private Manager $manager, private string $databaseNa throw new InvalidArgumentException('$collectionName is invalid: ' . $collectionName); } + if (isset($options['builderEncoder']) && ! $options['builderEncoder'] instanceof Encoder) { + throw InvalidArgumentException::invalidType('"builderEncoder" option', $options['builderEncoder'], Encoder::class); + } + if (isset($options['codec']) && ! $options['codec'] instanceof DocumentCodec) { throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class); } @@ -154,6 +169,7 @@ public function __construct(private Manager $manager, private string $databaseNa throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } + $this->builderEncoder = $options['builderEncoder'] ?? new BuilderEncoder(); $this->codec = $options['codec'] ?? null; $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern(); $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference(); @@ -170,6 +186,7 @@ public function __construct(private Manager $manager, private string $databaseNa public function __debugInfo() { return [ + 'builderEncoder' => $this->builderEncoder, 'codec' => $this->codec, 'collectionName' => $this->collectionName, 'databaseName' => $this->databaseName, @@ -1084,6 +1101,7 @@ public function watch(array $pipeline = [], array $options = []) public function withOptions(array $options = []) { $options += [ + 'builderEncoder' => $this->builderEncoder, 'codec' => $this->codec, 'readConcern' => $this->readConcern, 'readPreference' => $this->readPreference, diff --git a/src/Database.php b/src/Database.php index d466dcb6a..8b7742b70 100644 --- a/src/Database.php +++ b/src/Database.php @@ -18,6 +18,10 @@ namespace MongoDB; use Iterator; +use MongoDB\BSON\Document; +use MongoDB\BSON\PackedArray; +use MongoDB\Builder\BuilderEncoder; +use MongoDB\Codec\Encoder; use MongoDB\Driver\ClientEncryption; use MongoDB\Driver\Cursor; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; @@ -45,6 +49,7 @@ use MongoDB\Operation\ModifyCollection; use MongoDB\Operation\RenameCollection; use MongoDB\Operation\Watch; +use stdClass; use Throwable; use Traversable; @@ -61,6 +66,9 @@ class Database private const WIRE_VERSION_FOR_READ_CONCERN_WITH_WRITE_STAGE = 8; + /** @psalm-var Encoder */ + private readonly Encoder $builderEncoder; + private ReadConcern $readConcern; private ReadPreference $readPreference; @@ -77,6 +85,9 @@ class Database * * Supported options: * + * * builderEncoder (MongoDB\Builder\Encoder): Encoder for query and + * aggregation builders. If not given, the default encoder will be used. + * * * readConcern (MongoDB\Driver\ReadConcern): The default read concern to * use for database operations and selected collections. Defaults to the * Manager's read concern. @@ -102,6 +113,10 @@ public function __construct(private Manager $manager, private string $databaseNa throw new InvalidArgumentException('$databaseName is invalid: ' . $databaseName); } + if (isset($options['builderEncoder']) && ! $options['builderEncoder'] instanceof Encoder) { + throw InvalidArgumentException::invalidType('"builderEncoder" option', $options['builderEncoder'], Encoder::class); + } + if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } @@ -118,6 +133,7 @@ public function __construct(private Manager $manager, private string $databaseNa throw InvalidArgumentException::invalidType('"writeConcern" option', $options['writeConcern'], WriteConcern::class); } + $this->builderEncoder = $options['builderEncoder'] ?? new BuilderEncoder(); $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern(); $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference(); $this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP; @@ -133,6 +149,7 @@ public function __construct(private Manager $manager, private string $databaseNa public function __debugInfo() { return [ + 'builderEncoder' => $this->builderEncoder, 'databaseName' => $this->databaseName, 'manager' => $this->manager, 'readConcern' => $this->readConcern, @@ -553,6 +570,7 @@ public function renameCollection(string $fromCollectionName, string $toCollectio public function selectCollection(string $collectionName, array $options = []) { $options += [ + 'builderEncoder' => $this->builderEncoder, 'readConcern' => $this->readConcern, 'readPreference' => $this->readPreference, 'typeMap' => $this->typeMap, diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 04855d479..584714c98 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -3,6 +3,7 @@ namespace MongoDB\Tests; use MongoDB\Client; +use MongoDB\Codec\Encoder; use MongoDB\Driver\ClientEncryption; use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException; use MongoDB\Driver\ReadConcern; @@ -45,6 +46,10 @@ public function provideInvalidConstructorDriverOptions() { $options = []; + foreach ($this->getInvalidObjectValues() as $value) { + $options[][] = ['builderEncoder' => $value]; + } + foreach ($this->getInvalidArrayValues(true) as $value) { $options[][] = ['typeMap' => $value]; } @@ -85,6 +90,7 @@ public function testSelectCollectionInheritsOptions(): void ]; $driverOptions = [ + 'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class), 'typeMap' => ['root' => 'array'], ]; @@ -92,6 +98,7 @@ public function testSelectCollectionInheritsOptions(): void $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); $debug = $collection->__debugInfo(); + $this->assertSame($builderEncoder, $debug['builderEncoder']); $this->assertInstanceOf(ReadConcern::class, $debug['readConcern']); $this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel()); $this->assertInstanceOf(ReadPreference::class, $debug['readPreference']); @@ -105,6 +112,7 @@ public function testSelectCollectionInheritsOptions(): void public function testSelectCollectionPassesOptions(): void { $collectionOptions = [ + 'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class), 'readConcern' => new ReadConcern(ReadConcern::LOCAL), 'readPreference' => new ReadPreference(ReadPreference::SECONDARY_PREFERRED), 'typeMap' => ['root' => 'array'], @@ -115,6 +123,7 @@ public function testSelectCollectionPassesOptions(): void $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName(), $collectionOptions); $debug = $collection->__debugInfo(); + $this->assertSame($builderEncoder, $debug['builderEncoder']); $this->assertInstanceOf(ReadConcern::class, $debug['readConcern']); $this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel()); $this->assertInstanceOf(ReadPreference::class, $debug['readPreference']); @@ -129,11 +138,19 @@ public function testGetSelectsDatabaseAndInheritsOptions(): void { $uriOptions = ['w' => WriteConcern::MAJORITY]; - $client = new Client(static::getUri(), $uriOptions); + $driverOptions = [ + 'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class), + 'typeMap' => ['root' => 'array'], + ]; + + $client = new Client(static::getUri(), $uriOptions, $driverOptions); $database = $client->{$this->getDatabaseName()}; $debug = $database->__debugInfo(); + $this->assertSame($builderEncoder, $debug['builderEncoder']); $this->assertSame($this->getDatabaseName(), $debug['databaseName']); + $this->assertIsArray($debug['typeMap']); + $this->assertSame(['root' => 'array'], $debug['typeMap']); $this->assertInstanceOf(WriteConcern::class, $debug['writeConcern']); $this->assertSame(WriteConcern::MAJORITY, $debug['writeConcern']->getW()); } @@ -147,6 +164,7 @@ public function testSelectDatabaseInheritsOptions(): void ]; $driverOptions = [ + 'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class), 'typeMap' => ['root' => 'array'], ]; @@ -154,6 +172,7 @@ public function testSelectDatabaseInheritsOptions(): void $database = $client->selectDatabase($this->getDatabaseName()); $debug = $database->__debugInfo(); + $this->assertSame($builderEncoder, $debug['builderEncoder']); $this->assertInstanceOf(ReadConcern::class, $debug['readConcern']); $this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel()); $this->assertInstanceOf(ReadPreference::class, $debug['readPreference']); @@ -167,6 +186,7 @@ public function testSelectDatabaseInheritsOptions(): void public function testSelectDatabasePassesOptions(): void { $databaseOptions = [ + 'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class), 'readConcern' => new ReadConcern(ReadConcern::LOCAL), 'readPreference' => new ReadPreference(ReadPreference::SECONDARY_PREFERRED), 'typeMap' => ['root' => 'array'], diff --git a/tests/Collection/CollectionFunctionalTest.php b/tests/Collection/CollectionFunctionalTest.php index d95b07737..e4f6c649a 100644 --- a/tests/Collection/CollectionFunctionalTest.php +++ b/tests/Collection/CollectionFunctionalTest.php @@ -4,6 +4,7 @@ use Closure; use MongoDB\BSON\Javascript; +use MongoDB\Codec\Encoder; use MongoDB\Collection; use MongoDB\Database; use MongoDB\Driver\BulkWrite; @@ -66,6 +67,7 @@ public function testConstructorOptionTypeChecks(array $options): void public function provideInvalidConstructorOptions(): array { return $this->createOptionDataProvider([ + 'builderEncoder' => $this->getInvalidObjectValues(), 'codec' => $this->getInvalidDocumentCodecValues(), 'readConcern' => $this->getInvalidReadConcernValues(), 'readPreference' => $this->getInvalidReadPreferenceValues(), @@ -396,6 +398,7 @@ public function testWithOptionsInheritsOptions(): void public function testWithOptionsPassesOptions(): void { $collectionOptions = [ + 'builderEncoder' => $builderEncoder = $this->createMock(Encoder::class), 'readConcern' => new ReadConcern(ReadConcern::LOCAL), 'readPreference' => new ReadPreference(ReadPreference::SECONDARY_PREFERRED), 'typeMap' => ['root' => 'array'], @@ -405,6 +408,7 @@ public function testWithOptionsPassesOptions(): void $clone = $this->collection->withOptions($collectionOptions); $debug = $clone->__debugInfo(); + $this->assertSame($builderEncoder, $debug['builderEncoder']); $this->assertInstanceOf(ReadConcern::class, $debug['readConcern']); $this->assertSame(ReadConcern::LOCAL, $debug['readConcern']->getLevel()); $this->assertInstanceOf(ReadPreference::class, $debug['readPreference']); diff --git a/tests/TestCase.php b/tests/TestCase.php index 287dc0b59..53e46fb82 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -18,7 +18,6 @@ use Traversable; use function array_map; -use function array_merge; use function array_values; use function call_user_func; use function get_debug_type; @@ -209,7 +208,7 @@ protected function getCollectionName(): string */ protected function getInvalidArrayValues(bool $includeNull = false): array { - return array_merge([123, 3.14, 'foo', true, new stdClass()], $includeNull ? [null] : []); + return [123, 3.14, 'foo', true, new stdClass(), ...($includeNull ? [null] : [])]; } /** @@ -217,7 +216,7 @@ protected function getInvalidArrayValues(bool $includeNull = false): array */ protected function getInvalidBooleanValues(bool $includeNull = false): array { - return array_merge([123, 3.14, 'foo', [], new stdClass()], $includeNull ? [null] : []); + return [123, 3.14, 'foo', [], new stdClass(), ...($includeNull ? [null] : [])]; } /** @@ -225,7 +224,12 @@ protected function getInvalidBooleanValues(bool $includeNull = false): array */ protected function getInvalidDocumentValues(bool $includeNull = false): array { - return array_merge([123, 3.14, 'foo', true, PackedArray::fromPHP([])], $includeNull ? [null] : []); + return [123, 3.14, 'foo', true, PackedArray::fromPHP([]), ...($includeNull ? [null] : [])]; + } + + protected function getInvalidObjectValues(bool $includeNull = false): array + { + return [123, 3.14, 'foo', true, [], new stdClass(), ...($includeNull ? [null] : [])]; } protected function getInvalidDocumentCodecValues(): array @@ -246,7 +250,7 @@ protected function getInvalidHintValues() */ protected function getInvalidIntegerValues(bool $includeNull = false): array { - return array_merge([3.14, 'foo', true, [], new stdClass()], $includeNull ? [null] : []); + return [3.14, 'foo', true, [], new stdClass(), ...($includeNull ? [null] : [])]; } /** @@ -254,19 +258,17 @@ protected function getInvalidIntegerValues(bool $includeNull = false): array */ protected function getInvalidReadConcernValues(bool $includeNull = false): array { - return array_merge( - [ - 123, - 3.14, - 'foo', - true, - [], - new stdClass(), - new ReadPreference(ReadPreference::PRIMARY), - new WriteConcern(1), - ], - $includeNull ? ['null' => null] : [], - ); + return [ + 123, + 3.14, + 'foo', + true, + [], + new stdClass(), + new ReadPreference(ReadPreference::PRIMARY), + new WriteConcern(1), + ...($includeNull ? ['null' => null] : []), + ]; } /** @@ -274,19 +276,17 @@ protected function getInvalidReadConcernValues(bool $includeNull = false): array */ protected function getInvalidReadPreferenceValues(bool $includeNull = false): array { - return array_merge( - [ - 123, - 3.14, - 'foo', - true, - [], - new stdClass(), - new ReadConcern(), - new WriteConcern(1), - ], - $includeNull ? ['null' => null] : [], - ); + return [ + 123, + 3.14, + 'foo', + true, + [], + new stdClass(), + new ReadConcern(), + new WriteConcern(1), + ...($includeNull ? ['null' => null] : []), + ]; } /** @@ -294,20 +294,18 @@ protected function getInvalidReadPreferenceValues(bool $includeNull = false): ar */ protected function getInvalidSessionValues(bool $includeNull = false): array { - return array_merge( - [ - 123, - 3.14, - 'foo', - true, - [], - new stdClass(), - new ReadConcern(), - new ReadPreference(ReadPreference::PRIMARY), - new WriteConcern(1), - ], - $includeNull ? ['null' => null] : [], - ); + return [ + 123, + 3.14, + 'foo', + true, + [], + new stdClass(), + new ReadConcern(), + new ReadPreference(ReadPreference::PRIMARY), + new WriteConcern(1), + ...($includeNull ? ['null' => null] : []), + ]; } /** @@ -315,7 +313,7 @@ protected function getInvalidSessionValues(bool $includeNull = false): array */ protected function getInvalidStringValues(bool $includeNull = false): array { - return array_merge([123, 3.14, true, [], new stdClass()], $includeNull ? [null] : []); + return [123, 3.14, true, [], new stdClass(), ...($includeNull ? [null] : [])]; } /** @@ -323,19 +321,17 @@ protected function getInvalidStringValues(bool $includeNull = false): array */ protected function getInvalidWriteConcernValues(bool $includeNull = false): array { - return array_merge( - [ - 123, - 3.14, - 'foo', - true, - [], - new stdClass(), - new ReadConcern(), - new ReadPreference(ReadPreference::PRIMARY), - ], - $includeNull ? ['null' => null] : [], - ); + return [ + 123, + 3.14, + 'foo', + true, + [], + new stdClass(), + new ReadConcern(), + new ReadPreference(ReadPreference::PRIMARY), + ...($includeNull ? ['null' => null] : []), + ]; } /** From 5ebc5dc0bc3c5643b42d68e001bb797eebd36acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 11 Sep 2024 08:57:49 +0200 Subject: [PATCH 74/95] Fix optional parameter declared before required parameter (#1384) --- src/Operation/Aggregate.php | 2 +- src/Operation/Watch.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Operation/Aggregate.php b/src/Operation/Aggregate.php index ba6e1ea93..00d408c18 100644 --- a/src/Operation/Aggregate.php +++ b/src/Operation/Aggregate.php @@ -118,7 +118,7 @@ class Aggregate implements Executable, Explainable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(private string $databaseName, private ?string $collectionName = null, private array $pipeline, private array $options = []) + public function __construct(private string $databaseName, private ?string $collectionName, private array $pipeline, private array $options = []) { if (! is_pipeline($pipeline, true /* allowEmpty */)) { throw new InvalidArgumentException('$pipeline is not a valid aggregation pipeline'); diff --git a/src/Operation/Watch.php b/src/Operation/Watch.php index 1ae781d11..09f8b659b 100644 --- a/src/Operation/Watch.php +++ b/src/Operation/Watch.php @@ -187,7 +187,7 @@ class Watch implements Executable, /* @internal */ CommandSubscriber * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(private Manager $manager, ?string $databaseName, private ?string $collectionName = null, private array $pipeline, array $options = []) + public function __construct(private Manager $manager, ?string $databaseName, private ?string $collectionName, private array $pipeline, array $options = []) { if (isset($collectionName) && ! isset($databaseName)) { throw new InvalidArgumentException('$collectionName should also be null if $databaseName is null'); From 5da5c155a53f833eb8ea4b9f3a8bc516473049af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 11 Sep 2024 11:53:14 +0200 Subject: [PATCH 75/95] PHPLIB-1420 Integrate query builder for non-aggregation APIs (#1385) --- psalm-baseline.xml | 15 +- src/Collection.php | 21 ++ src/Operation/BulkWrite.php | 21 +- .../BuilderCollectionFunctionalTest.php | 250 ++++++++++++++++++ 4 files changed, 303 insertions(+), 4 deletions(-) create mode 100644 tests/Collection/BuilderCollectionFunctionalTest.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 00c11f728..f51206d0d 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -417,8 +417,9 @@ - - + + + @@ -448,6 +449,7 @@ + @@ -458,6 +460,10 @@ + + + + @@ -466,9 +472,14 @@ + + + + + diff --git a/src/Collection.php b/src/Collection.php index 28133ff55..7769b9a5b 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -262,6 +262,7 @@ public function aggregate(array $pipeline, array $options = []) */ public function bulkWrite(array $operations, array $options = []) { + $options = $this->inheritBuilderEncoder($options); $options = $this->inheritWriteOptions($options); $options = $this->inheritCodec($options); @@ -286,6 +287,7 @@ public function bulkWrite(array $operations, array $options = []) */ public function count(array|object $filter = [], array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritReadOptions($options); $operation = new Count($this->databaseName, $this->collectionName, $filter, $options); @@ -307,6 +309,7 @@ public function count(array|object $filter = [], array $options = []) */ public function countDocuments(array|object $filter = [], array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritReadOptions($options); $operation = new CountDocuments($this->databaseName, $this->collectionName, $filter, $options); @@ -444,6 +447,7 @@ public function createSearchIndexes(array $indexes, array $options = []): array */ public function deleteMany(array|object $filter, array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); $operation = new DeleteMany($this->databaseName, $this->collectionName, $filter, $options); @@ -465,6 +469,7 @@ public function deleteMany(array|object $filter, array $options = []) */ public function deleteOne(array|object $filter, array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); $operation = new DeleteOne($this->databaseName, $this->collectionName, $filter, $options); @@ -487,6 +492,7 @@ public function deleteOne(array|object $filter, array $options = []) */ public function distinct(string $fieldName, array|object $filter = [], array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritReadOptions($options); $options = $this->inheritTypeMap($options); @@ -645,6 +651,7 @@ public function explain(Explainable $explainable, array $options = []) */ public function find(array|object $filter = [], array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritReadOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -667,6 +674,7 @@ public function find(array|object $filter = [], array $options = []) */ public function findOne(array|object $filter = [], array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritReadOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -692,6 +700,7 @@ public function findOne(array|object $filter = [], array $options = []) */ public function findOneAndDelete(array|object $filter, array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -722,6 +731,7 @@ public function findOneAndDelete(array|object $filter, array $options = []) */ public function findOneAndReplace(array|object $filter, array|object $replacement, array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -752,6 +762,7 @@ public function findOneAndReplace(array|object $filter, array|object $replacemen */ public function findOneAndUpdate(array|object $filter, array|object $update, array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -1000,6 +1011,7 @@ public function rename(string $toCollectionName, ?string $toDatabaseName = null, */ public function replaceOne(array|object $filter, array|object $replacement, array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); $options = $this->inheritCodec($options); @@ -1023,6 +1035,8 @@ public function replaceOne(array|object $filter, array|object $replacement, arra */ public function updateMany(array|object $filter, array|object $update, array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); + $update = $this->builderEncoder->encodeIfSupported($update); $options = $this->inheritWriteOptions($options); $operation = new UpdateMany($this->databaseName, $this->collectionName, $filter, $update, $options); @@ -1045,6 +1059,8 @@ public function updateMany(array|object $filter, array|object $update, array $op */ public function updateOne(array|object $filter, array|object $update, array $options = []) { + $filter = $this->builderEncoder->encodeIfSupported($filter); + $update = $this->builderEncoder->encodeIfSupported($update); $options = $this->inheritWriteOptions($options); $operation = new UpdateOne($this->databaseName, $this->collectionName, $filter, $update, $options); @@ -1112,6 +1128,11 @@ public function withOptions(array $options = []) return new Collection($this->manager, $this->databaseName, $this->collectionName, $options); } + private function inheritBuilderEncoder(array $options): array + { + return ['builderEncoder' => $this->builderEncoder] + $options; + } + private function inheritCodec(array $options): array { // If the options contain a type map, don't inherit anything diff --git a/src/Operation/BulkWrite.php b/src/Operation/BulkWrite.php index 700ce3943..6da8a68b3 100644 --- a/src/Operation/BulkWrite.php +++ b/src/Operation/BulkWrite.php @@ -17,8 +17,10 @@ namespace MongoDB\Operation; +use MongoDB\Builder\BuilderEncoder; use MongoDB\BulkWriteResult; use MongoDB\Codec\DocumentCodec; +use MongoDB\Codec\Encoder; use MongoDB\Driver\BulkWrite as Bulk; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Server; @@ -94,6 +96,9 @@ class BulkWrite implements Executable * * Supported options for the bulk write operation: * + * * builderEncoder (MongoDB\Builder\Encoder): Encoder for query and + * aggregation builders. If not given, the default encoder will be used. + * * * bypassDocumentValidation (boolean): If true, allows the write to * circumvent document level validation. The default is false. * @@ -137,6 +142,10 @@ public function __construct(private string $databaseName, private string $collec $options += ['ordered' => true]; + if (isset($options['builderEncoder']) && ! $options['builderEncoder'] instanceof Encoder) { + throw InvalidArgumentException::invalidType('"builderEncoder" option', $options['builderEncoder'], Encoder::class); + } + if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); } @@ -169,7 +178,7 @@ public function __construct(private string $databaseName, private string $collec unset($options['writeConcern']); } - $this->operations = $this->validateOperations($operations, $options['codec'] ?? null); + $this->operations = $this->validateOperations($operations, $options['codec'] ?? null, $options['builderEncoder'] ?? new BuilderEncoder()); $this->options = $options; } @@ -264,7 +273,7 @@ private function createExecuteOptions(): array * @param array[] $operations * @return array[] */ - private function validateOperations(array $operations, ?DocumentCodec $codec): array + private function validateOperations(array $operations, ?DocumentCodec $codec, Encoder $builderEncoder): array { foreach ($operations as $i => $operation) { if (! is_array($operation)) { @@ -298,6 +307,8 @@ private function validateOperations(array $operations, ?DocumentCodec $codec): a case self::DELETE_MANY: case self::DELETE_ONE: + $operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]); + if (! isset($args[1])) { $args[1] = []; } @@ -317,6 +328,8 @@ private function validateOperations(array $operations, ?DocumentCodec $codec): a break; case self::REPLACE_ONE: + $operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]); + if (! isset($args[1]) && ! array_key_exists(1, $args)) { throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type)); } @@ -367,10 +380,14 @@ private function validateOperations(array $operations, ?DocumentCodec $codec): a case self::UPDATE_MANY: case self::UPDATE_ONE: + $operations[$i][$type][0] = $builderEncoder->encodeIfSupported($args[0]); + if (! isset($args[1]) && ! array_key_exists(1, $args)) { throw new InvalidArgumentException(sprintf('Missing second argument for $operations[%d]["%s"]', $i, $type)); } + $operations[$i][$type][1] = $args[1] = $builderEncoder->encodeIfSupported($args[1]); + if ((! is_document($args[1]) || ! is_first_key_operator($args[1])) && ! is_pipeline($args[1])) { throw new InvalidArgumentException(sprintf('Expected update operator(s) or non-empty pipeline for $operations[%d]["%s"][1]', $i, $type)); } diff --git a/tests/Collection/BuilderCollectionFunctionalTest.php b/tests/Collection/BuilderCollectionFunctionalTest.php new file mode 100644 index 000000000..15a89cda5 --- /dev/null +++ b/tests/Collection/BuilderCollectionFunctionalTest.php @@ -0,0 +1,250 @@ +collection->insertMany([['x' => 1], ['x' => 2], ['x' => 2]]); + } + + public function testAggregate(): void + { + $this->markTestSkipped('Not supported yet'); + } + + public function testBulkWriteDeleteMany(): void + { + $result = $this->collection->bulkWrite([ + [ + 'deleteMany' => [ + Query::query(x: Query::gt(1)), + ], + ], + ]); + $this->assertEquals(2, $result->getDeletedCount()); + } + + public function testBulkWriteDeleteOne(): void + { + $result = $this->collection->bulkWrite([ + [ + 'deleteOne' => [ + Query::query(x: Query::eq(1)), + ], + ], + ]); + $this->assertEquals(1, $result->getDeletedCount()); + } + + public function testBulkWriteReplaceOne(): void + { + $result = $this->collection->bulkWrite([ + [ + 'replaceOne' => [ + Query::query(x: Query::eq(1)), + ['x' => 3], + ], + ], + ]); + $this->assertEquals(1, $result->getModifiedCount()); + + $result = $this->collection->findOne(Query::query(x: Query::eq(3))); + $this->assertEquals(3, $result->x); + } + + public function testBulkWriteUpdateMany(): void + { + $result = $this->collection->bulkWrite([ + [ + 'updateMany' => [ + Query::query(x: Query::gt(1)), + // @todo Use Builder when update operators are supported by PHPLIB-1507 + ['$set' => ['x' => 3]], + ], + ], + ]); + $this->assertEquals(2, $result->getModifiedCount()); + + $result = $this->collection->find(Query::query(x: Query::eq(3)))->toArray(); + $this->assertCount(2, $result); + $this->assertEquals(3, $result[0]->x); + } + + public function testBulkWriteUpdateOne(): void + { + $result = $this->collection->bulkWrite([ + [ + 'updateOne' => [ + Query::query(x: Query::eq(1)), + // @todo Use Builder when update operators are supported by PHPLIB-1507 + ['$set' => ['x' => 3]], + ], + ], + ]); + + $this->assertEquals(1, $result->getModifiedCount()); + + $result = $this->collection->findOne(Query::query(x: Query::eq(3))); + $this->assertEquals(3, $result->x); + } + + public function testCountDocuments(): void + { + $result = $this->collection->countDocuments(Query::query(x: Query::gt(1))); + $this->assertEquals(2, $result); + } + + public function testDeleteMany(): void + { + $result = $this->collection->deleteMany(Query::query(x: Query::gt(1))); + $this->assertEquals(2, $result->getDeletedCount()); + } + + public function testDeleteOne(): void + { + $result = $this->collection->deleteOne(Query::query(x: Query::gt(1))); + $this->assertEquals(1, $result->getDeletedCount()); + } + + public function testDistinct(): void + { + $result = $this->collection->distinct('x', Query::query(x: Query::gt(1))); + $this->assertEquals([2], $result); + } + + public function testFind(): void + { + $results = $this->collection->find(Query::query(x: Query::gt(1)))->toArray(); + $this->assertCount(2, $results); + $this->assertEquals(2, $results[0]->x); + } + + public function testFindOne(): void + { + $result = $this->collection->findOne(Query::query(x: Query::eq(1))); + $this->assertEquals(1, $result->x); + } + + public function testFindOneAndDelete(): void + { + $result = $this->collection->findOneAndDelete(Query::query(x: Query::eq(1))); + $this->assertEquals(1, $result->x); + + $result = $this->collection->find()->toArray(); + $this->assertCount(2, $result); + } + + public function testFindOneAndReplace(): void + { + $this->collection->insertOne(['x' => 1]); + + $result = $this->collection->findOneAndReplace( + Query::query(x: Query::lt(2)), + ['x' => 3], + ); + $this->assertEquals(1, $result->x); + + $result = $this->collection->findOne(Query::query(x: Query::eq(3))); + $this->assertEquals(3, $result->x); + } + + public function testFindOneAndUpdate(): void + { + $result = $this->collection->findOneAndUpdate( + Query::query(x: Query::lt(2)), + // @todo Use Builder when update operators are supported by PHPLIB-1507 + ['$set' => ['x' => 3]], + ); + $this->assertEquals(1, $result->x); + + $result = $this->collection->findOne(Query::query(x: Query::eq(3))); + $this->assertEquals(3, $result->x); + } + + public function testReplaceOne(): void + { + $this->collection->insertOne(['x' => 1]); + + $result = $this->collection->replaceOne( + Query::query(x: Query::lt(2)), + ['x' => 3], + ); + $this->assertEquals(1, $result->getModifiedCount()); + + $result = $this->collection->findOne(Query::query(x: Query::eq(3))); + $this->assertEquals(3, $result->x); + } + + public function testUpdateOne(): void + { + $this->collection->insertOne(['x' => 1]); + + $result = $this->collection->updateOne( + Query::query(x: Query::lt(2)), + // @todo Use Builder when update operators are supported by PHPLIB-1507 + ['$set' => ['x' => 3]], + ); + $this->assertEquals(1, $result->getModifiedCount()); + + $result = $this->collection->findOne(Query::query(x: Query::eq(3))); + $this->assertEquals(3, $result->x); + } + + public function testUpdateWithPipeline(): void + { + $this->skipIfServerVersion('<', '4.2.0', 'Pipeline-style updates are not supported'); + + $result = $this->collection->updateOne( + Query::query(x: Query::lt(2)), + new Pipeline( + Stage::set(x: 3), + ), + ); + + $this->assertEquals(1, $result->getModifiedCount()); + } + + public function testUpdateMany(): void + { + $result = $this->collection->updateMany( + Query::query(x: Query::gt(1)), + // @todo Use Builder when update operators are supported by PHPLIB-1507 + ['$set' => ['x' => 3]], + ); + $this->assertEquals(2, $result->getModifiedCount()); + + $result = $this->collection->find(Query::query(x: Query::eq(3)))->toArray(); + $this->assertCount(2, $result); + $this->assertEquals(3, $result[0]->x); + } + + public function testUpdateManyWithPipeline(): void + { + $this->skipIfServerVersion('<', '4.2.0', 'Pipeline-style updates are not supported'); + + $result = $this->collection->updateMany( + Query::query(x: Query::gt(1)), + new Pipeline( + Stage::set(x: 3), + ), + ); + $this->assertEquals(2, $result->getModifiedCount()); + + $result = $this->collection->find(Query::query(x: Query::eq(3)))->toArray(); + $this->assertCount(2, $result); + $this->assertEquals(3, $result[0]->x); + } + + public function testWatch(): void + { + $this->markTestSkipped('Not supported yet'); + } +} From a829d2477dbba65cf2ab42fcc9663079cc210205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 12 Sep 2024 10:20:32 +0200 Subject: [PATCH 76/95] PHPLIB-1419 Encode Agg builder objects in Collection methods (#1383) --- psalm-baseline.xml | 34 ++++++++++ src/Client.php | 7 +++ src/Collection.php | 13 ++++ src/Database.php | 13 ++++ src/functions.php | 22 +++++++ tests/ClientFunctionalTest.php | 25 ++++++++ .../BuilderCollectionFunctionalTest.php | 34 +++++++++- .../BuilderDatabaseFunctionalTest.php | 63 +++++++++++++++++++ tests/FunctionsTest.php | 18 ++++++ 9 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 tests/Database/BuilderDatabaseFunctionalTest.php diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 732325e23..7e99f32d6 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -191,6 +191,7 @@ + @@ -198,6 +199,12 @@ + + + + + + @@ -220,9 +227,22 @@ + + + + + + + + + + + + + @@ -237,9 +257,22 @@ + + + + + + + + + + + + + @@ -854,6 +887,7 @@ + diff --git a/src/Client.php b/src/Client.php index 5b02aa44c..2e161707b 100644 --- a/src/Client.php +++ b/src/Client.php @@ -22,6 +22,7 @@ use MongoDB\BSON\Document; use MongoDB\BSON\PackedArray; use MongoDB\Builder\BuilderEncoder; +use MongoDB\Builder\Pipeline; use MongoDB\Codec\Encoder; use MongoDB\Driver\ClientEncryption; use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException; @@ -391,6 +392,12 @@ public function startSession(array $options = []) */ public function watch(array $pipeline = [], array $options = []) { + if (is_builder_pipeline($pipeline)) { + $pipeline = new Pipeline(...$pipeline); + } + + $pipeline = $this->builderEncoder->encodeIfSupported($pipeline); + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } diff --git a/src/Collection.php b/src/Collection.php index 7769b9a5b..8ed460c4e 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -23,6 +23,7 @@ use MongoDB\BSON\JavascriptInterface; use MongoDB\BSON\PackedArray; use MongoDB\Builder\BuilderEncoder; +use MongoDB\Builder\Pipeline; use MongoDB\Codec\DocumentCodec; use MongoDB\Codec\Encoder; use MongoDB\Driver\CursorInterface; @@ -223,6 +224,12 @@ public function __toString() */ public function aggregate(array $pipeline, array $options = []) { + if (is_builder_pipeline($pipeline)) { + $pipeline = new Pipeline(...$pipeline); + } + + $pipeline = $this->builderEncoder->encodeIfSupported($pipeline); + $hasWriteStage = is_last_pipeline_operator_write($pipeline); $options = $this->inheritReadPreference($options); @@ -1098,6 +1105,12 @@ public function updateSearchIndex(string $name, array|object $definition, array */ public function watch(array $pipeline = [], array $options = []) { + if (is_builder_pipeline($pipeline)) { + $pipeline = new Pipeline(...$pipeline); + } + + $pipeline = $this->builderEncoder->encodeIfSupported($pipeline); + $options = $this->inheritReadOptions($options); $options = $this->inheritCodecOrTypeMap($options); diff --git a/src/Database.php b/src/Database.php index 8b7742b70..125012c81 100644 --- a/src/Database.php +++ b/src/Database.php @@ -21,6 +21,7 @@ use MongoDB\BSON\Document; use MongoDB\BSON\PackedArray; use MongoDB\Builder\BuilderEncoder; +use MongoDB\Builder\Pipeline; use MongoDB\Codec\Encoder; use MongoDB\Driver\ClientEncryption; use MongoDB\Driver\Cursor; @@ -202,6 +203,12 @@ public function __toString() */ public function aggregate(array $pipeline, array $options = []) { + if (is_builder_pipeline($pipeline)) { + $pipeline = new Pipeline(...$pipeline); + } + + $pipeline = $this->builderEncoder->encodeIfSupported($pipeline); + $hasWriteStage = is_last_pipeline_operator_write($pipeline); if (! isset($options['readPreference']) && ! is_in_transaction($options)) { @@ -611,6 +618,12 @@ public function selectGridFSBucket(array $options = []) */ public function watch(array $pipeline = [], array $options = []) { + if (is_builder_pipeline($pipeline)) { + $pipeline = new Pipeline(...$pipeline); + } + + $pipeline = $this->builderEncoder->encodeIfSupported($pipeline); + if (! isset($options['readPreference']) && ! is_in_transaction($options)) { $options['readPreference'] = $this->readPreference; } diff --git a/src/functions.php b/src/functions.php index ca30fdbc0..a445467ba 100644 --- a/src/functions.php +++ b/src/functions.php @@ -21,6 +21,7 @@ use MongoDB\BSON\Document; use MongoDB\BSON\PackedArray; use MongoDB\BSON\Serializable; +use MongoDB\Builder\Type\StageInterface; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Manager; use MongoDB\Driver\ReadPreference; @@ -327,6 +328,27 @@ function is_pipeline(array|object $pipeline, bool $allowEmpty = false): bool return true; } +/** + * Returns whether the argument is a list that contains at least one + * {@see StageInterface} object. + * + * @internal + */ +function is_builder_pipeline(array $pipeline): bool +{ + if (! $pipeline || ! array_is_list($pipeline)) { + return false; + } + + foreach ($pipeline as $stage) { + if (is_object($stage) && $stage instanceof StageInterface) { + return true; + } + } + + return false; +} + /** * Returns whether we are currently in a transaction. * diff --git a/tests/ClientFunctionalTest.php b/tests/ClientFunctionalTest.php index 3c04ecd67..ca0ea2722 100644 --- a/tests/ClientFunctionalTest.php +++ b/tests/ClientFunctionalTest.php @@ -2,6 +2,9 @@ namespace MongoDB\Tests; +use MongoDB\Builder\Pipeline; +use MongoDB\Builder\Query; +use MongoDB\Builder\Stage; use MongoDB\Client; use MongoDB\Driver\BulkWrite; use MongoDB\Driver\Command; @@ -13,6 +16,7 @@ use function call_user_func; use function is_callable; +use function iterator_to_array; use function sprintf; /** @@ -137,4 +141,25 @@ public function testAddAndRemoveSubscriber(): void $client->getManager()->executeCommand('admin', new Command(['ping' => 1])); } + + public function testWatchWithBuilderPipeline(): void + { + $this->skipIfChangeStreamIsNotSupported(); + + if ($this->isShardedCluster()) { + $this->markTestSkipped('Test does not apply on sharded clusters: need more than a single getMore call on the change stream.'); + } + + $pipeline = new Pipeline( + Stage::match(operationType: Query::eq('insert')), + ); + // Extract the list of stages for arg type restriction + $pipeline = iterator_to_array($pipeline); + + $changeStream = $this->client->watch($pipeline); + $this->client->selectCollection($this->getDatabaseName(), $this->getCollectionName())->insertOne(['x' => 3]); + $changeStream->next(); + $this->assertTrue($changeStream->valid()); + $this->assertEquals('insert', $changeStream->current()->operationType); + } } diff --git a/tests/Collection/BuilderCollectionFunctionalTest.php b/tests/Collection/BuilderCollectionFunctionalTest.php index 15a89cda5..2ace15109 100644 --- a/tests/Collection/BuilderCollectionFunctionalTest.php +++ b/tests/Collection/BuilderCollectionFunctionalTest.php @@ -2,10 +2,13 @@ namespace MongoDB\Tests\Collection; +use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; +use function iterator_to_array; + class BuilderCollectionFunctionalTest extends FunctionalTestCase { public function setUp(): void @@ -17,7 +20,18 @@ public function setUp(): void public function testAggregate(): void { - $this->markTestSkipped('Not supported yet'); + $this->collection->insertMany([['x' => 10], ['x' => 10], ['x' => 10]]); + $pipeline = new Pipeline( + Stage::bucketAuto( + groupBy: Expression::intFieldPath('x'), + buckets: 2, + ), + ); + // Extract the list of stages for arg type restriction + $pipeline = iterator_to_array($pipeline); + + $results = $this->collection->aggregate($pipeline)->toArray(); + $this->assertCount(2, $results); } public function testBulkWriteDeleteMany(): void @@ -245,6 +259,22 @@ public function testUpdateManyWithPipeline(): void public function testWatch(): void { - $this->markTestSkipped('Not supported yet'); + $this->skipIfChangeStreamIsNotSupported(); + + if ($this->isShardedCluster()) { + $this->markTestSkipped('Test does not apply on sharded clusters: need more than a single getMore call on the change stream.'); + } + + $pipeline = new Pipeline( + Stage::match(operationType: Query::eq('insert')), + ); + // Extract the list of stages for arg type restriction + $pipeline = iterator_to_array($pipeline); + + $changeStream = $this->collection->watch($pipeline); + $this->collection->insertOne(['x' => 3]); + $changeStream->next(); + $this->assertTrue($changeStream->valid()); + $this->assertEquals('insert', $changeStream->current()->operationType); } } diff --git a/tests/Database/BuilderDatabaseFunctionalTest.php b/tests/Database/BuilderDatabaseFunctionalTest.php new file mode 100644 index 000000000..9b89d87e1 --- /dev/null +++ b/tests/Database/BuilderDatabaseFunctionalTest.php @@ -0,0 +1,63 @@ +dropCollection($this->getDatabaseName(), $this->getCollectionName()); + + parent::tearDown(); + } + + public function testAggregate(): void + { + $this->skipIfServerVersion('<', '6.0.0', '$documents stage is not supported'); + + $pipeline = new Pipeline( + Stage::documents([ + ['x' => 1], + ['x' => 2], + ['x' => 3], + ]), + Stage::bucketAuto( + groupBy: Expression::intFieldPath('x'), + buckets: 2, + ), + ); + // Extract the list of stages for arg type restriction + $pipeline = iterator_to_array($pipeline); + + $results = $this->database->aggregate($pipeline)->toArray(); + $this->assertCount(2, $results); + } + + public function testWatch(): void + { + $this->skipIfChangeStreamIsNotSupported(); + + if ($this->isShardedCluster()) { + $this->markTestSkipped('Test does not apply on sharded clusters: need more than a single getMore call on the change stream.'); + } + + $pipeline = new Pipeline( + Stage::match(operationType: Query::eq('insert')), + ); + // Extract the list of stages for arg type restriction + $pipeline = iterator_to_array($pipeline); + + $changeStream = $this->database->watch($pipeline); + $this->database->selectCollection($this->getCollectionName())->insertOne(['x' => 3]); + $changeStream->next(); + $this->assertTrue($changeStream->valid()); + $this->assertEquals('insert', $changeStream->current()->operationType); + } +} diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 7098cb24a..a673ef719 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -4,6 +4,8 @@ use MongoDB\BSON\Document; use MongoDB\BSON\PackedArray; +use MongoDB\Builder\Stage\LimitStage; +use MongoDB\Builder\Stage\MatchStage; use MongoDB\Driver\WriteConcern; use MongoDB\Model\BSONArray; use MongoDB\Model\BSONDocument; @@ -12,6 +14,7 @@ use function MongoDB\apply_type_map_to_document; use function MongoDB\create_field_path_type_map; use function MongoDB\document_to_array; +use function MongoDB\is_builder_pipeline; use function MongoDB\is_first_key_operator; use function MongoDB\is_last_pipeline_operator_write; use function MongoDB\is_mapreduce_output_inline; @@ -311,6 +314,21 @@ public function providePipelines(): array ]; } + /** @dataProvider provideStagePipelines */ + public function testIsBuilderPipeline($expected, $pipeline): void + { + $this->assertSame($expected, is_builder_pipeline($pipeline)); + } + + public function provideStagePipelines(): iterable + { + yield 'empty array' => [false, []]; + yield 'array of arrays' => [false, [['$match' => ['x' => 1]]]]; + yield 'map of stages' => [false, [1 => new MatchStage([])]]; + yield 'stages' => [true, [new MatchStage([]), new LimitStage(1)]]; + yield 'stages and operators' => [true, [new MatchStage([]), ['$limit' => 1]]]; + } + /** @dataProvider provideWriteConcerns */ public function testIsWriteConcernAcknowledged($expected, WriteConcern $writeConcern): void { From 585839632580fbff34196fe94fe571e1a5ca5467 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Thu, 12 Sep 2024 11:25:06 +0200 Subject: [PATCH 77/95] Update branch names for GHA workflows (#1390) --- .github/workflows/coding-standards.yml | 2 -- .github/workflows/generator.yml | 2 -- .github/workflows/merge-up.yml | 2 +- .github/workflows/static-analysis.yml | 2 -- .github/workflows/tests.yml | 2 -- 5 files changed, 1 insertion(+), 9 deletions(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 959e9b500..5dbb7dd71 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -5,12 +5,10 @@ on: pull_request: branches: - "v*.*" - - "master" - "feature/*" push: branches: - "v*.*" - - "master" - "feature/*" env: diff --git a/.github/workflows/generator.yml b/.github/workflows/generator.yml index 59d8eb186..2427a41eb 100644 --- a/.github/workflows/generator.yml +++ b/.github/workflows/generator.yml @@ -5,12 +5,10 @@ on: pull_request: branches: - "v*.*" - - "master" - "feature/*" push: branches: - "v*.*" - - "master" - "feature/*" env: diff --git a/.github/workflows/merge-up.yml b/.github/workflows/merge-up.yml index 03f3065bb..0ad47c1c2 100644 --- a/.github/workflows/merge-up.yml +++ b/.github/workflows/merge-up.yml @@ -3,7 +3,7 @@ name: Merge up on: push: branches: - - "v[0-9]+.[0-9]+" + - "v[0-9]+.[0-9x]+" env: GH_TOKEN: ${{ secrets.MERGE_UP_TOKEN }} diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 23bdd702e..ebb60cabf 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -5,12 +5,10 @@ on: pull_request: branches: - "v*.*" - - "master" - "feature/*" push: branches: - "v*.*" - - "master" - "feature/*" workflow_call: inputs: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 68ae09af4..5e7497813 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,12 +5,10 @@ on: pull_request: branches: - "v*.*" - - "master" - "feature/*" push: branches: - "v*.*" - - "master" - "feature/*" env: From dae397d9f78cc84649f728688a50c51c14ceedb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 13 Sep 2024 08:47:04 +0200 Subject: [PATCH 78/95] Remove PHPUnit functions polyfill (#1395) --- composer.json | 5 +- tests/PHPUnit/Functions.php | 2703 ----------------------------------- 2 files changed, 2 insertions(+), 2706 deletions(-) delete mode 100644 tests/PHPUnit/Functions.php diff --git a/composer.json b/composer.json index 9f2f2020f..a856f4e84 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "doctrine/coding-standard": "^12.0", "rector/rector": "^1.1", "squizlabs/php_codesniffer": "^3.7", - "symfony/phpunit-bridge": "^5.2", + "symfony/phpunit-bridge": "^7.1", "vimeo/psalm": "^5.13" }, "replace": { @@ -36,8 +36,7 @@ "autoload-dev": { "psr-4": { "MongoDB\\Tests\\": "tests/" - }, - "files": [ "tests/PHPUnit/Functions.php" ] + } }, "scripts": { "bench": "cd benchmark && composer update && vendor/bin/phpbench run --report=aggregate", diff --git a/tests/PHPUnit/Functions.php b/tests/PHPUnit/Functions.php deleted file mode 100644 index 6f445f410..000000000 --- a/tests/PHPUnit/Functions.php +++ /dev/null @@ -1,2703 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace PHPUnit\Framework; - -use ArrayAccess; -use Countable; -use DOMDocument; -use DOMElement; -use PHPUnit\Framework\Constraint\ArrayHasKey; -use PHPUnit\Framework\Constraint\Callback; -use PHPUnit\Framework\Constraint\ClassHasAttribute; -use PHPUnit\Framework\Constraint\ClassHasStaticAttribute; -use PHPUnit\Framework\Constraint\Constraint; -use PHPUnit\Framework\Constraint\Count; -use PHPUnit\Framework\Constraint\DirectoryExists; -use PHPUnit\Framework\Constraint\FileExists; -use PHPUnit\Framework\Constraint\GreaterThan; -use PHPUnit\Framework\Constraint\IsAnything; -use PHPUnit\Framework\Constraint\IsEmpty; -use PHPUnit\Framework\Constraint\IsEqual; -use PHPUnit\Framework\Constraint\IsEqualCanonicalizing; -use PHPUnit\Framework\Constraint\IsEqualIgnoringCase; -use PHPUnit\Framework\Constraint\IsEqualWithDelta; -use PHPUnit\Framework\Constraint\IsFalse; -use PHPUnit\Framework\Constraint\IsFinite; -use PHPUnit\Framework\Constraint\IsIdentical; -use PHPUnit\Framework\Constraint\IsInfinite; -use PHPUnit\Framework\Constraint\IsInstanceOf; -use PHPUnit\Framework\Constraint\IsJson; -use PHPUnit\Framework\Constraint\IsNan; -use PHPUnit\Framework\Constraint\IsNull; -use PHPUnit\Framework\Constraint\IsReadable; -use PHPUnit\Framework\Constraint\IsTrue; -use PHPUnit\Framework\Constraint\IsType; -use PHPUnit\Framework\Constraint\IsWritable; -use PHPUnit\Framework\Constraint\LessThan; -use PHPUnit\Framework\Constraint\LogicalAnd; -use PHPUnit\Framework\Constraint\LogicalNot; -use PHPUnit\Framework\Constraint\LogicalOr; -use PHPUnit\Framework\Constraint\LogicalXor; -use PHPUnit\Framework\Constraint\ObjectHasAttribute; -use PHPUnit\Framework\Constraint\RegularExpression; -use PHPUnit\Framework\Constraint\StringContains; -use PHPUnit\Framework\Constraint\StringEndsWith; -use PHPUnit\Framework\Constraint\StringMatchesFormatDescription; -use PHPUnit\Framework\Constraint\StringStartsWith; -use PHPUnit\Framework\Constraint\TraversableContainsEqual; -use PHPUnit\Framework\Constraint\TraversableContainsIdentical; -use PHPUnit\Framework\Constraint\TraversableContainsOnly; -use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount as AnyInvokedCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtIndex as InvokedAtIndexMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastCount as InvokedAtLeastCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtLeastOnce as InvokedAtLeastOnceMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedAtMostCount as InvokedAtMostCountMatcher; -use PHPUnit\Framework\MockObject\Rule\InvokedCount as InvokedCountMatcher; -use PHPUnit\Framework\MockObject\Stub\ConsecutiveCalls as ConsecutiveCallsStub; -use PHPUnit\Framework\MockObject\Stub\Exception as ExceptionStub; -use PHPUnit\Framework\MockObject\Stub\ReturnArgument as ReturnArgumentStub; -use PHPUnit\Framework\MockObject\Stub\ReturnCallback as ReturnCallbackStub; -use PHPUnit\Framework\MockObject\Stub\ReturnSelf as ReturnSelfStub; -use PHPUnit\Framework\MockObject\Stub\ReturnStub; -use PHPUnit\Framework\MockObject\Stub\ReturnValueMap as ReturnValueMapStub; -use PHPUnit\Util\Exception; -use PHPUnit\Util\Xml\Exception as XmlException; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use Throwable; - -use function func_get_args; -use function function_exists; - -if (! function_exists('PHPUnit\Framework\assertArrayHasKey')) { - /** - * Asserts that an array has a specified key. - * - * @see Assert::assertArrayHasKey - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertArrayHasKey(int|string $key, array|ArrayAccess $array, string $message = ''): void - { - Assert::assertArrayHasKey(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertArrayNotHasKey')) { - /** - * Asserts that an array does not have a specified key. - * - * @see Assert::assertArrayNotHasKey - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertArrayNotHasKey(int|string $key, array|ArrayAccess $array, string $message = ''): void - { - Assert::assertArrayNotHasKey(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertContains')) { - /** - * Asserts that a haystack contains a needle. - * - * @see Assert::assertContains - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertContains($needle, $haystack, string $message = ''): void - { - Assert::assertContains(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertContainsEquals')) { - function assertContainsEquals($needle, $haystack, string $message = ''): void - { - Assert::assertContainsEquals(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotContains')) { - /** - * Asserts that a haystack does not contain a needle. - * - * @see Assert::assertNotContains - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertNotContains($needle, $haystack, string $message = ''): void - { - Assert::assertNotContains(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotContainsEquals')) { - function assertNotContainsEquals($needle, $haystack, string $message = ''): void - { - Assert::assertNotContainsEquals(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertContainsOnly')) { - /** - * Asserts that a haystack contains only values of a given type. - * - * @see Assert::assertContainsOnly - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertContainsOnly(string $type, $haystack, ?bool $isNativeType = null, string $message = ''): void - { - Assert::assertContainsOnly(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertContainsOnlyInstancesOf')) { - /** - * Asserts that a haystack contains only instances of a given class name. - * - * @see Assert::assertContainsOnlyInstancesOf - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertContainsOnlyInstancesOf(string $className, $haystack, string $message = ''): void - { - Assert::assertContainsOnlyInstancesOf(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotContainsOnly')) { - /** - * Asserts that a haystack does not contain only values of a given type. - * - * @see Assert::assertNotContainsOnly - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertNotContainsOnly(string $type, $haystack, ?bool $isNativeType = null, string $message = ''): void - { - Assert::assertNotContainsOnly(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertCount')) { - /** - * Asserts the number of elements of an array, Countable or Traversable. - * - * @see Assert::assertCount - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void - { - Assert::assertCount(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotCount')) { - /** - * Asserts the number of elements of an array, Countable or Traversable. - * - * @see Assert::assertNotCount - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertNotCount(int $expectedCount, Countable|iterable $haystack, string $message = ''): void - { - Assert::assertNotCount(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertEquals')) { - /** - * Asserts that two variables are equal. - * - * @see Assert::assertEquals - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertEquals($expected, $actual, string $message = ''): void - { - Assert::assertEquals(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertEqualsCanonicalizing')) { - /** - * Asserts that two variables are equal (canonicalizing). - * - * @see Assert::assertEqualsCanonicalizing - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertEqualsCanonicalizing($expected, $actual, string $message = ''): void - { - Assert::assertEqualsCanonicalizing(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertEqualsIgnoringCase')) { - /** - * Asserts that two variables are equal (ignoring case). - * - * @see Assert::assertEqualsIgnoringCase - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertEqualsIgnoringCase($expected, $actual, string $message = ''): void - { - Assert::assertEqualsIgnoringCase(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertEqualsWithDelta')) { - /** - * Asserts that two variables are equal (with delta). - * - * @see Assert::assertEqualsWithDelta - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void - { - Assert::assertEqualsWithDelta(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotEquals')) { - /** - * Asserts that two variables are not equal. - * - * @see Assert::assertNotEquals - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertNotEquals($expected, $actual, string $message = ''): void - { - Assert::assertNotEquals(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotEqualsCanonicalizing')) { - /** - * Asserts that two variables are not equal (canonicalizing). - * - * @see Assert::assertNotEqualsCanonicalizing - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertNotEqualsCanonicalizing($expected, $actual, string $message = ''): void - { - Assert::assertNotEqualsCanonicalizing(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotEqualsIgnoringCase')) { - /** - * Asserts that two variables are not equal (ignoring case). - * - * @see Assert::assertNotEqualsIgnoringCase - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertNotEqualsIgnoringCase($expected, $actual, string $message = ''): void - { - Assert::assertNotEqualsIgnoringCase(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotEqualsWithDelta')) { - /** - * Asserts that two variables are not equal (with delta). - * - * @see Assert::assertNotEqualsWithDelta - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertNotEqualsWithDelta($expected, $actual, float $delta, string $message = ''): void - { - Assert::assertNotEqualsWithDelta(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertEmpty')) { - /** - * Asserts that a variable is empty. - * - * @see Assert::assertEmpty - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert empty $actual - */ - function assertEmpty($actual, string $message = ''): void - { - Assert::assertEmpty(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotEmpty')) { - /** - * Asserts that a variable is not empty. - * - * @see Assert::assertNotEmpty - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !empty $actual - */ - function assertNotEmpty($actual, string $message = ''): void - { - Assert::assertNotEmpty(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertGreaterThan')) { - /** - * Asserts that a value is greater than another value. - * - * @see Assert::assertGreaterThan - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertGreaterThan($expected, $actual, string $message = ''): void - { - Assert::assertGreaterThan(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertGreaterThanOrEqual')) { - /** - * Asserts that a value is greater than or equal to another value. - * - * @see Assert::assertGreaterThanOrEqual - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertGreaterThanOrEqual($expected, $actual, string $message = ''): void - { - Assert::assertGreaterThanOrEqual(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertLessThan')) { - /** - * Asserts that a value is smaller than another value. - * - * @see Assert::assertLessThan - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertLessThan($expected, $actual, string $message = ''): void - { - Assert::assertLessThan(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertLessThanOrEqual')) { - /** - * Asserts that a value is smaller than or equal to another value. - * - * @see Assert::assertLessThanOrEqual - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertLessThanOrEqual($expected, $actual, string $message = ''): void - { - Assert::assertLessThanOrEqual(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileEquals')) { - /** - * Asserts that the contents of one file is equal to the contents of another - * file. - * - * @see Assert::assertFileEquals - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileEquals(string $expected, string $actual, string $message = ''): void - { - Assert::assertFileEquals(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileEqualsCanonicalizing')) { - /** - * Asserts that the contents of one file is equal to the contents of another - * file (canonicalizing). - * - * @see Assert::assertFileEqualsCanonicalizing - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void - { - Assert::assertFileEqualsCanonicalizing(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileEqualsIgnoringCase')) { - /** - * Asserts that the contents of one file is equal to the contents of another - * file (ignoring case). - * - * @see Assert::assertFileEqualsIgnoringCase - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void - { - Assert::assertFileEqualsIgnoringCase(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileNotEquals')) { - /** - * Asserts that the contents of one file is not equal to the contents of - * another file. - * - * @see Assert::assertFileNotEquals - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileNotEquals(string $expected, string $actual, string $message = ''): void - { - Assert::assertFileNotEquals(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileNotEqualsCanonicalizing')) { - /** - * Asserts that the contents of one file is not equal to the contents of another - * file (canonicalizing). - * - * @see Assert::assertFileNotEqualsCanonicalizing - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileNotEqualsCanonicalizing(string $expected, string $actual, string $message = ''): void - { - Assert::assertFileNotEqualsCanonicalizing(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileNotEqualsIgnoringCase')) { - /** - * Asserts that the contents of one file is not equal to the contents of another - * file (ignoring case). - * - * @see Assert::assertFileNotEqualsIgnoringCase - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileNotEqualsIgnoringCase(string $expected, string $actual, string $message = ''): void - { - Assert::assertFileNotEqualsIgnoringCase(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringEqualsFile')) { - /** - * Asserts that the contents of a string is equal - * to the contents of a file. - * - * @see Assert::assertStringEqualsFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringEqualsFile(string $expectedFile, string $actualString, string $message = ''): void - { - Assert::assertStringEqualsFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringEqualsFileCanonicalizing')) { - /** - * Asserts that the contents of a string is equal - * to the contents of a file (canonicalizing). - * - * @see Assert::assertStringEqualsFileCanonicalizing - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void - { - Assert::assertStringEqualsFileCanonicalizing(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringEqualsFileIgnoringCase')) { - /** - * Asserts that the contents of a string is equal - * to the contents of a file (ignoring case). - * - * @see Assert::assertStringEqualsFileIgnoringCase - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void - { - Assert::assertStringEqualsFileIgnoringCase(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringNotEqualsFile')) { - /** - * Asserts that the contents of a string is not equal - * to the contents of a file. - * - * @see Assert::assertStringNotEqualsFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringNotEqualsFile(string $expectedFile, string $actualString, string $message = ''): void - { - Assert::assertStringNotEqualsFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringNotEqualsFileCanonicalizing')) { - /** - * Asserts that the contents of a string is not equal - * to the contents of a file (canonicalizing). - * - * @see Assert::assertStringNotEqualsFileCanonicalizing - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringNotEqualsFileCanonicalizing(string $expectedFile, string $actualString, string $message = ''): void - { - Assert::assertStringNotEqualsFileCanonicalizing(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringNotEqualsFileIgnoringCase')) { - /** - * Asserts that the contents of a string is not equal - * to the contents of a file (ignoring case). - * - * @see Assert::assertStringNotEqualsFileIgnoringCase - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringNotEqualsFileIgnoringCase(string $expectedFile, string $actualString, string $message = ''): void - { - Assert::assertStringNotEqualsFileIgnoringCase(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsReadable')) { - /** - * Asserts that a file/dir is readable. - * - * @see Assert::assertIsReadable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertIsReadable(string $filename, string $message = ''): void - { - Assert::assertIsReadable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotReadable')) { - /** - * Asserts that a file/dir exists and is not readable. - * - * @see Assert::assertIsNotReadable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertIsNotReadable(string $filename, string $message = ''): void - { - Assert::assertIsNotReadable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotIsReadable')) { - /** - * Asserts that a file/dir exists and is not readable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4062 - * @see Assert::assertNotIsReadable - */ - function assertNotIsReadable(string $filename, string $message = ''): void - { - Assert::assertNotIsReadable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsWritable')) { - /** - * Asserts that a file/dir exists and is writable. - * - * @see Assert::assertIsWritable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertIsWritable(string $filename, string $message = ''): void - { - Assert::assertIsWritable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotWritable')) { - /** - * Asserts that a file/dir exists and is not writable. - * - * @see Assert::assertIsNotWritable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertIsNotWritable(string $filename, string $message = ''): void - { - Assert::assertIsNotWritable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotIsWritable')) { - /** - * Asserts that a file/dir exists and is not writable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4065 - * @see Assert::assertNotIsWritable - */ - function assertNotIsWritable(string $filename, string $message = ''): void - { - Assert::assertNotIsWritable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertDirectoryExists')) { - /** - * Asserts that a directory exists. - * - * @see Assert::assertDirectoryExists - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertDirectoryExists(string $directory, string $message = ''): void - { - Assert::assertDirectoryExists(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertDirectoryDoesNotExist')) { - /** - * Asserts that a directory does not exist. - * - * @see Assert::assertDirectoryDoesNotExist - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertDirectoryDoesNotExist(string $directory, string $message = ''): void - { - Assert::assertDirectoryDoesNotExist(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertDirectoryNotExists')) { - /** - * Asserts that a directory does not exist. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4068 - * @see Assert::assertDirectoryNotExists - */ - function assertDirectoryNotExists(string $directory, string $message = ''): void - { - Assert::assertDirectoryNotExists(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertDirectoryIsReadable')) { - /** - * Asserts that a directory exists and is readable. - * - * @see Assert::assertDirectoryIsReadable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertDirectoryIsReadable(string $directory, string $message = ''): void - { - Assert::assertDirectoryIsReadable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertDirectoryIsNotReadable')) { - /** - * Asserts that a directory exists and is not readable. - * - * @see Assert::assertDirectoryIsNotReadable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertDirectoryIsNotReadable(string $directory, string $message = ''): void - { - Assert::assertDirectoryIsNotReadable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertDirectoryNotIsReadable')) { - /** - * Asserts that a directory exists and is not readable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4071 - * @see Assert::assertDirectoryNotIsReadable - */ - function assertDirectoryNotIsReadable(string $directory, string $message = ''): void - { - Assert::assertDirectoryNotIsReadable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertDirectoryIsWritable')) { - /** - * Asserts that a directory exists and is writable. - * - * @see Assert::assertDirectoryIsWritable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertDirectoryIsWritable(string $directory, string $message = ''): void - { - Assert::assertDirectoryIsWritable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertDirectoryIsNotWritable')) { - /** - * Asserts that a directory exists and is not writable. - * - * @see Assert::assertDirectoryIsNotWritable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertDirectoryIsNotWritable(string $directory, string $message = ''): void - { - Assert::assertDirectoryIsNotWritable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertDirectoryNotIsWritable')) { - /** - * Asserts that a directory exists and is not writable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4074 - * @see Assert::assertDirectoryNotIsWritable - */ - function assertDirectoryNotIsWritable(string $directory, string $message = ''): void - { - Assert::assertDirectoryNotIsWritable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileExists')) { - /** - * Asserts that a file exists. - * - * @see Assert::assertFileExists - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileExists(string $filename, string $message = ''): void - { - Assert::assertFileExists(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileDoesNotExist')) { - /** - * Asserts that a file does not exist. - * - * @see Assert::assertFileDoesNotExist - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileDoesNotExist(string $filename, string $message = ''): void - { - Assert::assertFileDoesNotExist(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileNotExists')) { - /** - * Asserts that a file does not exist. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4077 - * @see Assert::assertFileNotExists - */ - function assertFileNotExists(string $filename, string $message = ''): void - { - Assert::assertFileNotExists(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileIsReadable')) { - /** - * Asserts that a file exists and is readable. - * - * @see Assert::assertFileIsReadable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileIsReadable(string $file, string $message = ''): void - { - Assert::assertFileIsReadable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileIsNotReadable')) { - /** - * Asserts that a file exists and is not readable. - * - * @see Assert::assertFileIsNotReadable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileIsNotReadable(string $file, string $message = ''): void - { - Assert::assertFileIsNotReadable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileNotIsReadable')) { - /** - * Asserts that a file exists and is not readable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4080 - * @see Assert::assertFileNotIsReadable - */ - function assertFileNotIsReadable(string $file, string $message = ''): void - { - Assert::assertFileNotIsReadable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileIsWritable')) { - /** - * Asserts that a file exists and is writable. - * - * @see Assert::assertFileIsWritable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileIsWritable(string $file, string $message = ''): void - { - Assert::assertFileIsWritable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileIsNotWritable')) { - /** - * Asserts that a file exists and is not writable. - * - * @see Assert::assertFileIsNotWritable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFileIsNotWritable(string $file, string $message = ''): void - { - Assert::assertFileIsNotWritable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFileNotIsWritable')) { - /** - * Asserts that a file exists and is not writable. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4083 - * @see Assert::assertFileNotIsWritable - */ - function assertFileNotIsWritable(string $file, string $message = ''): void - { - Assert::assertFileNotIsWritable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertTrue')) { - /** - * Asserts that a condition is true. - * - * @see Assert::assertTrue - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert true $condition - */ - function assertTrue($condition, string $message = ''): void - { - Assert::assertTrue(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotTrue')) { - /** - * Asserts that a condition is not true. - * - * @see Assert::assertNotTrue - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !true $condition - */ - function assertNotTrue($condition, string $message = ''): void - { - Assert::assertNotTrue(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFalse')) { - /** - * Asserts that a condition is false. - * - * @see Assert::assertFalse - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert false $condition - */ - function assertFalse($condition, string $message = ''): void - { - Assert::assertFalse(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotFalse')) { - /** - * Asserts that a condition is not false. - * - * @see Assert::assertNotFalse - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !false $condition - */ - function assertNotFalse($condition, string $message = ''): void - { - Assert::assertNotFalse(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNull')) { - /** - * Asserts that a variable is null. - * - * @see Assert::assertNull - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert null $actual - */ - function assertNull($actual, string $message = ''): void - { - Assert::assertNull(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotNull')) { - /** - * Asserts that a variable is not null. - * - * @see Assert::assertNotNull - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !null $actual - */ - function assertNotNull($actual, string $message = ''): void - { - Assert::assertNotNull(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertFinite')) { - /** - * Asserts that a variable is finite. - * - * @see Assert::assertFinite - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertFinite($actual, string $message = ''): void - { - Assert::assertFinite(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertInfinite')) { - /** - * Asserts that a variable is infinite. - * - * @see Assert::assertInfinite - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertInfinite($actual, string $message = ''): void - { - Assert::assertInfinite(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNan')) { - /** - * Asserts that a variable is nan. - * - * @see Assert::assertNan - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertNan($actual, string $message = ''): void - { - Assert::assertNan(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertClassHasAttribute')) { - /** - * Asserts that a class has a specified attribute. - * - * @see Assert::assertClassHasAttribute - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertClassHasAttribute(string $attributeName, string $className, string $message = ''): void - { - Assert::assertClassHasAttribute(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertClassNotHasAttribute')) { - /** - * Asserts that a class does not have a specified attribute. - * - * @see Assert::assertClassNotHasAttribute - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertClassNotHasAttribute(string $attributeName, string $className, string $message = ''): void - { - Assert::assertClassNotHasAttribute(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertClassHasStaticAttribute')) { - /** - * Asserts that a class has a specified static attribute. - * - * @see Assert::assertClassHasStaticAttribute - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertClassHasStaticAttribute(string $attributeName, string $className, string $message = ''): void - { - Assert::assertClassHasStaticAttribute(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertClassNotHasStaticAttribute')) { - /** - * Asserts that a class does not have a specified static attribute. - * - * @see Assert::assertClassNotHasStaticAttribute - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertClassNotHasStaticAttribute(string $attributeName, string $className, string $message = ''): void - { - Assert::assertClassNotHasStaticAttribute(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertObjectHasAttribute')) { - /** - * Asserts that an object has a specified attribute. - * - * @see Assert::assertObjectHasAttribute - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertObjectHasAttribute(string $attributeName, object $object, string $message = ''): void - { - Assert::assertObjectHasAttribute(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertObjectNotHasAttribute')) { - /** - * Asserts that an object does not have a specified attribute. - * - * @see Assert::assertObjectNotHasAttribute - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertObjectNotHasAttribute(string $attributeName, object $object, string $message = ''): void - { - Assert::assertObjectNotHasAttribute(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertSame')) { - /** - * Asserts that two variables have the same type and value. - * Used on objects, it asserts that two variables reference - * the same object. - * - * @see Assert::assertSame - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-template ExpectedType - * @psalm-param ExpectedType $expected - * @psalm-assert =ExpectedType $actual - */ - function assertSame($expected, $actual, string $message = ''): void - { - Assert::assertSame(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotSame')) { - /** - * Asserts that two variables do not have the same type and value. - * Used on objects, it asserts that two variables do not reference - * the same object. - * - * @see Assert::assertNotSame - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertNotSame($expected, $actual, string $message = ''): void - { - Assert::assertNotSame(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertInstanceOf')) { - /** - * Asserts that a variable is of a given type. - * - * @see Assert::assertInstanceOf - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - * - * @psalm-template ExpectedType of object - * @psalm-param class-string $expected - * @psalm-assert ExpectedType $actual - */ - function assertInstanceOf(string $expected, $actual, string $message = ''): void - { - Assert::assertInstanceOf(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotInstanceOf')) { - /** - * Asserts that a variable is not of a given type. - * - * @see Assert::assertNotInstanceOf - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - * - * @psalm-template ExpectedType of object - * @psalm-param class-string $expected - * @psalm-assert !ExpectedType $actual - */ - function assertNotInstanceOf(string $expected, $actual, string $message = ''): void - { - Assert::assertNotInstanceOf(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsArray')) { - /** - * Asserts that a variable is of type array. - * - * @see Assert::assertIsArray - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert array $actual - */ - function assertIsArray($actual, string $message = ''): void - { - Assert::assertIsArray(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsBool')) { - /** - * Asserts that a variable is of type bool. - * - * @see Assert::assertIsBool - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert bool $actual - */ - function assertIsBool($actual, string $message = ''): void - { - Assert::assertIsBool(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsFloat')) { - /** - * Asserts that a variable is of type float. - * - * @see Assert::assertIsFloat - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert float $actual - */ - function assertIsFloat($actual, string $message = ''): void - { - Assert::assertIsFloat(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsInt')) { - /** - * Asserts that a variable is of type int. - * - * @see Assert::assertIsInt - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert int $actual - */ - function assertIsInt($actual, string $message = ''): void - { - Assert::assertIsInt(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNumeric')) { - /** - * Asserts that a variable is of type numeric. - * - * @see Assert::assertIsNumeric - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert numeric $actual - */ - function assertIsNumeric($actual, string $message = ''): void - { - Assert::assertIsNumeric(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsObject')) { - /** - * Asserts that a variable is of type object. - * - * @see Assert::assertIsObject - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert object $actual - */ - function assertIsObject($actual, string $message = ''): void - { - Assert::assertIsObject(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsResource')) { - /** - * Asserts that a variable is of type resource. - * - * @see Assert::assertIsResource - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert resource $actual - */ - function assertIsResource($actual, string $message = ''): void - { - Assert::assertIsResource(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsClosedResource')) { - /** - * Asserts that a variable is of type resource and is closed. - * - * @see Assert::assertIsClosedResource - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert resource $actual - */ - function assertIsClosedResource($actual, string $message = ''): void - { - Assert::assertIsClosedResource(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsString')) { - /** - * Asserts that a variable is of type string. - * - * @see Assert::assertIsString - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert string $actual - */ - function assertIsString($actual, string $message = ''): void - { - Assert::assertIsString(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsScalar')) { - /** - * Asserts that a variable is of type scalar. - * - * @see Assert::assertIsScalar - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert scalar $actual - */ - function assertIsScalar($actual, string $message = ''): void - { - Assert::assertIsScalar(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsCallable')) { - /** - * Asserts that a variable is of type callable. - * - * @see Assert::assertIsCallable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert callable $actual - */ - function assertIsCallable($actual, string $message = ''): void - { - Assert::assertIsCallable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsIterable')) { - /** - * Asserts that a variable is of type iterable. - * - * @see Assert::assertIsIterable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert iterable $actual - */ - function assertIsIterable($actual, string $message = ''): void - { - Assert::assertIsIterable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotArray')) { - /** - * Asserts that a variable is not of type array. - * - * @see Assert::assertIsNotArray - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !array $actual - */ - function assertIsNotArray($actual, string $message = ''): void - { - Assert::assertIsNotArray(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotBool')) { - /** - * Asserts that a variable is not of type bool. - * - * @see Assert::assertIsNotBool - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !bool $actual - */ - function assertIsNotBool($actual, string $message = ''): void - { - Assert::assertIsNotBool(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotFloat')) { - /** - * Asserts that a variable is not of type float. - * - * @see Assert::assertIsNotFloat - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !float $actual - */ - function assertIsNotFloat($actual, string $message = ''): void - { - Assert::assertIsNotFloat(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotInt')) { - /** - * Asserts that a variable is not of type int. - * - * @see Assert::assertIsNotInt - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !int $actual - */ - function assertIsNotInt($actual, string $message = ''): void - { - Assert::assertIsNotInt(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotNumeric')) { - /** - * Asserts that a variable is not of type numeric. - * - * @see Assert::assertIsNotNumeric - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !numeric $actual - */ - function assertIsNotNumeric($actual, string $message = ''): void - { - Assert::assertIsNotNumeric(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotObject')) { - /** - * Asserts that a variable is not of type object. - * - * @see Assert::assertIsNotObject - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !object $actual - */ - function assertIsNotObject($actual, string $message = ''): void - { - Assert::assertIsNotObject(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotResource')) { - /** - * Asserts that a variable is not of type resource. - * - * @see Assert::assertIsNotResource - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !resource $actual - */ - function assertIsNotResource($actual, string $message = ''): void - { - Assert::assertIsNotResource(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotClosedResource')) { - /** - * Asserts that a variable is not of type resource. - * - * @see Assert::assertIsNotClosedResource - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !resource $actual - */ - function assertIsNotClosedResource($actual, string $message = ''): void - { - Assert::assertIsNotClosedResource(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotString')) { - /** - * Asserts that a variable is not of type string. - * - * @see Assert::assertIsNotString - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !string $actual - */ - function assertIsNotString($actual, string $message = ''): void - { - Assert::assertIsNotString(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotScalar')) { - /** - * Asserts that a variable is not of type scalar. - * - * @see Assert::assertIsNotScalar - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !scalar $actual - */ - function assertIsNotScalar($actual, string $message = ''): void - { - Assert::assertIsNotScalar(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotCallable')) { - /** - * Asserts that a variable is not of type callable. - * - * @see Assert::assertIsNotCallable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !callable $actual - */ - function assertIsNotCallable($actual, string $message = ''): void - { - Assert::assertIsNotCallable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertIsNotIterable')) { - /** - * Asserts that a variable is not of type iterable. - * - * @see Assert::assertIsNotIterable - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @psalm-assert !iterable $actual - */ - function assertIsNotIterable($actual, string $message = ''): void - { - Assert::assertIsNotIterable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertMatchesRegularExpression')) { - /** - * Asserts that a string matches a given regular expression. - * - * @see Assert::assertMatchesRegularExpression - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void - { - Assert::assertMatchesRegularExpression(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertRegExp')) { - /** - * Asserts that a string matches a given regular expression. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4086 - * @see Assert::assertRegExp - */ - function assertRegExp(string $pattern, string $string, string $message = ''): void - { - Assert::assertRegExp(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertDoesNotMatchRegularExpression')) { - /** - * Asserts that a string does not match a given regular expression. - * - * @see Assert::assertDoesNotMatchRegularExpression - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertDoesNotMatchRegularExpression(string $pattern, string $string, string $message = ''): void - { - Assert::assertDoesNotMatchRegularExpression(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotRegExp')) { - /** - * Asserts that a string does not match a given regular expression. - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4089 - * @see Assert::assertNotRegExp - */ - function assertNotRegExp(string $pattern, string $string, string $message = ''): void - { - Assert::assertNotRegExp(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertSameSize')) { - /** - * Assert that the size of two arrays (or `Countable` or `Traversable` objects) - * is the same. - * - * @see Assert::assertSameSize - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void - { - Assert::assertSameSize(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertNotSameSize')) { - /** - * Assert that the size of two arrays (or `Countable` or `Traversable` objects) - * is not the same. - * - * @see Assert::assertNotSameSize - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertNotSameSize(Countable|iterable $expected, Countable|iterable $actual, string $message = ''): void - { - Assert::assertNotSameSize(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringMatchesFormat')) { - /** - * Asserts that a string matches a given format string. - * - * @see Assert::assertStringMatchesFormat - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringMatchesFormat(string $format, string $string, string $message = ''): void - { - Assert::assertStringMatchesFormat(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringNotMatchesFormat')) { - /** - * Asserts that a string does not match a given format string. - * - * @see Assert::assertStringNotMatchesFormat - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringNotMatchesFormat(string $format, string $string, string $message = ''): void - { - Assert::assertStringNotMatchesFormat(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringMatchesFormatFile')) { - /** - * Asserts that a string matches a given format file. - * - * @see Assert::assertStringMatchesFormatFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringMatchesFormatFile(string $formatFile, string $string, string $message = ''): void - { - Assert::assertStringMatchesFormatFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringNotMatchesFormatFile')) { - /** - * Asserts that a string does not match a given format string. - * - * @see Assert::assertStringNotMatchesFormatFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringNotMatchesFormatFile(string $formatFile, string $string, string $message = ''): void - { - Assert::assertStringNotMatchesFormatFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringStartsWith')) { - /** - * Asserts that a string starts with a given prefix. - * - * @see Assert::assertStringStartsWith - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringStartsWith(string $prefix, string $string, string $message = ''): void - { - Assert::assertStringStartsWith(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringStartsNotWith')) { - /** - * Asserts that a string starts not with a given prefix. - * - * @see Assert::assertStringStartsNotWith - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringStartsNotWith(string $prefix, string $string, string $message = ''): void - { - Assert::assertStringStartsNotWith(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringContainsString')) { - /** - * @see Assert::assertStringContainsString - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringContainsString(string $needle, string $haystack, string $message = ''): void - { - Assert::assertStringContainsString(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringContainsStringIgnoringCase')) { - /** - * @see Assert::assertStringContainsStringIgnoringCase - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void - { - Assert::assertStringContainsStringIgnoringCase(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringNotContainsString')) { - /** - * @see Assert::assertStringNotContainsString - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringNotContainsString(string $needle, string $haystack, string $message = ''): void - { - Assert::assertStringNotContainsString(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringNotContainsStringIgnoringCase')) { - /** - * @see Assert::assertStringNotContainsStringIgnoringCase - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringNotContainsStringIgnoringCase(string $needle, string $haystack, string $message = ''): void - { - Assert::assertStringNotContainsStringIgnoringCase(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringEndsWith')) { - /** - * Asserts that a string ends with a given suffix. - * - * @see Assert::assertStringEndsWith - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringEndsWith(string $suffix, string $string, string $message = ''): void - { - Assert::assertStringEndsWith(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertStringEndsNotWith')) { - /** - * Asserts that a string ends not with a given suffix. - * - * @see Assert::assertStringEndsNotWith - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertStringEndsNotWith(string $suffix, string $string, string $message = ''): void - { - Assert::assertStringEndsNotWith(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertXmlFileEqualsXmlFile')) { - /** - * Asserts that two XML files are equal. - * - * @see Assert::assertXmlFileEqualsXmlFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertXmlFileEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void - { - Assert::assertXmlFileEqualsXmlFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertXmlFileNotEqualsXmlFile')) { - /** - * Asserts that two XML files are not equal. - * - * @see Assert::assertXmlFileNotEqualsXmlFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws Exception - */ - function assertXmlFileNotEqualsXmlFile(string $expectedFile, string $actualFile, string $message = ''): void - { - Assert::assertXmlFileNotEqualsXmlFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertXmlStringEqualsXmlFile')) { - /** - * Asserts that two XML documents are equal. - * - * @see Assert::assertXmlStringEqualsXmlFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws XmlException - */ - function assertXmlStringEqualsXmlFile(string $expectedFile, DOMDocument|string $actualXml, string $message = ''): void - { - Assert::assertXmlStringEqualsXmlFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertXmlStringNotEqualsXmlFile')) { - /** - * Asserts that two XML documents are not equal. - * - * @see Assert::assertXmlStringNotEqualsXmlFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws XmlException - */ - function assertXmlStringNotEqualsXmlFile(string $expectedFile, DOMDocument|string $actualXml, string $message = ''): void - { - Assert::assertXmlStringNotEqualsXmlFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertXmlStringEqualsXmlString')) { - /** - * Asserts that two XML documents are equal. - * - * @see Assert::assertXmlStringEqualsXmlString - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws XmlException - */ - function assertXmlStringEqualsXmlString(DOMDocument|string $expectedXml, DOMDocument|string $actualXml, string $message = ''): void - { - Assert::assertXmlStringEqualsXmlString(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertXmlStringNotEqualsXmlString')) { - /** - * Asserts that two XML documents are not equal. - * - * @see Assert::assertXmlStringNotEqualsXmlString - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * @throws XmlException - */ - function assertXmlStringNotEqualsXmlString(DOMDocument|string $expectedXml, DOMDocument|string $actualXml, string $message = ''): void - { - Assert::assertXmlStringNotEqualsXmlString(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertEqualXMLStructure')) { - /** - * Asserts that a hierarchy of DOMElements matches. - * - * @throws AssertionFailedError - * @throws ExpectationFailedException - * @throws InvalidArgumentException - * - * @codeCoverageIgnore - * - * @deprecated https://github.com/sebastianbergmann/phpunit/issues/4091 - * @see Assert::assertEqualXMLStructure - */ - function assertEqualXMLStructure(DOMElement $expectedElement, DOMElement $actualElement, bool $checkAttributes = false, string $message = ''): void - { - Assert::assertEqualXMLStructure(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertThat')) { - /** - * Evaluates a PHPUnit\Framework\Constraint matcher object. - * - * @see Assert::assertThat - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertThat($value, Constraint $constraint, string $message = ''): void - { - Assert::assertThat(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertJson')) { - /** - * Asserts that a string is a valid JSON string. - * - * @see Assert::assertJson - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertJson(string $actualJson, string $message = ''): void - { - Assert::assertJson(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertJsonStringEqualsJsonString')) { - /** - * Asserts that two given JSON encoded objects or arrays are equal. - * - * @see Assert::assertJsonStringEqualsJsonString - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertJsonStringEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void - { - Assert::assertJsonStringEqualsJsonString(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertJsonStringNotEqualsJsonString')) { - /** - * Asserts that two given JSON encoded objects or arrays are not equal. - * - * @see Assert::assertJsonStringNotEqualsJsonString - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertJsonStringNotEqualsJsonString(string $expectedJson, string $actualJson, string $message = ''): void - { - Assert::assertJsonStringNotEqualsJsonString(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertJsonStringEqualsJsonFile')) { - /** - * Asserts that the generated JSON encoded object and the content of the given file are equal. - * - * @see Assert::assertJsonStringEqualsJsonFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertJsonStringEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void - { - Assert::assertJsonStringEqualsJsonFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertJsonStringNotEqualsJsonFile')) { - /** - * Asserts that the generated JSON encoded object and the content of the given file are not equal. - * - * @see Assert::assertJsonStringNotEqualsJsonFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertJsonStringNotEqualsJsonFile(string $expectedFile, string $actualJson, string $message = ''): void - { - Assert::assertJsonStringNotEqualsJsonFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertJsonFileEqualsJsonFile')) { - /** - * Asserts that two JSON files are equal. - * - * @see Assert::assertJsonFileEqualsJsonFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertJsonFileEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void - { - Assert::assertJsonFileEqualsJsonFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\assertJsonFileNotEqualsJsonFile')) { - /** - * Asserts that two JSON files are not equal. - * - * @see Assert::assertJsonFileNotEqualsJsonFile - * - * @throws ExpectationFailedException - * @throws InvalidArgumentException - */ - function assertJsonFileNotEqualsJsonFile(string $expectedFile, string $actualFile, string $message = ''): void - { - Assert::assertJsonFileNotEqualsJsonFile(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\logicalAnd')) { - function logicalAnd(): LogicalAnd - { - return Assert::logicalAnd(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\logicalOr')) { - function logicalOr(): LogicalOr - { - return Assert::logicalOr(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\logicalNot')) { - function logicalNot(Constraint $constraint): LogicalNot - { - return Assert::logicalNot(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\logicalXor')) { - function logicalXor(): LogicalXor - { - return Assert::logicalXor(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\anything')) { - function anything(): IsAnything - { - return Assert::anything(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isTrue')) { - function isTrue(): IsTrue - { - return Assert::isTrue(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\callback')) { - function callback(callable $callback): Callback - { - return Assert::callback(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isFalse')) { - function isFalse(): IsFalse - { - return Assert::isFalse(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isJson')) { - function isJson(): IsJson - { - return Assert::isJson(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isNull')) { - function isNull(): IsNull - { - return Assert::isNull(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isFinite')) { - function isFinite(): IsFinite - { - return Assert::isFinite(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isInfinite')) { - function isInfinite(): IsInfinite - { - return Assert::isInfinite(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isNan')) { - function isNan(): IsNan - { - return Assert::isNan(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\containsEqual')) { - function containsEqual($value): TraversableContainsEqual - { - return Assert::containsEqual(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\containsIdentical')) { - function containsIdentical($value): TraversableContainsIdentical - { - return Assert::containsIdentical(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\containsOnly')) { - function containsOnly(string $type): TraversableContainsOnly - { - return Assert::containsOnly(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\containsOnlyInstancesOf')) { - function containsOnlyInstancesOf(string $className): TraversableContainsOnly - { - return Assert::containsOnlyInstancesOf(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\arrayHasKey')) { - function arrayHasKey($key): ArrayHasKey - { - return Assert::arrayHasKey(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\equalTo')) { - function equalTo($value): IsEqual - { - return Assert::equalTo(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\equalToCanonicalizing')) { - function equalToCanonicalizing($value): IsEqualCanonicalizing - { - return Assert::equalToCanonicalizing(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\equalToIgnoringCase')) { - function equalToIgnoringCase($value): IsEqualIgnoringCase - { - return Assert::equalToIgnoringCase(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\equalToWithDelta')) { - function equalToWithDelta($value, float $delta): IsEqualWithDelta - { - return Assert::equalToWithDelta(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isEmpty')) { - function isEmpty(): IsEmpty - { - return Assert::isEmpty(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isWritable')) { - function isWritable(): IsWritable - { - return Assert::isWritable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isReadable')) { - function isReadable(): IsReadable - { - return Assert::isReadable(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\directoryExists')) { - function directoryExists(): DirectoryExists - { - return Assert::directoryExists(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\fileExists')) { - function fileExists(): FileExists - { - return Assert::fileExists(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\greaterThan')) { - function greaterThan($value): GreaterThan - { - return Assert::greaterThan(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\greaterThanOrEqual')) { - function greaterThanOrEqual($value): LogicalOr - { - return Assert::greaterThanOrEqual(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\classHasAttribute')) { - function classHasAttribute(string $attributeName): ClassHasAttribute - { - return Assert::classHasAttribute(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\classHasStaticAttribute')) { - function classHasStaticAttribute(string $attributeName): ClassHasStaticAttribute - { - return Assert::classHasStaticAttribute(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\objectHasAttribute')) { - function objectHasAttribute($attributeName): ObjectHasAttribute - { - return Assert::objectHasAttribute(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\identicalTo')) { - function identicalTo($value): IsIdentical - { - return Assert::identicalTo(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isInstanceOf')) { - function isInstanceOf(string $className): IsInstanceOf - { - return Assert::isInstanceOf(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\isType')) { - function isType(string $type): IsType - { - return Assert::isType(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\lessThan')) { - function lessThan($value): LessThan - { - return Assert::lessThan(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\lessThanOrEqual')) { - function lessThanOrEqual($value): LogicalOr - { - return Assert::lessThanOrEqual(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\matchesRegularExpression')) { - function matchesRegularExpression(string $pattern): RegularExpression - { - return Assert::matchesRegularExpression(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\matches')) { - function matches(string $string): StringMatchesFormatDescription - { - return Assert::matches(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\stringStartsWith')) { - function stringStartsWith($prefix): StringStartsWith - { - return Assert::stringStartsWith(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\stringContains')) { - function stringContains(string $string, bool $case = true): StringContains - { - return Assert::stringContains(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\stringEndsWith')) { - function stringEndsWith(string $suffix): StringEndsWith - { - return Assert::stringEndsWith(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\countOf')) { - function countOf(int $count): Count - { - return Assert::countOf(...func_get_args()); - } -} - -if (! function_exists('PHPUnit\Framework\any')) { - /** - * Returns a matcher that matches when the method is executed - * zero or more times. - */ - function any(): AnyInvokedCountMatcher - { - return new AnyInvokedCountMatcher(); - } -} - -if (! function_exists('PHPUnit\Framework\never')) { - /** - * Returns a matcher that matches when the method is never executed. - */ - function never(): InvokedCountMatcher - { - return new InvokedCountMatcher(0); - } -} - -if (! function_exists('PHPUnit\Framework\atLeast')) { - /** - * Returns a matcher that matches when the method is executed - * at least N times. - */ - function atLeast(int $requiredInvocations): InvokedAtLeastCountMatcher - { - return new InvokedAtLeastCountMatcher( - $requiredInvocations, - ); - } -} - -if (! function_exists('PHPUnit\Framework\atLeastOnce')) { - /** - * Returns a matcher that matches when the method is executed at least once. - */ - function atLeastOnce(): InvokedAtLeastOnceMatcher - { - return new InvokedAtLeastOnceMatcher(); - } -} - -if (! function_exists('PHPUnit\Framework\once')) { - /** - * Returns a matcher that matches when the method is executed exactly once. - */ - function once(): InvokedCountMatcher - { - return new InvokedCountMatcher(1); - } -} - -if (! function_exists('PHPUnit\Framework\exactly')) { - /** - * Returns a matcher that matches when the method is executed - * exactly $count times. - */ - function exactly(int $count): InvokedCountMatcher - { - return new InvokedCountMatcher($count); - } -} - -if (! function_exists('PHPUnit\Framework\atMost')) { - /** - * Returns a matcher that matches when the method is executed - * at most N times. - */ - function atMost(int $allowedInvocations): InvokedAtMostCountMatcher - { - return new InvokedAtMostCountMatcher($allowedInvocations); - } -} - -if (! function_exists('PHPUnit\Framework\at')) { - /** - * Returns a matcher that matches when the method is executed - * at the given index. - */ - function at(int $index): InvokedAtIndexMatcher - { - return new InvokedAtIndexMatcher($index); - } -} - -if (! function_exists('PHPUnit\Framework\returnValue')) { - function returnValue($value): ReturnStub - { - return new ReturnStub($value); - } -} - -if (! function_exists('PHPUnit\Framework\returnValueMap')) { - function returnValueMap(array $valueMap): ReturnValueMapStub - { - return new ReturnValueMapStub($valueMap); - } -} - -if (! function_exists('PHPUnit\Framework\returnArgument')) { - function returnArgument(int $argumentIndex): ReturnArgumentStub - { - return new ReturnArgumentStub($argumentIndex); - } -} - -if (! function_exists('PHPUnit\Framework\returnCallback')) { - function returnCallback($callback): ReturnCallbackStub - { - return new ReturnCallbackStub($callback); - } -} - -if (! function_exists('PHPUnit\Framework\returnSelf')) { - /** - * Returns the current object. - * - * This method is useful when mocking a fluent interface. - */ - function returnSelf(): ReturnSelfStub - { - return new ReturnSelfStub(); - } -} - -if (! function_exists('PHPUnit\Framework\throwException')) { - function throwException(Throwable $exception): ExceptionStub - { - return new ExceptionStub($exception); - } -} - -if (! function_exists('PHPUnit\Framework\onConsecutiveCalls')) { - function onConsecutiveCalls(): ConsecutiveCallsStub - { - $args = func_get_args(); - - return new ConsecutiveCallsStub($args); - } -} From 4c2b2f50437f3eff6ffac97e73414b8f571a6a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 13 Sep 2024 14:26:38 +0200 Subject: [PATCH 79/95] Use `match` instead of `switch` when a simple value is returned (#1393) --- rector.php | 9 +- src/Builder/Encoder/OperatorEncoder.php | 33 +++---- .../Prose22_RangeExplicitEncryptionTest.php | 29 ++---- tests/SpecTests/CommandExpectations.php | 28 ++---- tests/SpecTests/Context.php | 34 ++----- tests/SpecTests/FunctionalTestCase.php | 35 +++---- .../Constraint/IsBsonType.php | 97 +++++-------------- tests/UnifiedSpecTests/Context.php | 37 ++----- tests/UnifiedSpecTests/UnifiedTestRunner.php | 29 ++---- 9 files changed, 99 insertions(+), 232 deletions(-) diff --git a/rector.php b/rector.php index 0a8995c7b..a51182ee7 100644 --- a/rector.php +++ b/rector.php @@ -3,7 +3,7 @@ use Rector\Config\RectorConfig; use Rector\DeadCode\Rector\ClassLike\RemoveAnnotationRector; use Rector\Php70\Rector\StmtsAwareInterface\IfIssetToCoalescingRector; -use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector; +use Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector; use Rector\Set\ValueObject\LevelSetList; return static function (RectorConfig $rectorConfig): void { @@ -17,13 +17,14 @@ // Modernize code $rectorConfig->sets([LevelSetList::UP_TO_PHP_74]); + $rectorConfig->rule(ChangeSwitchToMatchRector::class); + // phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified $rectorConfig->skip([ // Do not use ternaries extensively IfIssetToCoalescingRector::class, - // Not necessary in documentation examples - JsonThrowOnErrorRector::class => [ - __DIR__ . '/tests/DocumentationExamplesTest.php', + ChangeSwitchToMatchRector::class => [ + __DIR__ . '/tests/SpecTests/Operation.php', ], ]); // phpcs:enable diff --git a/src/Builder/Encoder/OperatorEncoder.php b/src/Builder/Encoder/OperatorEncoder.php index f52ad392a..69ef27c24 100644 --- a/src/Builder/Encoder/OperatorEncoder.php +++ b/src/Builder/Encoder/OperatorEncoder.php @@ -38,27 +38,14 @@ public function encode(mixed $value): stdClass throw UnsupportedValueException::invalidEncodableValue($value); } - switch ($value::ENCODE) { - case Encode::Single: - return $this->encodeAsSingle($value); - - case Encode::Array: - return $this->encodeAsArray($value); - - case Encode::Object: - case Encode::FlatObject: - return $this->encodeAsObject($value); - - case Encode::DollarObject: - return $this->encodeAsDollarObject($value); - - case Encode::Group: - assert($value instanceof GroupStage); - - return $this->encodeAsGroup($value); - } - - throw new LogicException(sprintf('Class "%s" does not have a valid ENCODE constant.', $value::class)); + return match ($value::ENCODE) { + Encode::Single => $this->encodeAsSingle($value), + Encode::Array => $this->encodeAsArray($value), + Encode::Object, Encode::FlatObject => $this->encodeAsObject($value), + Encode::DollarObject => $this->encodeAsDollarObject($value), + Encode::Group => $this->encodeAsGroup($value), + default => throw new LogicException(sprintf('Class "%s" does not have a valid ENCODE constant.', $value::class)), + }; } /** @@ -111,8 +98,10 @@ private function encodeAsDollarObject(OperatorInterface $value): stdClass /** * $group stage have a specific encoding because the _id argument is required and others are variadic */ - private function encodeAsGroup(GroupStage $value): stdClass + private function encodeAsGroup(OperatorInterface $value): stdClass { + assert($value instanceof GroupStage); + $result = new stdClass(); $result->_id = $this->recursiveEncode($value->_id); diff --git a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php b/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php index aebfa6303..0c80cf2e0 100644 --- a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php +++ b/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php @@ -451,26 +451,13 @@ private function assertMultipleDocumentsMatch(array $expectedDocuments, Iterator private static function getCastCallableForType(string $type): callable { - switch ($type) { - case 'DecimalNoPrecision': - case 'DecimalPrecision': - return fn (int $value) => new Decimal128((string) $value); - - case 'DoubleNoPrecision': - case 'DoublePrecision': - return fn (int $value) => (double) $value; - - case 'Date': - return fn (int $value) => new UTCDateTime($value); - - case 'Int': - return fn (int $value) => $value; - - case 'Long': - return fn (int $value) => new Int64($value); - - default: - throw new LogicException('Unsupported type: ' . $type); - } + return match ($type) { + 'DecimalNoPrecision', 'DecimalPrecision' => fn (int $value) => new Decimal128((string) $value), + 'DoubleNoPrecision', 'DoublePrecision' => fn (int $value) => (double) $value, + 'Date' => fn (int $value) => new UTCDateTime($value), + 'Int' => fn (int $value) => $value, + 'Long' => fn (int $value) => new Int64($value), + default => throw new LogicException('Unsupported type: ' . $type), + }; } } diff --git a/tests/SpecTests/CommandExpectations.php b/tests/SpecTests/CommandExpectations.php index 7c9a946f0..8adc8cd5c 100644 --- a/tests/SpecTests/CommandExpectations.php +++ b/tests/SpecTests/CommandExpectations.php @@ -40,25 +40,15 @@ class CommandExpectations implements CommandSubscriber private function __construct(private Client $observedClient, array $events) { foreach ($events as $event) { - switch (key((array) $event)) { - case 'command_failed_event': - // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps - $this->expectedEvents[] = [$event->command_failed_event, CommandFailedEvent::class]; - break; - - case 'command_started_event': - // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps - $this->expectedEvents[] = [$event->command_started_event, CommandStartedEvent::class]; - break; - - case 'command_succeeded_event': - // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps - $this->expectedEvents[] = [$event->command_succeeded_event, CommandSucceededEvent::class]; - break; - - default: - throw new LogicException('Unsupported event type: ' . key($event)); - } + $this->expectedEvents[] = match (key((array) $event)) { + // phpcs:disable Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps + 'command_failed_event' => [$event->command_failed_event, CommandFailedEvent::class], + // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps + 'command_started_event' => [$event->command_started_event, CommandStartedEvent::class], + // phpcs:ignore Squiz.NamingConventions.ValidVariableName.MemberNotCamelCaps + 'command_succeeded_event' => [$event->command_succeeded_event, CommandSucceededEvent::class], + default => throw new LogicException('Unsupported event type: ' . key($event)), + }; } } diff --git a/tests/SpecTests/Context.php b/tests/SpecTests/Context.php index 1341e65da..c3704900f 100644 --- a/tests/SpecTests/Context.php +++ b/tests/SpecTests/Context.php @@ -358,18 +358,11 @@ public function replaceArgumentSessionPlaceholder(array &$args): void return; } - switch ($args['session']) { - case 'session0': - $args['session'] = $this->session0; - break; - - case 'session1': - $args['session'] = $this->session1; - break; - - default: - throw new LogicException('Unsupported session placeholder: ' . $args['session']); - } + $args['session'] = match ($args['session']) { + 'session0' => $this->session0, + 'session1' => $this->session1, + default => throw new LogicException('Unsupported session placeholder: ' . $args['session']), + }; } /** @@ -386,18 +379,11 @@ public function replaceCommandSessionPlaceholder(stdClass $command): void return; } - switch ($command->lsid) { - case 'session0': - $command->lsid = $this->session0Lsid; - break; - - case 'session1': - $command->lsid = $this->session1Lsid; - break; - - default: - throw new LogicException('Unsupported session placeholder: ' . $command->lsid); - } + $command->lsid = match ($command->lsid) { + 'session0' => $this->session0Lsid, + 'session1' => $this->session1Lsid, + default => throw new LogicException('Unsupported session placeholder: ' . $command->lsid), + }; } public function selectCollection($databaseName, $collectionName, array $collectionOptions = [], array $databaseOptions = []) diff --git a/tests/SpecTests/FunctionalTestCase.php b/tests/SpecTests/FunctionalTestCase.php index cd02f5908..b4ca35a9e 100644 --- a/tests/SpecTests/FunctionalTestCase.php +++ b/tests/SpecTests/FunctionalTestCase.php @@ -126,18 +126,11 @@ protected function assertOutcomeCollectionData(array $expectedDocuments, int $re $this->assertNotNull($expectedDocument); $this->assertNotNull($actualDocument); - switch ($resultExpectation) { - case ResultExpectation::ASSERT_SAME_DOCUMENT: - $this->assertSameDocument($expectedDocument, $actualDocument); - break; - - case ResultExpectation::ASSERT_DOCUMENTS_MATCH: - $this->assertDocumentsMatch($expectedDocument, $actualDocument); - break; - - default: - $this->fail(sprintf('Invalid result expectation "%d" for %s', $resultExpectation, __METHOD__)); - } + match ($resultExpectation) { + ResultExpectation::ASSERT_SAME_DOCUMENT => $this->assertSameDocument($expectedDocument, $actualDocument), + ResultExpectation::ASSERT_DOCUMENTS_MATCH => $this->assertDocumentsMatch($expectedDocument, $actualDocument), + default => $this->fail(sprintf('Invalid result expectation "%d" for %s', $resultExpectation, __METHOD__)), + }; } } @@ -284,18 +277,12 @@ private function isServerlessRequirementSatisfied(?string $serverlessMode): bool return true; } - switch ($serverlessMode) { - case self::SERVERLESS_ALLOW: - return true; - - case self::SERVERLESS_FORBID: - return ! static::isServerless(); - - case self::SERVERLESS_REQUIRE: - return static::isServerless(); - } - - throw new UnexpectedValueException(sprintf('Invalid serverless requirement "%s" found.', $serverlessMode)); + return match ($serverlessMode) { + self::SERVERLESS_ALLOW => true, + self::SERVERLESS_FORBID => ! static::isServerless(), + self::SERVERLESS_REQUIRE => static::isServerless(), + default => throw new UnexpectedValueException(sprintf('Invalid serverless requirement "%s" found.', $serverlessMode)), + }; } /** diff --git a/tests/UnifiedSpecTests/Constraint/IsBsonType.php b/tests/UnifiedSpecTests/Constraint/IsBsonType.php index e2f73e807..7d17a9af7 100644 --- a/tests/UnifiedSpecTests/Constraint/IsBsonType.php +++ b/tests/UnifiedSpecTests/Constraint/IsBsonType.php @@ -90,77 +90,32 @@ public static function anyOf(string ...$types): Constraint private function doMatches($other): bool { - switch ($this->type) { - case 'double': - return is_float($other); - - case 'string': - return is_string($other); - - case 'object': - return self::isObject($other); - - case 'array': - return self::isArray($other); - - case 'binData': - return $other instanceof BinaryInterface; - - case 'undefined': - return $other instanceof Undefined; - - case 'objectId': - return $other instanceof ObjectIdInterface; - - case 'bool': - return is_bool($other); - - case 'date': - return $other instanceof UTCDateTimeInterface; - - case 'null': - return $other === null; - - case 'regex': - return $other instanceof RegexInterface; - - case 'dbPointer': - return $other instanceof DBPointer; - - case 'javascript': - return $other instanceof JavascriptInterface && $other->getScope() === null; - - case 'symbol': - return $other instanceof Symbol; - - case 'javascriptWithScope': - return $other instanceof JavascriptInterface && $other->getScope() !== null; - - case 'int': - return is_int($other); - - case 'timestamp': - return $other instanceof TimestampInterface; - - case 'long': - return is_int($other) || $other instanceof Int64; - - case 'decimal': - return $other instanceof Decimal128Interface; - - case 'minKey': - return $other instanceof MinKeyInterface; - - case 'maxKey': - return $other instanceof MaxKeyInterface; - - case 'number': - return is_int($other) || $other instanceof Int64 || is_float($other) || $other instanceof Decimal128Interface; - - default: - // This should already have been caught in the constructor - throw new LogicException('Unsupported type: ' . $this->type); - } + return match ($this->type) { + 'double' => is_float($other), + 'string' => is_string($other), + 'object' => self::isObject($other), + 'array' => self::isArray($other), + 'binData' => $other instanceof BinaryInterface, + 'undefined' => $other instanceof Undefined, + 'objectId' => $other instanceof ObjectIdInterface, + 'bool' => is_bool($other), + 'date' => $other instanceof UTCDateTimeInterface, + 'null' => $other === null, + 'regex' => $other instanceof RegexInterface, + 'dbPointer' => $other instanceof DBPointer, + 'javascript' => $other instanceof JavascriptInterface && $other->getScope() === null, + 'symbol' => $other instanceof Symbol, + 'javascriptWithScope' => $other instanceof JavascriptInterface && $other->getScope() !== null, + 'int' => is_int($other), + 'timestamp' => $other instanceof TimestampInterface, + 'long' => is_int($other) || $other instanceof Int64, + 'decimal' => $other instanceof Decimal128Interface, + 'minKey' => $other instanceof MinKeyInterface, + 'maxKey' => $other instanceof MaxKeyInterface, + 'number' => is_int($other) || $other instanceof Int64 || is_float($other) || $other instanceof Decimal128Interface, + // This should already have been caught in the constructor + default => throw new LogicException('Unsupported type: ' . $this->type), + }; } private function doToString(): string diff --git a/tests/UnifiedSpecTests/Context.php b/tests/UnifiedSpecTests/Context.php index 2210cad75..56f68adc0 100644 --- a/tests/UnifiedSpecTests/Context.php +++ b/tests/UnifiedSpecTests/Context.php @@ -95,34 +95,15 @@ public function createEntities(array $entities): void $id = $def->id ?? null; assertIsString($id); - switch ($type) { - case 'client': - $this->createClient($id, $def); - break; - - case 'clientEncryption': - $this->createClientEncryption($id, $def); - break; - - case 'database': - $this->createDatabase($id, $def); - break; - - case 'collection': - $this->createCollection($id, $def); - break; - - case 'session': - $this->createSession($id, $def); - break; - - case 'bucket': - $this->createBucket($id, $def); - break; - - default: - throw new LogicException('Unsupported entity type: ' . $type); - } + match ($type) { + 'client' => $this->createClient($id, $def), + 'clientEncryption' => $this->createClientEncryption($id, $def), + 'database' => $this->createDatabase($id, $def), + 'collection' => $this->createCollection($id, $def), + 'session' => $this->createSession($id, $def), + 'bucket' => $this->createBucket($id, $def), + default => throw new LogicException('Unsupported entity type: ' . $type), + }; } } diff --git a/tests/UnifiedSpecTests/UnifiedTestRunner.php b/tests/UnifiedSpecTests/UnifiedTestRunner.php index 605eb2cac..39ea3832d 100644 --- a/tests/UnifiedSpecTests/UnifiedTestRunner.php +++ b/tests/UnifiedSpecTests/UnifiedTestRunner.php @@ -299,25 +299,16 @@ private function getServerVersion(): string */ private function getTopology(): string { - switch ($this->getPrimaryServer()->getType()) { - case Server::TYPE_STANDALONE: - return RunOnRequirement::TOPOLOGY_SINGLE; - - case Server::TYPE_RS_PRIMARY: - return RunOnRequirement::TOPOLOGY_REPLICASET; - - case Server::TYPE_MONGOS: - /* Since MongoDB 3.6, all sharded clusters use replica sets. The - * unified test format deprecated use of "sharded-replicaset" in - * tests but we should still identify as such. */ - return RunOnRequirement::TOPOLOGY_SHARDED_REPLICASET; - - case Server::TYPE_LOAD_BALANCER: - return RunOnRequirement::TOPOLOGY_LOAD_BALANCED; - - default: - throw new UnexpectedValueException('Topology is neither single nor RS nor sharded'); - } + return match ($this->getPrimaryServer()->getType()) { + Server::TYPE_STANDALONE => RunOnRequirement::TOPOLOGY_SINGLE, + Server::TYPE_RS_PRIMARY => RunOnRequirement::TOPOLOGY_REPLICASET, + /* Since MongoDB 3.6, all sharded clusters use replica sets. The + * unified test format deprecated use of "sharded-replicaset" in + * tests but we should still identify as such. */ + Server::TYPE_MONGOS => RunOnRequirement::TOPOLOGY_SHARDED_REPLICASET, + Server::TYPE_LOAD_BALANCER => RunOnRequirement::TOPOLOGY_LOAD_BALANCED, + default => throw new UnexpectedValueException('Topology is neither single nor RS nor sharded'), + }; } private function isAtlasDataLake(): bool From 0ed8dff705349eecb5b0a6513235552fd72603d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 13 Sep 2024 15:08:21 +0200 Subject: [PATCH 80/95] Remove Prose22_RangeExplicitEncryptionTest that requires ext-mongodb < 1.20 (#1394) --- .../Prose22_RangeExplicitEncryptionTest.php | 463 ------------------ .../ClientSideEncryptionSpecTest.php | 7 - 2 files changed, 470 deletions(-) delete mode 100644 tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php diff --git a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php b/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php deleted file mode 100644 index 0c80cf2e0..000000000 --- a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php +++ /dev/null @@ -1,463 +0,0 @@ -=')) { - $this->markTestIncomplete('Range protocol V1 is not supported by ext-mongodb 1.20+'); - } - - if ($this->isStandalone()) { - $this->markTestSkipped('Range explicit encryption tests require replica sets'); - } - - $this->skipIfServerVersion('<', '8.0.0', 'Range explicit encryption tests require MongoDB 8.0 or later'); - - $client = static::createTestClient(); - - $key1Document = $this->decodeJson(file_get_contents(__DIR__ . '/../client-side-encryption/etc/data/keys/key1-document.json')); - $this->key1Id = $key1Document->_id; - - // Drop the key vault collection and insert key1Document with a majority write concern - self::insertKeyVaultData($client, [$key1Document]); - - $this->clientEncryption = $client->createClientEncryption([ - 'keyVaultNamespace' => 'keyvault.datakeys', - 'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY))]], - ]); - - $autoEncryptionOpts = [ - 'keyVaultNamespace' => 'keyvault.datakeys', - 'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY))]], - 'bypassQueryAnalysis' => true, - ]; - - $this->encryptedClient = self::createTestClient(null, [], [ - 'autoEncryption' => $autoEncryptionOpts, - /* libmongocrypt caches results from listCollections. Use a new - * client in each test to ensure its encryptedFields is applied. */ - 'disableClientPersistence' => true, - ]); - } - - public function setUpWithTypeAndRangeOpts(string $type, array $rangeOpts): void - { - if ($type === 'DecimalNoPrecision' || $type === 'DecimalPrecision') { - $this->markTestSkipped('Bundled libmongocrypt does not support Decimal128 (PHPC-2207)'); - } - - /* Read the encryptedFields file directly into BSON to preserve typing - * for 64-bit integers. This means that DropEncryptedCollection and - * CreateEncryptedCollection will be unable to inspect the option for - * metadata collection names, but that's not necessary for the test. */ - $encryptedFields = Document::fromJSON(file_get_contents(__DIR__ . '/../client-side-encryption/etc/data/range-encryptedFields-' . $type . '.json')); - - $database = $this->encryptedClient->selectDatabase($this->getDatabaseName()); - $database->dropCollection('explicit_encryption', ['encryptedFields' => $encryptedFields]); - $database->createCollection('explicit_encryption', ['encryptedFields' => $encryptedFields]); - $this->collection = $database->selectCollection('explicit_encryption'); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $fieldName = 'encrypted' . $type; - - $this->collection->insertMany([ - ['_id' => 0, $fieldName => $this->clientEncryption->encrypt($cast(0), $encryptOpts)], - ['_id' => 1, $fieldName => $this->clientEncryption->encrypt($cast(6), $encryptOpts)], - ['_id' => 2, $fieldName => $this->clientEncryption->encrypt($cast(30), $encryptOpts)], - ['_id' => 3, $fieldName => $this->clientEncryption->encrypt($cast(200), $encryptOpts)], - ]); - } - - public function tearDown(): void - { - /* Since encryptedClient is created with disableClientPersistence=true, - * free any objects that may hold a reference to its mongoc_client_t */ - $this->collection = null; - $this->clientEncryption = null; - $this->encryptedClient = null; - } - - /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#test-setup-rangeopts */ - public static function provideTypeAndRangeOpts(): Generator - { - // TODO: skip DecimalNoPrecision test on mongos - yield 'DecimalNoPrecision' => [ - 'DecimalNoPrecision', - ['sparsity' => 1], - ]; - - yield 'DecimalPrecision' => [ - 'DecimalPrecision', - [ - 'min' => new Decimal128('0'), - 'max' => new Decimal128('200'), - 'sparsity' => 1, - 'precision' => 2, - ], - ]; - - yield 'DoubleNoPrecision' => [ - 'DoubleNoPrecision', - ['sparsity' => 1], - ]; - - yield 'DoublePrecision' => [ - 'DoublePrecision', - [ - 'min' => 0.0, - 'max' => 200.0, - 'sparsity' => 1, - 'precision' => 2, - ], - ]; - - yield 'Date' => [ - 'Date', - [ - 'min' => new UTCDateTime(0), - 'max' => new UTCDateTime(200), - 'sparsity' => 1, - ], - ]; - - yield 'Int' => [ - 'Int', - [ - 'min' => 0, - 'max' => 200, - 'sparsity' => 1, - ], - ]; - - yield 'Long' => [ - 'Long', - [ - 'min' => new Int64(0), - 'max' => new Int64(200), - 'sparsity' => 1, - ], - ]; - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-1-can-decrypt-a-payload - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase1_CanDecryptAPayload(string $type, array $rangeOpts): void - { - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $originalValue = $cast(6); - - $insertPayload = $this->clientEncryption->encrypt($originalValue, $encryptOpts); - $decryptedValue = $this->clientEncryption->decrypt($insertPayload); - - /* Decryption of a 64-bit integer will likely result in a scalar int, so - * cast it back to an Int64 before comparing to the original value. */ - if ($type === 'Long' && is_int($decryptedValue)) { - $decryptedValue = $cast($decryptedValue); - } - - /* Use separate assertions for type and equality as assertSame isn't - * suitable for comparing BSON objects and using assertEquals alone - * would disregard scalar type differences. */ - $this->assertSame(get_debug_type($originalValue), get_debug_type($decryptedValue)); - $this->assertEquals($originalValue, $decryptedValue); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-2-can-find-encrypted-range-and-return-the-maximum - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase2_CanFindEncryptedRangeAndReturnTheMaximum(string $type, array $rangeOpts): void - { - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $fieldName = 'encrypted' . $type; - - $expr = [ - '$and' => [ - [$fieldName => ['$gte' => $cast(6)]], - [$fieldName => ['$lte' => $cast(200)]], - ], - ]; - - $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); - $cursor = $this->collection->find($encryptedExpr, ['sort' => ['_id' => 1]]); - - $expectedDocuments = [ - ['_id' => 1, $fieldName => $cast(6)], - ['_id' => 2, $fieldName => $cast(30)], - ['_id' => 3, $fieldName => $cast(200)], - ]; - - $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-3-can-find-encrypted-range-and-return-the-minimum - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase3_CanFindEncryptedRangeAndReturnTheMinimum(string $type, array $rangeOpts): void - { - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $fieldName = 'encrypted' . $type; - - $expr = [ - '$and' => [ - [$fieldName => ['$gte' => $cast(0)]], - [$fieldName => ['$lte' => $cast(6)]], - ], - ]; - - $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); - $cursor = $this->collection->find($encryptedExpr, ['sort' => ['_id' => 1]]); - - $expectedDocuments = [ - ['_id' => 0, $fieldName => $cast(0)], - ['_id' => 1, $fieldName => $cast(6)], - ]; - - $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-4-can-find-encrypted-range-with-an-open-range-query - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase4_CanFindEncryptedRangeWithAnOpenRangeQuery(string $type, array $rangeOpts): void - { - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $fieldName = 'encrypted' . $type; - - $expr = ['$and' => [[$fieldName => ['$gt' => $cast(30)]]]]; - - $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); - $cursor = $this->collection->find($encryptedExpr, ['sort' => ['_id' => 1]]); - $expectedDocuments = [['_id' => 3, $fieldName => $cast(200)]]; - - $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-5-can-run-an-aggregation-expression-inside-expr - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase5_CanRunAnAggregationExpressionInsideExpr(string $type, array $rangeOpts): void - { - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - $fieldName = 'encrypted' . $type; - $fieldPath = '$' . $fieldName; - - $expr = ['$and' => [['$lt' => [$fieldPath, $cast(30)]]]]; - - $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); - $cursor = $this->collection->find(['$expr' => $encryptedExpr], ['sort' => ['_id' => 1]]); - - $expectedDocuments = [ - ['_id' => 0, $fieldName => $cast(0)], - ['_id' => 1, $fieldName => $cast(6)], - ]; - - $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-6-encrypting-a-document-greater-than-the-maximum-errors - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase6_EncryptingADocumentGreaterThanTheMaximumErrors(string $type, array $rangeOpts): void - { - if ($type === 'DecimalNoPrecision' || $type === 'DoubleNoPrecision') { - $this->markTestSkipped('Test is not applicable to "NoPrecision" types'); - } - - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $cast = self::getCastCallableForType($type); - - $this->expectException(EncryptionException::class); - $this->expectExceptionMessage('Value must be greater than or equal to the minimum value and less than or equal to the maximum value'); - $this->clientEncryption->encrypt($cast(201), $encryptOpts); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-7-encrypting-a-value-of-a-different-type-errors - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase7_EncryptingAValueOfADifferentTypeErrors(string $type, array $rangeOpts): void - { - if ($type === 'DecimalNoPrecision' || $type === 'DoubleNoPrecision') { - /* Explicit encryption relies on min/max range options to check - * types and "NoPrecision" intentionally omits those options. */ - $this->markTestSkipped('Test is not applicable to DoubleNoPrecision and DecimalNoPrecision'); - } - - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts, - ]; - - $value = $type === 'Int' ? 6.0 : 6; - - $this->expectException(EncryptionException::class); - $this->expectExceptionMessage('expected matching \'min\' and value type'); - $this->clientEncryption->encrypt($value, $encryptOpts); - } - - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-8-setting-precision-errors-if-the-type-is-not-double-or-decimal128 - * @dataProvider provideTypeAndRangeOpts - */ - public function testCase8_SettingPrecisionErrorsIfTheTypeIsNotDoubleOrDecimal128(string $type, array $rangeOpts): void - { - if ($type === 'DecimalNoPrecision' || $type === 'DecimalPrecision' || $type === 'DoubleNoPrecision' || $type === 'DoublePrecision') { - $this->markTestSkipped('Test is not applicable to Double and Decimal types'); - } - - $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); - - $encryptOpts = [ - 'keyId' => $this->key1Id, - 'algorithm' => ClientEncryption::ALGORITHM_RANGE, - 'contentionFactor' => 0, - 'rangeOpts' => $rangeOpts + ['precision' => 2], - ]; - - $cast = self::getCastCallableForType($type); - - $this->expectException(EncryptionException::class); - $this->expectExceptionMessage('expected \'precision\' to be set with double or decimal128 index'); - $this->clientEncryption->encrypt($cast(6), $encryptOpts); - } - - private function assertMultipleDocumentsMatch(array $expectedDocuments, Iterator $actualDocuments): void - { - $mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY); - $mi->attachIterator(new ArrayIterator($expectedDocuments)); - $mi->attachIterator($actualDocuments); - - foreach ($mi as $documents) { - [$expectedDocument, $actualDocument] = $documents; - $this->assertNotNull($expectedDocument); - $this->assertNotNull($actualDocument); - - $this->assertDocumentsMatch($expectedDocument, $actualDocument); - } - } - - private static function getCastCallableForType(string $type): callable - { - return match ($type) { - 'DecimalNoPrecision', 'DecimalPrecision' => fn (int $value) => new Decimal128((string) $value), - 'DoubleNoPrecision', 'DoublePrecision' => fn (int $value) => (double) $value, - 'Date' => fn (int $value) => new UTCDateTime($value), - 'Int' => fn (int $value) => $value, - 'Long' => fn (int $value) => new Int64($value), - default => throw new LogicException('Unsupported type: ' . $type), - }; - } -} diff --git a/tests/SpecTests/ClientSideEncryptionSpecTest.php b/tests/SpecTests/ClientSideEncryptionSpecTest.php index 574cf4f23..001d78a84 100644 --- a/tests/SpecTests/ClientSideEncryptionSpecTest.php +++ b/tests/SpecTests/ClientSideEncryptionSpecTest.php @@ -38,12 +38,9 @@ use function in_array; use function iterator_to_array; use function json_decode; -use function phpversion; use function sprintf; use function str_repeat; -use function str_starts_with; use function substr; -use function version_compare; use const JSON_THROW_ON_ERROR; @@ -167,10 +164,6 @@ public function testClientSideEncryption(stdClass $test, ?array $runOn, array $d $this->markTestIncomplete(self::$incompleteTests[$this->dataDescription()]); } - if (str_starts_with($this->dataDescription(), 'fle2v2-Range-') && version_compare(phpversion('mongodb'), '1.20.0dev', '>=')) { - $this->markTestIncomplete('Range protocol V1 is not supported by ext-mongodb 1.20+'); - } - if (isset($runOn)) { $this->checkServerRequirements($runOn); } From 3929135bab703442605474324680e97dea2ca428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 13 Sep 2024 20:30:33 +0200 Subject: [PATCH 81/95] Restore Prose22_RangeExplicitEncryptionTest (#1400) * Revert "Remove Prose22_RangeExplicitEncryptionTest that requires ext-mongodb < 1.20 (#1394)" This reverts commit 0ed8dff705349eecb5b0a6513235552fd72603d2. * Keep removing useless skip * Replace getCastCallableForType with a cast function --- .../Prose22_RangeExplicitEncryptionTest.php | 448 ++++++++++++++++++ 1 file changed, 448 insertions(+) create mode 100644 tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php diff --git a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php b/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php new file mode 100644 index 000000000..f805df3c0 --- /dev/null +++ b/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php @@ -0,0 +1,448 @@ +isStandalone()) { + $this->markTestSkipped('Range explicit encryption tests require replica sets'); + } + + $this->skipIfServerVersion('<', '8.0.0', 'Range explicit encryption tests require MongoDB 8.0 or later'); + + $client = static::createTestClient(); + + $key1Document = $this->decodeJson(file_get_contents(__DIR__ . '/../client-side-encryption/etc/data/keys/key1-document.json')); + $this->key1Id = $key1Document->_id; + + // Drop the key vault collection and insert key1Document with a majority write concern + self::insertKeyVaultData($client, [$key1Document]); + + $this->clientEncryption = $client->createClientEncryption([ + 'keyVaultNamespace' => 'keyvault.datakeys', + 'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY))]], + ]); + + $autoEncryptionOpts = [ + 'keyVaultNamespace' => 'keyvault.datakeys', + 'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY))]], + 'bypassQueryAnalysis' => true, + ]; + + $this->encryptedClient = self::createTestClient(null, [], [ + 'autoEncryption' => $autoEncryptionOpts, + /* libmongocrypt caches results from listCollections. Use a new + * client in each test to ensure its encryptedFields is applied. */ + 'disableClientPersistence' => true, + ]); + } + + public function setUpWithTypeAndRangeOpts(string $type, array $rangeOpts): void + { + if ($type === 'DecimalNoPrecision' || $type === 'DecimalPrecision') { + $this->markTestSkipped('Bundled libmongocrypt does not support Decimal128 (PHPC-2207)'); + } + + /* Read the encryptedFields file directly into BSON to preserve typing + * for 64-bit integers. This means that DropEncryptedCollection and + * CreateEncryptedCollection will be unable to inspect the option for + * metadata collection names, but that's not necessary for the test. */ + $encryptedFields = Document::fromJSON(file_get_contents(__DIR__ . '/../client-side-encryption/etc/data/range-encryptedFields-' . $type . '.json')); + + $database = $this->encryptedClient->selectDatabase($this->getDatabaseName()); + $database->dropCollection('explicit_encryption', ['encryptedFields' => $encryptedFields]); + $database->createCollection('explicit_encryption', ['encryptedFields' => $encryptedFields]); + $this->collection = $database->selectCollection('explicit_encryption'); + + $encryptOpts = [ + 'keyId' => $this->key1Id, + 'algorithm' => ClientEncryption::ALGORITHM_RANGE, + 'contentionFactor' => 0, + 'rangeOpts' => $rangeOpts, + ]; + + $fieldName = 'encrypted' . $type; + + $this->collection->insertMany([ + ['_id' => 0, $fieldName => $this->clientEncryption->encrypt(self::cast($type, 0), $encryptOpts)], + ['_id' => 1, $fieldName => $this->clientEncryption->encrypt(self::cast($type, 6), $encryptOpts)], + ['_id' => 2, $fieldName => $this->clientEncryption->encrypt(self::cast($type, 30), $encryptOpts)], + ['_id' => 3, $fieldName => $this->clientEncryption->encrypt(self::cast($type, 200), $encryptOpts)], + ]); + } + + public function tearDown(): void + { + /* Since encryptedClient is created with disableClientPersistence=true, + * free any objects that may hold a reference to its mongoc_client_t */ + $this->collection = null; + $this->clientEncryption = null; + $this->encryptedClient = null; + } + + /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#test-setup-rangeopts */ + public static function provideTypeAndRangeOpts(): Generator + { + // TODO: skip DecimalNoPrecision test on mongos + yield 'DecimalNoPrecision' => [ + 'DecimalNoPrecision', + ['sparsity' => 1], + ]; + + yield 'DecimalPrecision' => [ + 'DecimalPrecision', + [ + 'min' => new Decimal128('0'), + 'max' => new Decimal128('200'), + 'sparsity' => 1, + 'precision' => 2, + ], + ]; + + yield 'DoubleNoPrecision' => [ + 'DoubleNoPrecision', + ['sparsity' => 1], + ]; + + yield 'DoublePrecision' => [ + 'DoublePrecision', + [ + 'min' => 0.0, + 'max' => 200.0, + 'sparsity' => 1, + 'precision' => 2, + ], + ]; + + yield 'Date' => [ + 'Date', + [ + 'min' => new UTCDateTime(0), + 'max' => new UTCDateTime(200), + 'sparsity' => 1, + ], + ]; + + yield 'Int' => [ + 'Int', + [ + 'min' => 0, + 'max' => 200, + 'sparsity' => 1, + ], + ]; + + yield 'Long' => [ + 'Long', + [ + 'min' => new Int64(0), + 'max' => new Int64(200), + 'sparsity' => 1, + ], + ]; + } + + /** + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-1-can-decrypt-a-payload + * @dataProvider provideTypeAndRangeOpts + */ + public function testCase1_CanDecryptAPayload(string $type, array $rangeOpts): void + { + $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); + + $encryptOpts = [ + 'keyId' => $this->key1Id, + 'algorithm' => ClientEncryption::ALGORITHM_RANGE, + 'contentionFactor' => 0, + 'rangeOpts' => $rangeOpts, + ]; + + $originalValue = self::cast($type, 6); + + $insertPayload = $this->clientEncryption->encrypt($originalValue, $encryptOpts); + $decryptedValue = $this->clientEncryption->decrypt($insertPayload); + + /* Decryption of a 64-bit integer will likely result in a scalar int, so + * cast it back to an Int64 before comparing to the original value. */ + if ($type === 'Long' && is_int($decryptedValue)) { + $decryptedValue = self::cast($type, $decryptedValue); + } + + /* Use separate assertions for type and equality as assertSame isn't + * suitable for comparing BSON objects and using assertEquals alone + * would disregard scalar type differences. */ + $this->assertSame(get_debug_type($originalValue), get_debug_type($decryptedValue)); + $this->assertEquals($originalValue, $decryptedValue); + } + + /** + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-2-can-find-encrypted-range-and-return-the-maximum + * @dataProvider provideTypeAndRangeOpts + */ + public function testCase2_CanFindEncryptedRangeAndReturnTheMaximum(string $type, array $rangeOpts): void + { + $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); + + $encryptOpts = [ + 'keyId' => $this->key1Id, + 'algorithm' => ClientEncryption::ALGORITHM_RANGE, + 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, + 'contentionFactor' => 0, + 'rangeOpts' => $rangeOpts, + ]; + + $fieldName = 'encrypted' . $type; + + $expr = [ + '$and' => [ + [$fieldName => ['$gte' => self::cast($type, 6)]], + [$fieldName => ['$lte' => self::cast($type, 200)]], + ], + ]; + + $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); + $cursor = $this->collection->find($encryptedExpr, ['sort' => ['_id' => 1]]); + + $expectedDocuments = [ + ['_id' => 1, $fieldName => self::cast($type, 6)], + ['_id' => 2, $fieldName => self::cast($type, 30)], + ['_id' => 3, $fieldName => self::cast($type, 200)], + ]; + + $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); + } + + /** + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-3-can-find-encrypted-range-and-return-the-minimum + * @dataProvider provideTypeAndRangeOpts + */ + public function testCase3_CanFindEncryptedRangeAndReturnTheMinimum(string $type, array $rangeOpts): void + { + $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); + + $encryptOpts = [ + 'keyId' => $this->key1Id, + 'algorithm' => ClientEncryption::ALGORITHM_RANGE, + 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, + 'contentionFactor' => 0, + 'rangeOpts' => $rangeOpts, + ]; + + $fieldName = 'encrypted' . $type; + + $expr = [ + '$and' => [ + [$fieldName => ['$gte' => self::cast($type, 0)]], + [$fieldName => ['$lte' => self::cast($type, 6)]], + ], + ]; + + $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); + $cursor = $this->collection->find($encryptedExpr, ['sort' => ['_id' => 1]]); + + $expectedDocuments = [ + ['_id' => 0, $fieldName => self::cast($type, 0)], + ['_id' => 1, $fieldName => self::cast($type, 6)], + ]; + + $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); + } + + /** + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-4-can-find-encrypted-range-with-an-open-range-query + * @dataProvider provideTypeAndRangeOpts + */ + public function testCase4_CanFindEncryptedRangeWithAnOpenRangeQuery(string $type, array $rangeOpts): void + { + $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); + + $encryptOpts = [ + 'keyId' => $this->key1Id, + 'algorithm' => ClientEncryption::ALGORITHM_RANGE, + 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, + 'contentionFactor' => 0, + 'rangeOpts' => $rangeOpts, + ]; + + $fieldName = 'encrypted' . $type; + + $expr = ['$and' => [[$fieldName => ['$gt' => self::cast($type, 30)]]]]; + + $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); + $cursor = $this->collection->find($encryptedExpr, ['sort' => ['_id' => 1]]); + $expectedDocuments = [['_id' => 3, $fieldName => self::cast($type, 200)]]; + + $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); + } + + /** + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-5-can-run-an-aggregation-expression-inside-expr + * @dataProvider provideTypeAndRangeOpts + */ + public function testCase5_CanRunAnAggregationExpressionInsideExpr(string $type, array $rangeOpts): void + { + $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); + + $encryptOpts = [ + 'keyId' => $this->key1Id, + 'algorithm' => ClientEncryption::ALGORITHM_RANGE, + 'queryType' => ClientEncryption::QUERY_TYPE_RANGE, + 'contentionFactor' => 0, + 'rangeOpts' => $rangeOpts, + ]; + + $fieldName = 'encrypted' . $type; + $fieldPath = '$' . $fieldName; + + $expr = ['$and' => [['$lt' => [$fieldPath, self::cast($type, 30)]]]]; + + $encryptedExpr = $this->clientEncryption->encryptExpression($expr, $encryptOpts); + $cursor = $this->collection->find(['$expr' => $encryptedExpr], ['sort' => ['_id' => 1]]); + + $expectedDocuments = [ + ['_id' => 0, $fieldName => self::cast($type, 0)], + ['_id' => 1, $fieldName => self::cast($type, 6)], + ]; + + $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); + } + + /** + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-6-encrypting-a-document-greater-than-the-maximum-errors + * @dataProvider provideTypeAndRangeOpts + */ + public function testCase6_EncryptingADocumentGreaterThanTheMaximumErrors(string $type, array $rangeOpts): void + { + if ($type === 'DecimalNoPrecision' || $type === 'DoubleNoPrecision') { + $this->markTestSkipped('Test is not applicable to "NoPrecision" types'); + } + + $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); + + $encryptOpts = [ + 'keyId' => $this->key1Id, + 'algorithm' => ClientEncryption::ALGORITHM_RANGE, + 'contentionFactor' => 0, + 'rangeOpts' => $rangeOpts, + ]; + + $this->expectException(EncryptionException::class); + $this->expectExceptionMessage('Value must be greater than or equal to the minimum value and less than or equal to the maximum value'); + $this->clientEncryption->encrypt(self::cast($type, 201), $encryptOpts); + } + + /** + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-7-encrypting-a-value-of-a-different-type-errors + * @dataProvider provideTypeAndRangeOpts + */ + public function testCase7_EncryptingAValueOfADifferentTypeErrors(string $type, array $rangeOpts): void + { + if ($type === 'DecimalNoPrecision' || $type === 'DoubleNoPrecision') { + /* Explicit encryption relies on min/max range options to check + * types and "NoPrecision" intentionally omits those options. */ + $this->markTestSkipped('Test is not applicable to DoubleNoPrecision and DecimalNoPrecision'); + } + + $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); + + $encryptOpts = [ + 'keyId' => $this->key1Id, + 'algorithm' => ClientEncryption::ALGORITHM_RANGE, + 'contentionFactor' => 0, + 'rangeOpts' => $rangeOpts, + ]; + + $value = $type === 'Int' ? 6.0 : 6; + + $this->expectException(EncryptionException::class); + $this->expectExceptionMessage('expected matching \'min\' and value type'); + $this->clientEncryption->encrypt($value, $encryptOpts); + } + + /** + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-8-setting-precision-errors-if-the-type-is-not-double-or-decimal128 + * @dataProvider provideTypeAndRangeOpts + */ + public function testCase8_SettingPrecisionErrorsIfTheTypeIsNotDoubleOrDecimal128(string $type, array $rangeOpts): void + { + if ($type === 'DecimalNoPrecision' || $type === 'DecimalPrecision' || $type === 'DoubleNoPrecision' || $type === 'DoublePrecision') { + $this->markTestSkipped('Test is not applicable to Double and Decimal types'); + } + + $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); + + $encryptOpts = [ + 'keyId' => $this->key1Id, + 'algorithm' => ClientEncryption::ALGORITHM_RANGE, + 'contentionFactor' => 0, + 'rangeOpts' => $rangeOpts + ['precision' => 2], + ]; + + $this->expectException(EncryptionException::class); + $this->expectExceptionMessage('expected \'precision\' to be set with double or decimal128 index'); + $this->clientEncryption->encrypt(self::cast($type, 6), $encryptOpts); + } + + private function assertMultipleDocumentsMatch(array $expectedDocuments, Iterator $actualDocuments): void + { + $mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY); + $mi->attachIterator(new ArrayIterator($expectedDocuments)); + $mi->attachIterator($actualDocuments); + + foreach ($mi as $documents) { + [$expectedDocument, $actualDocument] = $documents; + $this->assertNotNull($expectedDocument); + $this->assertNotNull($actualDocument); + + $this->assertDocumentsMatch($expectedDocument, $actualDocument); + } + } + + private static function cast(string $type, int $value): mixed + { + return match ($type) { + 'DecimalNoPrecision', 'DecimalPrecision' => new Decimal128((string) $value), + 'DoubleNoPrecision', 'DoublePrecision' => (double) $value, + 'Date' => new UTCDateTime($value), + 'Int' => $value, + 'Long' => new Int64($value), + default => throw new LogicException('Unsupported type: ' . $type), + }; + } +} From ae90e894e7576a95c100aa99e7ed26af9b4c9fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 13 Sep 2024 23:31:55 +0200 Subject: [PATCH 82/95] PHPLIB-1515 Replace assertObjectHasAttribute with assertObjectHasProperty (#1405) https://github.com/sebastianbergmann/phpunit/blob/9.6/DEPRECATIONS.md --- tests/Collection/CollectionFunctionalTest.php | 18 +++---- tests/Database/DatabaseFunctionalTest.php | 2 +- tests/DocumentationExamplesTest.php | 48 +++++++++---------- tests/Operation/AggregateFunctionalTest.php | 18 +++---- tests/Operation/BulkWriteFunctionalTest.php | 6 +-- tests/Operation/CountFunctionalTest.php | 4 +- .../CreateCollectionFunctionalTest.php | 4 +- .../Operation/CreateIndexesFunctionalTest.php | 6 +-- .../DatabaseCommandFunctionalTest.php | 2 +- tests/Operation/DeleteFunctionalTest.php | 2 +- tests/Operation/DistinctFunctionalTest.php | 4 +- .../DropCollectionFunctionalTest.php | 4 +- .../Operation/DropDatabaseFunctionalTest.php | 4 +- tests/Operation/DropIndexesFunctionalTest.php | 4 +- tests/Operation/ExplainFunctionalTest.php | 14 +++--- .../Operation/FindAndModifyFunctionalTest.php | 10 ++-- tests/Operation/FindFunctionalTest.php | 4 +- tests/Operation/InsertManyFunctionalTest.php | 6 +-- tests/Operation/InsertOneFunctionalTest.php | 6 +-- .../ListCollectionNamesFunctionalTest.php | 4 +- .../ListCollectionsFunctionalTest.php | 4 +- .../ListDatabaseNamesFunctionalTest.php | 6 +-- .../Operation/ListDatabasesFunctionalTest.php | 6 +-- tests/Operation/ListIndexesFunctionalTest.php | 2 +- tests/Operation/MapReduceFunctionalTest.php | 10 ++-- .../RenameCollectionFunctionalTest.php | 4 +- tests/Operation/UpdateFunctionalTest.php | 6 +-- tests/Operation/WatchFunctionalTest.php | 30 ++++++------ tests/SpecTests/AtlasDataLakeSpecTest.php | 12 ++--- .../ClientSideEncryptionSpecTest.php | 14 +++--- tests/SpecTests/ErrorExpectation.php | 4 +- tests/SpecTests/FunctionalTestCase.php | 2 +- tests/UnifiedSpecTests/EventObserver.php | 4 +- tests/UnifiedSpecTests/ExpectedError.php | 6 +-- tests/UnifiedSpecTests/Operation.php | 6 +-- 35 files changed, 143 insertions(+), 143 deletions(-) diff --git a/tests/Collection/CollectionFunctionalTest.php b/tests/Collection/CollectionFunctionalTest.php index e4f6c649a..62d17d8cb 100644 --- a/tests/Collection/CollectionFunctionalTest.php +++ b/tests/Collection/CollectionFunctionalTest.php @@ -157,13 +157,13 @@ function (): void { }, function (array $event): void { $command = $event['started']->getCommand(); - $this->assertObjectHasAttribute('comment', $command); - $this->assertObjectHasAttribute('commitQuorum', $command); - $this->assertObjectHasAttribute('lsid', $command); - $this->assertObjectHasAttribute('maxTimeMS', $command); - $this->assertObjectHasAttribute('writeConcern', $command); - $this->assertObjectHasAttribute('sparse', $command->indexes[0]); - $this->assertObjectHasAttribute('unique', $command->indexes[0]); + $this->assertObjectHasProperty('comment', $command); + $this->assertObjectHasProperty('commitQuorum', $command); + $this->assertObjectHasProperty('lsid', $command); + $this->assertObjectHasProperty('maxTimeMS', $command); + $this->assertObjectHasProperty('writeConcern', $command); + $this->assertObjectHasProperty('sparse', $command->indexes[0]); + $this->assertObjectHasProperty('unique', $command->indexes[0]); }, ); } @@ -752,8 +752,8 @@ function () use ($method, $collection, $session): void { call_user_func($method, $collection, $session); }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); - $this->assertObjectNotHasAttribute('readConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('readConcern', $event['started']->getCommand()); }, ); } diff --git a/tests/Database/DatabaseFunctionalTest.php b/tests/Database/DatabaseFunctionalTest.php index c5f337bfc..cb536a50f 100644 --- a/tests/Database/DatabaseFunctionalTest.php +++ b/tests/Database/DatabaseFunctionalTest.php @@ -83,7 +83,7 @@ public function testCommand(): void $commandResult = current($cursor->toArray()); $this->assertCommandSucceeded($commandResult); - $this->assertObjectHasAttribute('ok', $commandResult); + $this->assertObjectHasProperty('ok', $commandResult); $this->assertSame(1, (int) $commandResult->ok); } diff --git a/tests/DocumentationExamplesTest.php b/tests/DocumentationExamplesTest.php index e325b4914..a8adedffe 100644 --- a/tests/DocumentationExamplesTest.php +++ b/tests/DocumentationExamplesTest.php @@ -591,7 +591,7 @@ public function testExample_42_50(): void $this->assertCount(3, $documents); foreach ($documents as $document) { foreach (['_id', 'item', 'status', 'size', 'instock'] as $field) { - $this->assertObjectHasAttribute($field, $document); + $this->assertObjectHasProperty($field, $document); } } @@ -606,11 +606,11 @@ public function testExample_42_50(): void $this->assertCount(3, $documents); foreach ($documents as $document) { foreach (['_id', 'item', 'status'] as $field) { - $this->assertObjectHasAttribute($field, $document); + $this->assertObjectHasProperty($field, $document); } foreach (['size', 'instock'] as $field) { - $this->assertObjectNotHasAttribute($field, $document); + $this->assertObjectNotHasProperty($field, $document); } } @@ -625,11 +625,11 @@ public function testExample_42_50(): void $this->assertCount(3, $documents); foreach ($documents as $document) { foreach (['item', 'status'] as $field) { - $this->assertObjectHasAttribute($field, $document); + $this->assertObjectHasProperty($field, $document); } foreach (['_id', 'size', 'instock'] as $field) { - $this->assertObjectNotHasAttribute($field, $document); + $this->assertObjectNotHasProperty($field, $document); } } @@ -644,11 +644,11 @@ public function testExample_42_50(): void $this->assertCount(3, $documents); foreach ($documents as $document) { foreach (['_id', 'item', 'size'] as $field) { - $this->assertObjectHasAttribute($field, $document); + $this->assertObjectHasProperty($field, $document); } foreach (['status', 'instock'] as $field) { - $this->assertObjectNotHasAttribute($field, $document); + $this->assertObjectNotHasProperty($field, $document); } } @@ -663,13 +663,13 @@ public function testExample_42_50(): void $this->assertCount(3, $documents); foreach ($documents as $document) { foreach (['_id', 'item', 'status', 'size'] as $field) { - $this->assertObjectHasAttribute($field, $document); + $this->assertObjectHasProperty($field, $document); } - $this->assertObjectNotHasAttribute('instock', $document); - $this->assertObjectHasAttribute('uom', $document->size); - $this->assertObjectNotHasAttribute('h', $document->size); - $this->assertObjectNotHasAttribute('w', $document->size); + $this->assertObjectNotHasProperty('instock', $document); + $this->assertObjectHasProperty('uom', $document->size); + $this->assertObjectNotHasProperty('h', $document->size); + $this->assertObjectNotHasProperty('w', $document->size); } // Start Example 48 @@ -683,12 +683,12 @@ public function testExample_42_50(): void $this->assertCount(3, $documents); foreach ($documents as $document) { foreach (['_id', 'item', 'status', 'size', 'instock'] as $field) { - $this->assertObjectHasAttribute($field, $document); + $this->assertObjectHasProperty($field, $document); } - $this->assertObjectHasAttribute('h', $document->size); - $this->assertObjectHasAttribute('w', $document->size); - $this->assertObjectNotHasAttribute('uom', $document->size); + $this->assertObjectHasProperty('h', $document->size); + $this->assertObjectHasProperty('w', $document->size); + $this->assertObjectNotHasProperty('uom', $document->size); } // Start Example 49 @@ -702,13 +702,13 @@ public function testExample_42_50(): void $this->assertCount(3, $documents); foreach ($documents as $document) { foreach (['_id', 'item', 'status', 'instock'] as $field) { - $this->assertObjectHasAttribute($field, $document); + $this->assertObjectHasProperty($field, $document); } - $this->assertObjectNotHasAttribute('size', $document); + $this->assertObjectNotHasProperty('size', $document); foreach ($document->instock as $instock) { - $this->assertObjectHasAttribute('qty', $instock); - $this->assertObjectNotHasAttribute('warehouse', $instock); + $this->assertObjectHasProperty('qty', $instock); + $this->assertObjectNotHasProperty('warehouse', $instock); } } @@ -723,10 +723,10 @@ public function testExample_42_50(): void $this->assertCount(3, $documents); foreach ($documents as $document) { foreach (['_id', 'item', 'status', 'instock'] as $field) { - $this->assertObjectHasAttribute($field, $document); + $this->assertObjectHasProperty($field, $document); } - $this->assertObjectNotHasAttribute('size', $document); + $this->assertObjectNotHasProperty('size', $document); $this->assertCount(1, $document->instock); } } @@ -786,10 +786,10 @@ public function testAggregationProjectionExample_1(): void $this->assertCount(3, $documents); foreach ($documents as $document) { foreach (['item', 'status', 'area', 'reportNumber'] as $field) { - $this->assertObjectHasAttribute($field, $document); + $this->assertObjectHasProperty($field, $document); } - $this->assertObjectNotHasAttribute('_id', $document); + $this->assertObjectNotHasProperty('_id', $document); $this->assertIsString($document->status); $this->assertIsString($document->area); $this->assertSame(1, $document->reportNumber); diff --git a/tests/Operation/AggregateFunctionalTest.php b/tests/Operation/AggregateFunctionalTest.php index bd5ccee80..2b45b49c3 100644 --- a/tests/Operation/AggregateFunctionalTest.php +++ b/tests/Operation/AggregateFunctionalTest.php @@ -33,7 +33,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('allowDiskUse', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('allowDiskUse', $event['started']->getCommand()); }, ); } @@ -92,7 +92,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('readConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('readConcern', $event['started']->getCommand()); }, ); } @@ -111,7 +111,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); }, ); @@ -156,7 +156,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } @@ -218,14 +218,14 @@ function () use ($pipeline, $options): void { if (isset($result->shards)) { foreach ($result->shards as $shard) { - $this->assertObjectHasAttribute('stages', $shard); + $this->assertObjectHasProperty('stages', $shard); } } else { - $this->assertObjectHasAttribute('stages', $result); + $this->assertObjectHasProperty('stages', $result); } }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); }, ); @@ -246,7 +246,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectHasProperty('bypassDocumentValidation', $event['started']->getCommand()); $this->assertEquals(true, $event['started']->getCommand()->bypassDocumentValidation); }, ); @@ -266,7 +266,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('bypassDocumentValidation', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/BulkWriteFunctionalTest.php b/tests/Operation/BulkWriteFunctionalTest.php index 46958e324..96006d689 100644 --- a/tests/Operation/BulkWriteFunctionalTest.php +++ b/tests/Operation/BulkWriteFunctionalTest.php @@ -379,7 +379,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } @@ -398,7 +398,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectHasProperty('bypassDocumentValidation', $event['started']->getCommand()); $this->assertEquals(true, $event['started']->getCommand()->bypassDocumentValidation); }, ); @@ -418,7 +418,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('bypassDocumentValidation', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/CountFunctionalTest.php b/tests/Operation/CountFunctionalTest.php index 47e9bea07..55435ab98 100644 --- a/tests/Operation/CountFunctionalTest.php +++ b/tests/Operation/CountFunctionalTest.php @@ -43,7 +43,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('readConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('readConcern', $event['started']->getCommand()); }, ); } @@ -99,7 +99,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/CreateCollectionFunctionalTest.php b/tests/Operation/CreateCollectionFunctionalTest.php index aa383f091..023a67e60 100644 --- a/tests/Operation/CreateCollectionFunctionalTest.php +++ b/tests/Operation/CreateCollectionFunctionalTest.php @@ -20,7 +20,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); }, ); } @@ -38,7 +38,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/CreateIndexesFunctionalTest.php b/tests/Operation/CreateIndexesFunctionalTest.php index 4204465d7..41e92bb44 100644 --- a/tests/Operation/CreateIndexesFunctionalTest.php +++ b/tests/Operation/CreateIndexesFunctionalTest.php @@ -162,7 +162,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); }, ); } @@ -181,7 +181,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } @@ -206,7 +206,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('commitQuorum', $event['started']->getCommand()); + $this->assertObjectHasProperty('commitQuorum', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/DatabaseCommandFunctionalTest.php b/tests/Operation/DatabaseCommandFunctionalTest.php index 4381718e8..6a1410669 100644 --- a/tests/Operation/DatabaseCommandFunctionalTest.php +++ b/tests/Operation/DatabaseCommandFunctionalTest.php @@ -55,7 +55,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/DeleteFunctionalTest.php b/tests/Operation/DeleteFunctionalTest.php index 44d96bda7..7ff002f97 100644 --- a/tests/Operation/DeleteFunctionalTest.php +++ b/tests/Operation/DeleteFunctionalTest.php @@ -115,7 +115,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/DistinctFunctionalTest.php b/tests/Operation/DistinctFunctionalTest.php index 625053bd9..4293293b0 100644 --- a/tests/Operation/DistinctFunctionalTest.php +++ b/tests/Operation/DistinctFunctionalTest.php @@ -50,7 +50,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('readConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('readConcern', $event['started']->getCommand()); }, ); } @@ -70,7 +70,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/DropCollectionFunctionalTest.php b/tests/Operation/DropCollectionFunctionalTest.php index 39b82a69f..602982f5c 100644 --- a/tests/Operation/DropCollectionFunctionalTest.php +++ b/tests/Operation/DropCollectionFunctionalTest.php @@ -21,7 +21,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); }, ); } @@ -67,7 +67,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/DropDatabaseFunctionalTest.php b/tests/Operation/DropDatabaseFunctionalTest.php index 6dd101a5d..628c3b0f3 100644 --- a/tests/Operation/DropDatabaseFunctionalTest.php +++ b/tests/Operation/DropDatabaseFunctionalTest.php @@ -24,7 +24,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); }, ); } @@ -69,7 +69,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/DropIndexesFunctionalTest.php b/tests/Operation/DropIndexesFunctionalTest.php index 1c3604159..d54c8544a 100644 --- a/tests/Operation/DropIndexesFunctionalTest.php +++ b/tests/Operation/DropIndexesFunctionalTest.php @@ -32,7 +32,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); }, ); } @@ -137,7 +137,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/ExplainFunctionalTest.php b/tests/Operation/ExplainFunctionalTest.php index 19b59dc5f..714a3f868 100644 --- a/tests/Operation/ExplainFunctionalTest.php +++ b/tests/Operation/ExplainFunctionalTest.php @@ -150,9 +150,9 @@ function () use ($operation): void { }, function (array $event): void { $command = $event['started']->getCommand(); - $this->assertObjectNotHasAttribute('maxAwaitTimeMS', $command->explain); - $this->assertObjectHasAttribute('tailable', $command->explain); - $this->assertObjectHasAttribute('awaitData', $command->explain); + $this->assertObjectNotHasProperty('maxAwaitTimeMS', $command->explain); + $this->assertObjectHasProperty('tailable', $command->explain); + $this->assertObjectHasProperty('awaitData', $command->explain); }, ); } @@ -175,8 +175,8 @@ function () use ($operation): void { }, function (array $event): void { $command = $event['started']->getCommand(); - $this->assertObjectHasAttribute('sort', $command->explain); - $this->assertObjectNotHasAttribute('modifiers', $command->explain); + $this->assertObjectHasProperty('sort', $command->explain); + $this->assertObjectNotHasProperty('modifiers', $command->explain); }, ); } @@ -261,7 +261,7 @@ function (): void { $result = $explainOperation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute( + $this->assertObjectHasProperty( 'bypassDocumentValidation', $event['started']->getCommand()->explain, ); @@ -288,7 +288,7 @@ function (): void { $result = $explainOperation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute( + $this->assertObjectNotHasProperty( 'bypassDocumentValidation', $event['started']->getCommand()->explain, ); diff --git a/tests/Operation/FindAndModifyFunctionalTest.php b/tests/Operation/FindAndModifyFunctionalTest.php index 05e819132..28c636d7c 100644 --- a/tests/Operation/FindAndModifyFunctionalTest.php +++ b/tests/Operation/FindAndModifyFunctionalTest.php @@ -104,7 +104,7 @@ function () use ($server): void { $operation->execute($server); }, function (array $event): void { - $this->assertObjectNotHasAttribute('readConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('readConcern', $event['started']->getCommand()); }, ); } @@ -122,7 +122,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); }, ); } @@ -191,7 +191,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } @@ -209,7 +209,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectHasProperty('bypassDocumentValidation', $event['started']->getCommand()); $this->assertEquals(true, $event['started']->getCommand()->bypassDocumentValidation); }, ); @@ -228,7 +228,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('bypassDocumentValidation', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/FindFunctionalTest.php b/tests/Operation/FindFunctionalTest.php index 2b70a421b..1238cec6e 100644 --- a/tests/Operation/FindFunctionalTest.php +++ b/tests/Operation/FindFunctionalTest.php @@ -82,7 +82,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('readConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('readConcern', $event['started']->getCommand()); }, ); } @@ -152,7 +152,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/InsertManyFunctionalTest.php b/tests/Operation/InsertManyFunctionalTest.php index ae17f7342..d4a786e71 100644 --- a/tests/Operation/InsertManyFunctionalTest.php +++ b/tests/Operation/InsertManyFunctionalTest.php @@ -132,7 +132,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } @@ -151,7 +151,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectHasProperty('bypassDocumentValidation', $event['started']->getCommand()); $this->assertEquals(true, $event['started']->getCommand()->bypassDocumentValidation); }, ); @@ -171,7 +171,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('bypassDocumentValidation', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/InsertOneFunctionalTest.php b/tests/Operation/InsertOneFunctionalTest.php index 2075f0fc0..d34a89df1 100644 --- a/tests/Operation/InsertOneFunctionalTest.php +++ b/tests/Operation/InsertOneFunctionalTest.php @@ -123,7 +123,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } @@ -142,7 +142,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectHasProperty('bypassDocumentValidation', $event['started']->getCommand()); $this->assertEquals(true, $event['started']->getCommand()->bypassDocumentValidation); }, ); @@ -162,7 +162,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('bypassDocumentValidation', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/ListCollectionNamesFunctionalTest.php b/tests/Operation/ListCollectionNamesFunctionalTest.php index 91f8b2fb7..74e188627 100644 --- a/tests/Operation/ListCollectionNamesFunctionalTest.php +++ b/tests/Operation/ListCollectionNamesFunctionalTest.php @@ -41,7 +41,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('authorizedCollections', $event['started']->getCommand()); + $this->assertObjectHasProperty('authorizedCollections', $event['started']->getCommand()); $this->assertSame(true, $event['started']->getCommand()->authorizedCollections); }, ); @@ -59,7 +59,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/ListCollectionsFunctionalTest.php b/tests/Operation/ListCollectionsFunctionalTest.php index ffdc3748c..9f03e14fe 100644 --- a/tests/Operation/ListCollectionsFunctionalTest.php +++ b/tests/Operation/ListCollectionsFunctionalTest.php @@ -86,7 +86,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('authorizedCollections', $event['started']->getCommand()); + $this->assertObjectHasProperty('authorizedCollections', $event['started']->getCommand()); $this->assertSame(true, $event['started']->getCommand()->authorizedCollections); }, ); @@ -104,7 +104,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/ListDatabaseNamesFunctionalTest.php b/tests/Operation/ListDatabaseNamesFunctionalTest.php index 651112afd..b9bbcc9a7 100644 --- a/tests/Operation/ListDatabaseNamesFunctionalTest.php +++ b/tests/Operation/ListDatabaseNamesFunctionalTest.php @@ -24,7 +24,7 @@ function () use (&$databases, $server): void { $databases = $operation->execute($server); }, function (array $event): void { - $this->assertObjectNotHasAttribute('authorizedDatabases', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('authorizedDatabases', $event['started']->getCommand()); $this->assertSame(true, $event['started']->getCommand()->nameOnly); }, ); @@ -45,7 +45,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('authorizedDatabases', $event['started']->getCommand()); + $this->assertObjectHasProperty('authorizedDatabases', $event['started']->getCommand()); $this->assertSame(true, $event['started']->getCommand()->authorizedDatabases); }, ); @@ -79,7 +79,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/ListDatabasesFunctionalTest.php b/tests/Operation/ListDatabasesFunctionalTest.php index 525b3d94e..0a6271827 100644 --- a/tests/Operation/ListDatabasesFunctionalTest.php +++ b/tests/Operation/ListDatabasesFunctionalTest.php @@ -26,7 +26,7 @@ function () use (&$databases, $server): void { $databases = $operation->execute($server); }, function (array $event): void { - $this->assertObjectNotHasAttribute('authorizedDatabases', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('authorizedDatabases', $event['started']->getCommand()); }, ); @@ -48,7 +48,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('authorizedDatabases', $event['started']->getCommand()); + $this->assertObjectHasProperty('authorizedDatabases', $event['started']->getCommand()); $this->assertSame(true, $event['started']->getCommand()->authorizedDatabases); }, ); @@ -86,7 +86,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/ListIndexesFunctionalTest.php b/tests/Operation/ListIndexesFunctionalTest.php index 553615e4d..dcdcac71c 100644 --- a/tests/Operation/ListIndexesFunctionalTest.php +++ b/tests/Operation/ListIndexesFunctionalTest.php @@ -55,7 +55,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/MapReduceFunctionalTest.php b/tests/Operation/MapReduceFunctionalTest.php index cc4084c71..ea1b1e70b 100644 --- a/tests/Operation/MapReduceFunctionalTest.php +++ b/tests/Operation/MapReduceFunctionalTest.php @@ -41,7 +41,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('readConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('readConcern', $event['started']->getCommand()); }, ); } @@ -66,7 +66,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); }, ); } @@ -161,7 +161,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } @@ -184,7 +184,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectHasProperty('bypassDocumentValidation', $event['started']->getCommand()); $this->assertEquals(true, $event['started']->getCommand()->bypassDocumentValidation); }, ); @@ -208,7 +208,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('bypassDocumentValidation', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/RenameCollectionFunctionalTest.php b/tests/Operation/RenameCollectionFunctionalTest.php index 05be70da7..9dccae0e3 100644 --- a/tests/Operation/RenameCollectionFunctionalTest.php +++ b/tests/Operation/RenameCollectionFunctionalTest.php @@ -46,7 +46,7 @@ function (): void { $operation->execute($server); }, function (array $event): void { - $this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); }, ); } @@ -138,7 +138,7 @@ function (): void { $operation->execute($server); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/UpdateFunctionalTest.php b/tests/Operation/UpdateFunctionalTest.php index e70331433..d475866ce 100644 --- a/tests/Operation/UpdateFunctionalTest.php +++ b/tests/Operation/UpdateFunctionalTest.php @@ -102,7 +102,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('lsid', $event['started']->getCommand()); + $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); }, ); } @@ -122,7 +122,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectHasProperty('bypassDocumentValidation', $event['started']->getCommand()); $this->assertEquals(true, $event['started']->getCommand()->bypassDocumentValidation); }, ); @@ -143,7 +143,7 @@ function (): void { $operation->execute($this->getPrimaryServer()); }, function (array $event): void { - $this->assertObjectNotHasAttribute('bypassDocumentValidation', $event['started']->getCommand()); + $this->assertObjectNotHasProperty('bypassDocumentValidation', $event['started']->getCommand()); }, ); } diff --git a/tests/Operation/WatchFunctionalTest.php b/tests/Operation/WatchFunctionalTest.php index a7e0ae534..c71818cf2 100644 --- a/tests/Operation/WatchFunctionalTest.php +++ b/tests/Operation/WatchFunctionalTest.php @@ -97,7 +97,7 @@ public function encode($value): Document yield 'Codec' => [ 'options' => ['codec' => $codec], 'getIdentifier' => static function (object $document): object { - self::assertObjectHasAttribute('id', $document); + self::assertObjectHasProperty('id', $document); return $document->id; }, @@ -322,11 +322,11 @@ function (array $event) use (&$events): void { private function assertResumeAfter($expectedResumeToken, stdClass $command): void { - $this->assertObjectHasAttribute('pipeline', $command); + $this->assertObjectHasProperty('pipeline', $command); $this->assertIsArray($command->pipeline); $this->assertArrayHasKey(0, $command->pipeline); - $this->assertObjectHasAttribute('$changeStream', $command->pipeline[0]); - $this->assertObjectHasAttribute('resumeAfter', $command->pipeline[0]->{'$changeStream'}); + $this->assertObjectHasProperty('$changeStream', $command->pipeline[0]); + $this->assertObjectHasProperty('resumeAfter', $command->pipeline[0]->{'$changeStream'}); $this->assertEquals($expectedResumeToken, $command->pipeline[0]->{'$changeStream'}->resumeAfter); } @@ -355,7 +355,7 @@ function (array $event) use (&$events): void { $this->assertCount(1, $events); $this->assertSame('aggregate', $events[0]['started']->getCommandName()); $reply = $events[0]['succeeded']->getReply(); - $this->assertObjectHasAttribute('operationTime', $reply); + $this->assertObjectHasProperty('operationTime', $reply); $operationTime = $reply->operationTime; $this->assertInstanceOf(TimestampInterface::class, $operationTime); @@ -395,11 +395,11 @@ function (array $event) use (&$events): void { private function assertStartAtOperationTime(TimestampInterface $expectedOperationTime, stdClass $command): void { - $this->assertObjectHasAttribute('pipeline', $command); + $this->assertObjectHasProperty('pipeline', $command); $this->assertIsArray($command->pipeline); $this->assertArrayHasKey(0, $command->pipeline); - $this->assertObjectHasAttribute('$changeStream', $command->pipeline[0]); - $this->assertObjectHasAttribute('startAtOperationTime', $command->pipeline[0]->{'$changeStream'}); + $this->assertObjectHasProperty('$changeStream', $command->pipeline[0]); + $this->assertObjectHasProperty('startAtOperationTime', $command->pipeline[0]->{'$changeStream'}); $this->assertEquals($expectedOperationTime, $command->pipeline[0]->{'$changeStream'}->startAtOperationTime); } @@ -1454,7 +1454,7 @@ public function testResumeTokenBehaviour(array $options): void $this->assertInstanceOf(CommandSucceededEvent::class, $event['succeeded']); $reply = $event['succeeded']->getReply(); - $this->assertObjectHasAttribute('operationTime', $reply); + $this->assertObjectHasProperty('operationTime', $reply); $lastOpTime = $reply->operationTime; }); @@ -1544,8 +1544,8 @@ function (array $event) use (&$aggregateCommand): void { ); $this->assertNotNull($aggregateCommand); - $this->assertObjectNotHasAttribute('resumeAfter', $aggregateCommand->pipeline[0]->{'$changeStream'}); - $this->assertObjectHasAttribute('startAfter', $aggregateCommand->pipeline[0]->{'$changeStream'}); + $this->assertObjectNotHasProperty('resumeAfter', $aggregateCommand->pipeline[0]->{'$changeStream'}); + $this->assertObjectHasProperty('startAfter', $aggregateCommand->pipeline[0]->{'$changeStream'}); } /** @@ -1593,8 +1593,8 @@ function (array $event) use (&$aggregateCommand): void { ); $this->assertNotNull($aggregateCommand); - $this->assertObjectNotHasAttribute('startAfter', $aggregateCommand->pipeline[0]->{'$changeStream'}); - $this->assertObjectHasAttribute('resumeAfter', $aggregateCommand->pipeline[0]->{'$changeStream'}); + $this->assertObjectNotHasProperty('startAfter', $aggregateCommand->pipeline[0]->{'$changeStream'}); + $this->assertObjectHasProperty('resumeAfter', $aggregateCommand->pipeline[0]->{'$changeStream'}); } private function assertNoCommandExecuted(callable $callable): void @@ -1626,9 +1626,9 @@ private function forceChangeStreamResume(): void private function getPostBatchResumeTokenFromReply(stdClass $reply) { - $this->assertObjectHasAttribute('cursor', $reply); + $this->assertObjectHasProperty('cursor', $reply); $this->assertIsObject($reply->cursor); - $this->assertObjectHasAttribute('postBatchResumeToken', $reply->cursor); + $this->assertObjectHasProperty('postBatchResumeToken', $reply->cursor); $this->assertIsObject($reply->cursor->postBatchResumeToken); return $reply->cursor->postBatchResumeToken; diff --git a/tests/SpecTests/AtlasDataLakeSpecTest.php b/tests/SpecTests/AtlasDataLakeSpecTest.php index 15f45434c..3d008093c 100644 --- a/tests/SpecTests/AtlasDataLakeSpecTest.php +++ b/tests/SpecTests/AtlasDataLakeSpecTest.php @@ -44,11 +44,11 @@ function (array $event) use (&$cursorId, &$cursorNamespace): void { $this->assertArrayHasKey('succeeded', $event); $reply = $event['succeeded']->getReply(); - $this->assertObjectHasAttribute('cursor', $reply); + $this->assertObjectHasProperty('cursor', $reply); $this->assertIsObject($reply->cursor); - $this->assertObjectHasAttribute('id', $reply->cursor); + $this->assertObjectHasProperty('id', $reply->cursor); $this->assertIsInt($reply->cursor->id); - $this->assertObjectHasAttribute('ns', $reply->cursor); + $this->assertObjectHasProperty('ns', $reply->cursor); $this->assertIsString($reply->cursor->ns); /* Note: MongoDB\Driver\CursorId is not used here; however, @@ -74,9 +74,9 @@ function (array $event) use (&$cursorId, &$cursorNamespace): void { * cursor ID from the find command reply. */ $this->assertSame($databaseName, $event['started']->getDatabaseName()); $this->assertSame($databaseName, $command->{'$db'}); - $this->assertObjectHasAttribute('killCursors', $command); + $this->assertObjectHasProperty('killCursors', $command); $this->assertSame($collectionName, $command->killCursors); - $this->assertObjectHasAttribute('cursors', $command); + $this->assertObjectHasProperty('cursors', $command); $this->assertIsArray($command->cursors); $this->assertArrayHasKey(0, $command->cursors); $this->assertSame($cursorId, $command->cursors[0]); @@ -84,7 +84,7 @@ function (array $event) use (&$cursorId, &$cursorNamespace): void { /* Assert that the killCursors command reply indicates that the * expected cursor ID was killed. */ $reply = $event['succeeded']->getReply(); - $this->assertObjectHasAttribute('cursorsKilled', $reply); + $this->assertObjectHasProperty('cursorsKilled', $reply); $this->assertIsArray($reply->cursorsKilled); $this->assertArrayHasKey(0, $reply->cursorsKilled); $this->assertSame($cursorId, $reply->cursorsKilled[0]); diff --git a/tests/SpecTests/ClientSideEncryptionSpecTest.php b/tests/SpecTests/ClientSideEncryptionSpecTest.php index 001d78a84..1c716edb0 100644 --- a/tests/SpecTests/ClientSideEncryptionSpecTest.php +++ b/tests/SpecTests/ClientSideEncryptionSpecTest.php @@ -340,8 +340,8 @@ function ($command) use (&$insertCommand): void { $this->assertSame(Binary::TYPE_UUID, $dataKeyId->getType()); $this->assertNotNull($insertCommand); - $this->assertObjectHasAttribute('writeConcern', $insertCommand); - $this->assertObjectHasAttribute('w', $insertCommand->writeConcern); + $this->assertObjectHasProperty('writeConcern', $insertCommand); + $this->assertObjectHasProperty('w', $insertCommand->writeConcern); $this->assertSame(WriteConcern::MAJORITY, $insertCommand->writeConcern->w); $keys = $client->selectCollection('keyvault', 'datakeys')->find(['_id' => $dataKeyId]); @@ -1559,10 +1559,10 @@ static function (self $test, Client $client, ClientEncryption $clientEncryption) $keyId = $clientEncryption->createDataKey('local'); $keyBeforeUpdate = $clientEncryption->addKeyAltName($keyId, 'abc'); - $test->assertObjectNotHasAttribute('keyAltNames', $keyBeforeUpdate); + $test->assertObjectNotHasProperty('keyAltNames', $keyBeforeUpdate); $keyBeforeUpdate = $clientEncryption->addKeyAltName($keyId, 'abc'); - $test->assertObjectHasAttribute('keyAltNames', $keyBeforeUpdate); + $test->assertObjectHasProperty('keyAltNames', $keyBeforeUpdate); $test->assertIsArray($keyBeforeUpdate->keyAltNames); $test->assertContains('abc', $keyBeforeUpdate->keyAltNames); @@ -1576,7 +1576,7 @@ static function (self $test, Client $client, ClientEncryption $clientEncryption) $originalKeyId = $clientEncryption->getKeyByAltName('def')->_id; $originalKeyBeforeUpdate = $clientEncryption->addKeyAltName($originalKeyId, 'def'); - $test->assertObjectHasAttribute('keyAltNames', $originalKeyBeforeUpdate); + $test->assertObjectHasProperty('keyAltNames', $originalKeyBeforeUpdate); $test->assertIsArray($originalKeyBeforeUpdate->keyAltNames); $test->assertContains('def', $originalKeyBeforeUpdate->keyAltNames); }, @@ -1832,10 +1832,10 @@ public function testRewrapManyDataKey(string $srcProvider, string $dstProvider): $result = $clientEncryption2->rewrapManyDataKey([], $rewrapManyDataKeyOpts); - $this->assertObjectHasAttribute('bulkWriteResult', $result); + $this->assertObjectHasProperty('bulkWriteResult', $result); $this->assertIsObject($result->bulkWriteResult); // libmongoc uses different field names for its BulkWriteResult - $this->assertObjectHasAttribute('nModified', $result->bulkWriteResult); + $this->assertObjectHasProperty('nModified', $result->bulkWriteResult); $this->assertSame(1, $result->bulkWriteResult->nModified); $this->assertSame('test', $clientEncryption1->decrypt($ciphertext)); diff --git a/tests/SpecTests/ErrorExpectation.php b/tests/SpecTests/ErrorExpectation.php index 82511b4d6..cc10dcba9 100644 --- a/tests/SpecTests/ErrorExpectation.php +++ b/tests/SpecTests/ErrorExpectation.php @@ -163,13 +163,13 @@ private function assertCodeName(TestCase $test, ?Throwable $actual = null): void $result = $actual->getResultDocument(); if (isset($result->writeConcernError)) { - $test->assertObjectHasAttribute('codeName', $result->writeConcernError); + $test->assertObjectHasProperty('codeName', $result->writeConcernError); $test->assertSame($this->codeName, $result->writeConcernError->codeName); return; } - $test->assertObjectHasAttribute('codeName', $result); + $test->assertObjectHasProperty('codeName', $result); $test->assertSame($this->codeName, $result->codeName); } diff --git a/tests/SpecTests/FunctionalTestCase.php b/tests/SpecTests/FunctionalTestCase.php index b4ca35a9e..ce2b815d5 100644 --- a/tests/SpecTests/FunctionalTestCase.php +++ b/tests/SpecTests/FunctionalTestCase.php @@ -104,7 +104,7 @@ protected static function assertCommandOmittedFields(stdClass $expected, stdClas { foreach ($expected as $key => $value) { if ($value === null) { - static::assertObjectNotHasAttribute($key, $actual); + static::assertObjectNotHasProperty($key, $actual); unset($expected->{$key}); } } diff --git a/tests/UnifiedSpecTests/EventObserver.php b/tests/UnifiedSpecTests/EventObserver.php index cd2f44d24..767216643 100644 --- a/tests/UnifiedSpecTests/EventObserver.php +++ b/tests/UnifiedSpecTests/EventObserver.php @@ -27,7 +27,7 @@ use function PHPUnit\Framework\assertIsObject; use function PHPUnit\Framework\assertIsString; use function PHPUnit\Framework\assertNotEmpty; -use function PHPUnit\Framework\assertObjectHasAttribute; +use function PHPUnit\Framework\assertObjectHasProperty; use function PHPUnit\Framework\assertSame; use function PHPUnit\Framework\assertThat; use function sprintf; @@ -165,7 +165,7 @@ public function getLsidsOnLastTwoCommands(): array } $command = $event->getCommand(); - assertObjectHasAttribute('lsid', $command); + assertObjectHasProperty('lsid', $command); $lsids[] = $command->lsid; if (count($lsids) === 2) { diff --git a/tests/UnifiedSpecTests/ExpectedError.php b/tests/UnifiedSpecTests/ExpectedError.php index 52fc1f91c..06cd2c902 100644 --- a/tests/UnifiedSpecTests/ExpectedError.php +++ b/tests/UnifiedSpecTests/ExpectedError.php @@ -26,7 +26,7 @@ use function PHPUnit\Framework\assertNotInstanceOf; use function PHPUnit\Framework\assertNotNull; use function PHPUnit\Framework\assertNull; -use function PHPUnit\Framework\assertObjectHasAttribute; +use function PHPUnit\Framework\assertObjectHasProperty; use function PHPUnit\Framework\assertSame; use function PHPUnit\Framework\assertStringContainsStringIgnoringCase; use function PHPUnit\Framework\assertThat; @@ -218,13 +218,13 @@ private function assertCodeName(ServerException $e): void $result = $e->getResultDocument(); if (isset($result->writeConcernError)) { - assertObjectHasAttribute('codeName', $result->writeConcernError); + assertObjectHasProperty('codeName', $result->writeConcernError); assertSame($this->codeName, $result->writeConcernError->codeName); return; } - assertObjectHasAttribute('codeName', $result); + assertObjectHasProperty('codeName', $result); assertSame($this->codeName, $result->codeName); } } diff --git a/tests/UnifiedSpecTests/Operation.php b/tests/UnifiedSpecTests/Operation.php index 4c916ffa4..68383443e 100644 --- a/tests/UnifiedSpecTests/Operation.php +++ b/tests/UnifiedSpecTests/Operation.php @@ -51,7 +51,7 @@ use function PHPUnit\Framework\assertNotEquals; use function PHPUnit\Framework\assertNotNull; use function PHPUnit\Framework\assertNull; -use function PHPUnit\Framework\assertObjectHasAttribute; +use function PHPUnit\Framework\assertObjectHasProperty; use function PHPUnit\Framework\assertSame; use function PHPUnit\Framework\assertThat; use function PHPUnit\Framework\assertTrue; @@ -535,7 +535,7 @@ private function executeForCollection(Collection $collection) case 'createSearchIndex': assertArrayHasKey('model', $args); assertIsObject($args['model']); - assertObjectHasAttribute('definition', $args['model']); + assertObjectHasProperty('definition', $args['model']); assertInstanceOf(stdClass::class, $args['model']->definition); /* Note: tests specify options within "model". A top-level @@ -1050,7 +1050,7 @@ private static function prepareUploadArguments(array $args): array { $source = $args['source'] ?? null; assertIsObject($source); - assertObjectHasAttribute('$$hexBytes', $source); + assertObjectHasProperty('$$hexBytes', $source); Util::assertHasOnlyKeys($source, ['$$hexBytes']); $hexBytes = $source->{'$$hexBytes'}; assertIsString($hexBytes); From 3724afc329619018b7ff0c283c6374e4ce7a2cd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 16 Sep 2024 09:00:10 +0200 Subject: [PATCH 83/95] PHPLIB-1514 Make data providers static (#1404) --- rector.php | 2 + tests/Builder/FieldPathTest.php | 2 +- tests/Builder/Type/CombinedFieldQueryTest.php | 2 +- tests/Builder/Type/OutputWindowTest.php | 4 +- tests/Builder/Type/QueryObjectTest.php | 2 +- tests/Builder/VariableTest.php | 2 +- tests/ClientTest.php | 12 +- .../CodecCollectionFunctionalTest.php | 2 +- tests/Collection/CollectionFunctionalTest.php | 30 ++-- tests/Command/ListCollectionsTest.php | 12 +- tests/Command/ListDatabasesTest.php | 14 +- tests/Database/DatabaseFunctionalTest.php | 14 +- .../InvalidArgumentExceptionTest.php | 2 +- tests/FunctionsTest.php | 18 +- tests/GridFS/BucketFunctionalTest.php | 158 +++++++++--------- tests/GridFS/FunctionalTestCase.php | 2 +- tests/GridFS/ReadableStreamFunctionalTest.php | 18 +- tests/GridFS/WritableStreamFunctionalTest.php | 12 +- tests/Model/BSONIteratorTest.php | 2 +- tests/Model/ChangeStreamIteratorTest.php | 4 +- tests/Model/IndexInputTest.php | 6 +- tests/Operation/AggregateFunctionalTest.php | 2 +- tests/Operation/AggregateTest.php | 34 ++-- tests/Operation/BulkWriteFunctionalTest.php | 4 +- tests/Operation/BulkWriteTest.php | 18 +- tests/Operation/CountDocumentsTest.php | 20 +-- tests/Operation/CountTest.php | 20 +-- tests/Operation/CreateCollectionTest.php | 48 +++--- ...reateEncryptedCollectionFunctionalTest.php | 8 +- .../CreateEncryptedCollectionTest.php | 6 +- .../Operation/CreateIndexesFunctionalTest.php | 2 +- tests/Operation/CreateIndexesTest.php | 16 +- .../DatabaseCommandFunctionalTest.php | 2 +- tests/Operation/DatabaseCommandTest.php | 10 +- tests/Operation/DeleteTest.php | 18 +- tests/Operation/DistinctFunctionalTest.php | 2 +- tests/Operation/DistinctTest.php | 16 +- tests/Operation/DropCollectionTest.php | 10 +- tests/Operation/DropDatabaseTest.php | 10 +- .../Operation/DropEncryptedCollectionTest.php | 6 +- tests/Operation/DropIndexesTest.php | 12 +- .../Operation/EstimatedDocumentCountTest.php | 12 +- tests/Operation/ExplainFunctionalTest.php | 2 +- tests/Operation/ExplainTest.php | 12 +- .../Operation/FindAndModifyFunctionalTest.php | 6 +- tests/Operation/FindAndModifyTest.php | 34 ++-- tests/Operation/FindFunctionalTest.php | 4 +- tests/Operation/FindOneAndDeleteTest.php | 6 +- tests/Operation/FindOneAndReplaceTest.php | 14 +- tests/Operation/FindOneAndUpdateTest.php | 12 +- tests/Operation/FindOneFunctionalTest.php | 2 +- tests/Operation/FindTest.php | 56 +++---- tests/Operation/FunctionalTestCase.php | 8 +- tests/Operation/InsertManyTest.php | 14 +- tests/Operation/InsertOneFunctionalTest.php | 4 +- tests/Operation/InsertOneTest.php | 12 +- tests/Operation/ListIndexesTest.php | 8 +- tests/Operation/ListSearchIndexesTest.php | 4 +- tests/Operation/MapReduceFunctionalTest.php | 2 +- tests/Operation/MapReduceTest.php | 42 ++--- tests/Operation/ModifyCollectionTest.php | 10 +- tests/Operation/RenameCollectionTest.php | 12 +- tests/Operation/ReplaceOneTest.php | 6 +- tests/Operation/TestCase.php | 16 +- tests/Operation/UpdateFunctionalTest.php | 2 +- tests/Operation/UpdateTest.php | 20 +-- tests/Operation/WatchFunctionalTest.php | 2 +- tests/Operation/WatchTest.php | 32 ++-- tests/PedantryTest.php | 2 +- .../ClientSideEncryptionSpecTest.php | 2 +- .../DocumentsMatchConstraintTest.php | 4 +- tests/TestCase.php | 75 ++++++--- .../Constraint/IsBsonTypeTest.php | 2 +- .../Constraint/MatchesTest.php | 4 +- tests/UnifiedSpecTests/UnifiedSpecTest.php | 78 ++++----- 75 files changed, 568 insertions(+), 537 deletions(-) diff --git a/rector.php b/rector.php index a51182ee7..31f1f56f6 100644 --- a/rector.php +++ b/rector.php @@ -4,6 +4,7 @@ use Rector\DeadCode\Rector\ClassLike\RemoveAnnotationRector; use Rector\Php70\Rector\StmtsAwareInterface\IfIssetToCoalescingRector; use Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector; +use Rector\PHPUnit\PHPUnit100\Rector\Class_\StaticDataProviderClassMethodRector; use Rector\Set\ValueObject\LevelSetList; return static function (RectorConfig $rectorConfig): void { @@ -18,6 +19,7 @@ $rectorConfig->sets([LevelSetList::UP_TO_PHP_74]); $rectorConfig->rule(ChangeSwitchToMatchRector::class); + $rectorConfig->rule(StaticDataProviderClassMethodRector::class); // phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified $rectorConfig->skip([ diff --git a/tests/Builder/FieldPathTest.php b/tests/Builder/FieldPathTest.php index ada1cc164..f8bd3a309 100644 --- a/tests/Builder/FieldPathTest.php +++ b/tests/Builder/FieldPathTest.php @@ -35,7 +35,7 @@ public function testRejectDollarPrefix(string $fieldPathClass): void Expression::{$fieldPathClass}('$foo'); } - public function provideFieldPath(): Generator + public static function provideFieldPath(): Generator { yield 'double' => ['doubleFieldPath', Expression\ResolvesToDouble::class]; yield 'string' => ['stringFieldPath', Expression\ResolvesToString::class]; diff --git a/tests/Builder/Type/CombinedFieldQueryTest.php b/tests/Builder/Type/CombinedFieldQueryTest.php index 1e302f8bd..ab326afd5 100644 --- a/tests/Builder/Type/CombinedFieldQueryTest.php +++ b/tests/Builder/Type/CombinedFieldQueryTest.php @@ -87,7 +87,7 @@ public function testRejectDuplicateOperator(array $fieldQueries): void ]); } - public function provideDuplicateOperator(): Generator + public static function provideDuplicateOperator(): Generator { yield 'array and FieldQuery' => [ [ diff --git a/tests/Builder/Type/OutputWindowTest.php b/tests/Builder/Type/OutputWindowTest.php index fed42a3eb..1527a1826 100644 --- a/tests/Builder/Type/OutputWindowTest.php +++ b/tests/Builder/Type/OutputWindowTest.php @@ -73,7 +73,7 @@ public function testRejectInvalidDocuments(array $documents): void ); } - public function provideInvalidDocuments(): Generator + public static function provideInvalidDocuments(): Generator { yield 'too few' => [[1]]; yield 'too many' => [[1, 2, 3]]; @@ -98,7 +98,7 @@ public function testRejectInvalidRange(array $range): void ); } - public function provideInvalidRange(): Generator + public static function provideInvalidRange(): Generator { yield 'too few' => [[1]]; yield 'too many' => [[1, 2, 3]]; diff --git a/tests/Builder/Type/QueryObjectTest.php b/tests/Builder/Type/QueryObjectTest.php index fe7a86959..cf5b67fde 100644 --- a/tests/Builder/Type/QueryObjectTest.php +++ b/tests/Builder/Type/QueryObjectTest.php @@ -57,7 +57,7 @@ public function testCreateQueryObjectFromArray(array $value, int $expectedCount $this->assertCount($expectedCount, $queryObject->queries); } - public function provideQueryObjectValue(): Generator + public static function provideQueryObjectValue(): Generator { yield 'int' => [['foo' => 1]]; yield 'float' => [['foo' => 1.1]]; diff --git a/tests/Builder/VariableTest.php b/tests/Builder/VariableTest.php index f9273bfe2..dc30134a5 100644 --- a/tests/Builder/VariableTest.php +++ b/tests/Builder/VariableTest.php @@ -35,7 +35,7 @@ public function testSystemVariables($factory): void $this->assertStringStartsNotWith('$$', $variable->name); } - public function provideVariableBuilders(): Generator + public static function provideVariableBuilders(): Generator { yield 'now' => [fn () => Variable::now()]; yield 'clusterTime' => [fn () => Variable::clusterTime()]; diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 584714c98..b830556d8 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -42,29 +42,29 @@ public function testConstructorDriverOptionTypeChecks(array $driverOptions, stri new Client(static::getUri(), [], $driverOptions); } - public function provideInvalidConstructorDriverOptions() + public static function provideInvalidConstructorDriverOptions() { $options = []; - foreach ($this->getInvalidObjectValues() as $value) { + foreach (self::getInvalidObjectValues() as $value) { $options[][] = ['builderEncoder' => $value]; } - foreach ($this->getInvalidArrayValues(true) as $value) { + foreach (self::getInvalidArrayValues(true) as $value) { $options[][] = ['typeMap' => $value]; } $options[][] = ['autoEncryption' => ['keyVaultClient' => 'foo']]; - foreach ($this->getInvalidStringValues() as $value) { + foreach (self::getInvalidStringValues() as $value) { $options[][] = ['driver' => ['name' => $value]]; } - foreach ($this->getInvalidStringValues() as $value) { + foreach (self::getInvalidStringValues() as $value) { $options[][] = ['driver' => ['version' => $value]]; } - foreach ($this->getInvalidStringValues() as $value) { + foreach (self::getInvalidStringValues() as $value) { $options[] = [ 'driverOptions' => ['driver' => ['platform' => $value]], 'exception' => DriverInvalidArgumentException::class, diff --git a/tests/Collection/CodecCollectionFunctionalTest.php b/tests/Collection/CodecCollectionFunctionalTest.php index 2eb0c09dd..d0d33522c 100644 --- a/tests/Collection/CodecCollectionFunctionalTest.php +++ b/tests/Collection/CodecCollectionFunctionalTest.php @@ -151,7 +151,7 @@ public function testBulkWrite($expected, $options): void ); } - public function provideFindOneAndModifyOptions(): Generator + public static function provideFindOneAndModifyOptions(): Generator { yield 'Default codec' => [ 'expected' => TestObject::createDecodedForFixture(1), diff --git a/tests/Collection/CollectionFunctionalTest.php b/tests/Collection/CollectionFunctionalTest.php index 62d17d8cb..d00ebd84e 100644 --- a/tests/Collection/CollectionFunctionalTest.php +++ b/tests/Collection/CollectionFunctionalTest.php @@ -49,7 +49,7 @@ public function testConstructorCollectionNameArgument($collectionName, string $e new Collection($this->manager, $this->getDatabaseName(), $collectionName); } - public function provideInvalidDatabaseAndCollectionNames() + public static function provideInvalidDatabaseAndCollectionNames() { return [ [null, TypeError::class], @@ -64,15 +64,15 @@ public function testConstructorOptionTypeChecks(array $options): void new Collection($this->manager, $this->getDatabaseName(), $this->getCollectionName(), $options); } - public function provideInvalidConstructorOptions(): array + public static function provideInvalidConstructorOptions(): array { - return $this->createOptionDataProvider([ - 'builderEncoder' => $this->getInvalidObjectValues(), - 'codec' => $this->getInvalidDocumentCodecValues(), - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'builderEncoder' => self::getInvalidObjectValues(), + 'codec' => self::getInvalidDocumentCodecValues(), + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } @@ -210,7 +210,7 @@ public function testDistinctWithTypeMap(array $typeMap, array $expectedDocuments $this->assertEquals($expectedDocuments, $values); } - public function provideTypeMapOptionsAndExpectedDocuments() + public static function provideTypeMapOptionsAndExpectedDocuments() { return [ 'No type map' => [ @@ -448,7 +448,7 @@ public function testMapReduce(): void } } - public function collectionMethodClosures() + public static function collectionMethodClosures() { return [ 'read-only aggregate' => [ @@ -716,18 +716,18 @@ function($collection, $session, $options = []) { ]; } - public function collectionReadMethodClosures(): array + public static function collectionReadMethodClosures(): array { return array_filter( - $this->collectionMethodClosures(), + self::collectionMethodClosures(), fn ($rw) => str_contains($rw[1], 'r'), ); } - public function collectionWriteMethodClosures(): array + public static function collectionWriteMethodClosures(): array { return array_filter( - $this->collectionMethodClosures(), + self::collectionMethodClosures(), fn ($rw) => str_contains($rw[1], 'w'), ); } diff --git a/tests/Command/ListCollectionsTest.php b/tests/Command/ListCollectionsTest.php index 78c90afa6..b1d4b0535 100644 --- a/tests/Command/ListCollectionsTest.php +++ b/tests/Command/ListCollectionsTest.php @@ -15,13 +15,13 @@ public function testConstructorOptionTypeChecks(array $options): void new ListCollections($this->getDatabaseName(), $options); } - public function provideInvalidConstructorOptions(): array + public static function provideInvalidConstructorOptions(): array { - return $this->createOptionDataProvider([ - 'authorizedCollections' => $this->getInvalidBooleanValues(), - 'filter' => $this->getInvalidDocumentValues(), - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'session' => $this->getInvalidSessionValues(), + return self::createOptionDataProvider([ + 'authorizedCollections' => self::getInvalidBooleanValues(), + 'filter' => self::getInvalidDocumentValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'session' => self::getInvalidSessionValues(), ]); } } diff --git a/tests/Command/ListDatabasesTest.php b/tests/Command/ListDatabasesTest.php index 92de68afd..4043826cb 100644 --- a/tests/Command/ListDatabasesTest.php +++ b/tests/Command/ListDatabasesTest.php @@ -15,14 +15,14 @@ public function testConstructorOptionTypeChecks(array $options): void new ListDatabases($options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'authorizedDatabases' => $this->getInvalidBooleanValues(), - 'filter' => $this->getInvalidDocumentValues(), - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'nameOnly' => $this->getInvalidBooleanValues(), - 'session' => $this->getInvalidSessionValues(), + return self::createOptionDataProvider([ + 'authorizedDatabases' => self::getInvalidBooleanValues(), + 'filter' => self::getInvalidDocumentValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'nameOnly' => self::getInvalidBooleanValues(), + 'session' => self::getInvalidSessionValues(), ]); } } diff --git a/tests/Database/DatabaseFunctionalTest.php b/tests/Database/DatabaseFunctionalTest.php index cb536a50f..2647f9f8b 100644 --- a/tests/Database/DatabaseFunctionalTest.php +++ b/tests/Database/DatabaseFunctionalTest.php @@ -30,7 +30,7 @@ public function testConstructorDatabaseNameArgument($databaseName, string $expec new Database($this->manager, $databaseName); } - public function provideInvalidDatabaseNames() + public static function provideInvalidDatabaseNames() { return [ [null, TypeError::class], @@ -45,13 +45,13 @@ public function testConstructorOptionTypeChecks(array $options): void new Database($this->manager, $this->getDatabaseName(), $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Exception/InvalidArgumentExceptionTest.php b/tests/Exception/InvalidArgumentExceptionTest.php index c4ab90a1d..a7a13d40a 100644 --- a/tests/Exception/InvalidArgumentExceptionTest.php +++ b/tests/Exception/InvalidArgumentExceptionTest.php @@ -15,7 +15,7 @@ public function testExpectedTypeFormatting($expectedType, $typeString): void $this->assertStringContainsString($typeString, $e->getMessage()); } - public function provideExpectedTypes() + public static function provideExpectedTypes() { yield 'expectedType is a string' => [ 'array', diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index a673ef719..cab0b529a 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -32,7 +32,7 @@ public function testApplyTypeMapToDocument($document, array $typeMap, $expectedD $this->assertEquals($expectedDocument, apply_type_map_to_document($document, $typeMap)); } - public function provideDocumentAndTypeMap() + public static function provideDocumentAndTypeMap() { return [ [ @@ -102,7 +102,7 @@ public function testDocumentToArray($document, array $expectedArray): void $this->assertSame($expectedArray, document_to_array($document)); } - public function provideDocumentsAndExpectedArrays(): array + public static function provideDocumentsAndExpectedArrays(): array { return [ 'array' => [['x' => 1], ['x' => 1]], @@ -122,13 +122,13 @@ public function testDocumentToArrayArgumentTypeCheck($document): void document_to_array($document); } - public function provideInvalidDocumentValuesForChecks(): array + public static function provideInvalidDocumentValuesForChecks(): array { // PackedArray is intentionally left out, as document_to_array is used to convert aggregation pipelines - return $this->wrapValuesForDataProvider([123, 3.14, 'foo', true]); + return self::wrapValuesForDataProvider([123, 3.14, 'foo', true]); } - public function provideDocumentCasts(): array + public static function provideDocumentCasts(): array { // phpcs:disable SlevomatCodingStandard.ControlStructures.JumpStatementsSpacing // phpcs:disable Squiz.Functions.MultiLineFunctionDeclaration @@ -180,7 +180,7 @@ public function testCreateFieldPathTypeMap(array $expected, array $typeMap, $fie $this->assertEquals($expected, create_field_path_type_map($typeMap, $fieldPath)); } - public function provideTypeMapValues() + public static function provideTypeMapValues() { return [ 'No root type' => [ @@ -256,7 +256,7 @@ public function testIsPipeline($expected, $pipeline, $allowEmpty = false): void $this->assertSame($expected, is_pipeline($pipeline, $allowEmpty)); } - public function providePipelines(): array + public static function providePipelines(): array { $valid = [ ['$match' => ['foo' => 'bar']], @@ -320,7 +320,7 @@ public function testIsBuilderPipeline($expected, $pipeline): void $this->assertSame($expected, is_builder_pipeline($pipeline)); } - public function provideStagePipelines(): iterable + public static function provideStagePipelines(): iterable { yield 'empty array' => [false, []]; yield 'array of arrays' => [false, [['$match' => ['x' => 1]]]]; @@ -335,7 +335,7 @@ public function testIsWriteConcernAcknowledged($expected, WriteConcern $writeCon $this->assertSame($expected, is_write_concern_acknowledged($writeConcern)); } - public function provideWriteConcerns(): array + public static function provideWriteConcerns(): array { // Note: WriteConcern constructor prohibits w=-1 or w=0 and journal=true return [ diff --git a/tests/GridFS/BucketFunctionalTest.php b/tests/GridFS/BucketFunctionalTest.php index 795764900..2f2d126d4 100644 --- a/tests/GridFS/BucketFunctionalTest.php +++ b/tests/GridFS/BucketFunctionalTest.php @@ -71,17 +71,17 @@ public function testConstructorOptionTypeChecks(array $options): void new Bucket($this->manager, $this->getDatabaseName(), $options); } - public function provideInvalidConstructorOptions() - { - return $this->createOptionDataProvider([ - 'bucketName' => $this->getInvalidStringValues(true), - 'chunkSizeBytes' => $this->getInvalidIntegerValues(true), - 'codec' => $this->getInvalidDocumentCodecValues(), - 'disableMD5' => $this->getInvalidBooleanValues(true), - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + public static function provideInvalidConstructorOptions() + { + return self::createOptionDataProvider([ + 'bucketName' => self::getInvalidStringValues(true), + 'chunkSizeBytes' => self::getInvalidIntegerValues(true), + 'codec' => self::getInvalidDocumentCodecValues(), + 'disableMD5' => self::getInvalidBooleanValues(true), + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } @@ -106,7 +106,7 @@ public function testConstructorWithCodecAndTypeMapOptions(): void /** @dataProvider provideInputDataAndExpectedChunks */ public function testDelete($input, $expectedChunks): void { - $id = $this->bucket->uploadFromStream('filename', $this->createStream($input)); + $id = $this->bucket->uploadFromStream('filename', self::createStream($input)); $this->assertCollectionCount($this->filesCollection, 1); $this->assertCollectionCount($this->chunksCollection, $expectedChunks); @@ -117,7 +117,7 @@ public function testDelete($input, $expectedChunks): void $this->assertCollectionCount($this->chunksCollection, 0); } - public function provideInputDataAndExpectedChunks() + public static function provideInputDataAndExpectedChunks() { return [ ['', 0], @@ -142,7 +142,7 @@ public function testDeleteShouldRequireFileToExist(): void /** @dataProvider provideInputDataAndExpectedChunks */ public function testDeleteStillRemovesChunksIfFileDoesNotExist($input, $expectedChunks): void { - $id = $this->bucket->uploadFromStream('filename', $this->createStream($input)); + $id = $this->bucket->uploadFromStream('filename', self::createStream($input)); $this->assertCollectionCount($this->filesCollection, 1); $this->assertCollectionCount($this->chunksCollection, $expectedChunks); @@ -160,7 +160,7 @@ public function testDeleteStillRemovesChunksIfFileDoesNotExist($input, $expected public function testDownloadingFileWithMissingChunk(): void { - $id = $this->bucket->uploadFromStream('filename', $this->createStream('foobar')); + $id = $this->bucket->uploadFromStream('filename', self::createStream('foobar')); $this->chunksCollection->deleteOne(['files_id' => $id, 'n' => 0]); @@ -171,7 +171,7 @@ public function testDownloadingFileWithMissingChunk(): void public function testDownloadingFileWithUnexpectedChunkIndex(): void { - $id = $this->bucket->uploadFromStream('filename', $this->createStream('foobar')); + $id = $this->bucket->uploadFromStream('filename', self::createStream('foobar')); $this->chunksCollection->updateOne( ['files_id' => $id, 'n' => 0], @@ -185,7 +185,7 @@ public function testDownloadingFileWithUnexpectedChunkIndex(): void public function testDownloadingFileWithUnexpectedChunkSize(): void { - $id = $this->bucket->uploadFromStream('filename', $this->createStream('foobar')); + $id = $this->bucket->uploadFromStream('filename', self::createStream('foobar')); $this->chunksCollection->updateOne( ['files_id' => $id, 'n' => 0], @@ -200,8 +200,8 @@ public function testDownloadingFileWithUnexpectedChunkSize(): void /** @dataProvider provideInputDataAndExpectedChunks */ public function testDownloadToStream($input): void { - $id = $this->bucket->uploadFromStream('filename', $this->createStream($input)); - $destination = $this->createStream(); + $id = $this->bucket->uploadFromStream('filename', self::createStream($input)); + $destination = self::createStream(); $this->bucket->downloadToStream($id, $destination); $this->assertStreamContents($input, $destination); @@ -214,48 +214,48 @@ public function testDownloadToStreamShouldRequireDestinationStream($destination) $this->bucket->downloadToStream('id', $destination); } - public function provideInvalidStreamValues() + public static function provideInvalidStreamValues(): array { - return $this->wrapValuesForDataProvider($this->getInvalidStreamValues()); + return self::wrapValuesForDataProvider(self::getInvalidStreamValues()); } public function testDownloadToStreamShouldRequireFileToExist(): void { $this->expectException(FileNotFoundException::class); - $this->bucket->downloadToStream('nonexistent-id', $this->createStream()); + $this->bucket->downloadToStream('nonexistent-id', self::createStream()); } public function testDownloadToStreamByName(): void { - $this->bucket->uploadFromStream('filename', $this->createStream('foo')); - $this->bucket->uploadFromStream('filename', $this->createStream('bar')); - $this->bucket->uploadFromStream('filename', $this->createStream('baz')); + $this->bucket->uploadFromStream('filename', self::createStream('foo')); + $this->bucket->uploadFromStream('filename', self::createStream('bar')); + $this->bucket->uploadFromStream('filename', self::createStream('baz')); - $destination = $this->createStream(); + $destination = self::createStream(); $this->bucket->downloadToStreamByName('filename', $destination); $this->assertStreamContents('baz', $destination); - $destination = $this->createStream(); + $destination = self::createStream(); $this->bucket->downloadToStreamByName('filename', $destination, ['revision' => -3]); $this->assertStreamContents('foo', $destination); - $destination = $this->createStream(); + $destination = self::createStream(); $this->bucket->downloadToStreamByName('filename', $destination, ['revision' => -2]); $this->assertStreamContents('bar', $destination); - $destination = $this->createStream(); + $destination = self::createStream(); $this->bucket->downloadToStreamByName('filename', $destination, ['revision' => -1]); $this->assertStreamContents('baz', $destination); - $destination = $this->createStream(); + $destination = self::createStream(); $this->bucket->downloadToStreamByName('filename', $destination, ['revision' => 0]); $this->assertStreamContents('foo', $destination); - $destination = $this->createStream(); + $destination = self::createStream(); $this->bucket->downloadToStreamByName('filename', $destination, ['revision' => 1]); $this->assertStreamContents('bar', $destination); - $destination = $this->createStream(); + $destination = self::createStream(); $this->bucket->downloadToStreamByName('filename', $destination, ['revision' => 2]); $this->assertStreamContents('baz', $destination); } @@ -270,15 +270,15 @@ public function testDownloadToStreamByNameShouldRequireDestinationStream($destin /** @dataProvider provideNonexistentFilenameAndRevision */ public function testDownloadToStreamByNameShouldRequireFilenameAndRevisionToExist($filename, $revision): void { - $this->bucket->uploadFromStream('filename', $this->createStream('foo')); - $this->bucket->uploadFromStream('filename', $this->createStream('bar')); + $this->bucket->uploadFromStream('filename', self::createStream('foo')); + $this->bucket->uploadFromStream('filename', self::createStream('bar')); - $destination = $this->createStream(); + $destination = self::createStream(); $this->expectException(FileNotFoundException::class); $this->bucket->downloadToStreamByName($filename, $destination, ['revision' => $revision]); } - public function provideNonexistentFilenameAndRevision() + public static function provideNonexistentFilenameAndRevision() { return [ ['filename', 2], @@ -290,7 +290,7 @@ public function provideNonexistentFilenameAndRevision() public function testDrop(): void { - $this->bucket->uploadFromStream('filename', $this->createStream('foobar')); + $this->bucket->uploadFromStream('filename', self::createStream('foobar')); $this->assertCollectionCount($this->filesCollection, 1); $this->assertCollectionCount($this->chunksCollection, 1); @@ -303,9 +303,9 @@ public function testDrop(): void public function testFind(): void { - $this->bucket->uploadFromStream('a', $this->createStream('foo')); - $this->bucket->uploadFromStream('b', $this->createStream('foobar')); - $this->bucket->uploadFromStream('c', $this->createStream('foobarbaz')); + $this->bucket->uploadFromStream('a', self::createStream('foo')); + $this->bucket->uploadFromStream('b', self::createStream('foobar')); + $this->bucket->uploadFromStream('c', self::createStream('foobarbaz')); $cursor = $this->bucket->find( ['length' => ['$lte' => 6]], @@ -329,7 +329,7 @@ public function testFind(): void public function testFindUsesTypeMap(): void { - $this->bucket->uploadFromStream('a', $this->createStream('foo')); + $this->bucket->uploadFromStream('a', self::createStream('foo')); $cursor = $this->bucket->find(); $fileDocument = current($cursor->toArray()); @@ -339,7 +339,7 @@ public function testFindUsesTypeMap(): void public function testFindUsesCodec(): void { - $this->bucket->uploadFromStream('a', $this->createStream('foo')); + $this->bucket->uploadFromStream('a', self::createStream('foo')); $cursor = $this->bucket->find([], ['codec' => new TestFileCodec()]); $fileDocument = current($cursor->toArray()); @@ -351,7 +351,7 @@ public function testFindUsesCodec(): void public function testFindInheritsBucketCodec(): void { $bucket = new Bucket($this->manager, $this->getDatabaseName(), ['codec' => new TestFileCodec()]); - $bucket->uploadFromStream('a', $this->createStream('foo')); + $bucket->uploadFromStream('a', self::createStream('foo')); $cursor = $bucket->find(); $fileDocument = current($cursor->toArray()); @@ -363,7 +363,7 @@ public function testFindInheritsBucketCodec(): void public function testFindResetsInheritedBucketCodec(): void { $bucket = new Bucket($this->manager, $this->getDatabaseName(), ['codec' => new TestFileCodec()]); - $bucket->uploadFromStream('a', $this->createStream('foo')); + $bucket->uploadFromStream('a', self::createStream('foo')); $cursor = $bucket->find([], ['codec' => null]); $fileDocument = current($cursor->toArray()); @@ -374,9 +374,9 @@ public function testFindResetsInheritedBucketCodec(): void public function testFindOne(): void { - $this->bucket->uploadFromStream('a', $this->createStream('foo')); - $this->bucket->uploadFromStream('b', $this->createStream('foobar')); - $this->bucket->uploadFromStream('c', $this->createStream('foobarbaz')); + $this->bucket->uploadFromStream('a', self::createStream('foo')); + $this->bucket->uploadFromStream('b', self::createStream('foobar')); + $this->bucket->uploadFromStream('c', self::createStream('foobarbaz')); $fileDocument = $this->bucket->findOne( ['length' => ['$lte' => 6]], @@ -396,9 +396,9 @@ public function testFindOne(): void public function testFindOneUsesCodec(): void { - $this->bucket->uploadFromStream('a', $this->createStream('foo')); - $this->bucket->uploadFromStream('b', $this->createStream('foobar')); - $this->bucket->uploadFromStream('c', $this->createStream('foobarbaz')); + $this->bucket->uploadFromStream('a', self::createStream('foo')); + $this->bucket->uploadFromStream('b', self::createStream('foobar')); + $this->bucket->uploadFromStream('c', self::createStream('foobarbaz')); $fileDocument = $this->bucket->findOne( ['length' => ['$lte' => 6]], @@ -417,9 +417,9 @@ public function testFindOneInheritsBucketCodec(): void { $bucket = new Bucket($this->manager, $this->getDatabaseName(), ['codec' => new TestFileCodec()]); - $bucket->uploadFromStream('a', $this->createStream('foo')); - $bucket->uploadFromStream('b', $this->createStream('foobar')); - $bucket->uploadFromStream('c', $this->createStream('foobarbaz')); + $bucket->uploadFromStream('a', self::createStream('foo')); + $bucket->uploadFromStream('b', self::createStream('foobar')); + $bucket->uploadFromStream('c', self::createStream('foobarbaz')); $fileDocument = $bucket->findOne( ['length' => ['$lte' => 6]], @@ -435,9 +435,9 @@ public function testFindOneResetsInheritedBucketCodec(): void { $bucket = new Bucket($this->manager, $this->getDatabaseName(), ['codec' => new TestFileCodec()]); - $bucket->uploadFromStream('a', $this->createStream('foo')); - $bucket->uploadFromStream('b', $this->createStream('foobar')); - $bucket->uploadFromStream('c', $this->createStream('foobarbaz')); + $bucket->uploadFromStream('a', self::createStream('foo')); + $bucket->uploadFromStream('b', self::createStream('foobar')); + $bucket->uploadFromStream('c', self::createStream('foobarbaz')); $fileDocument = $bucket->findOne( ['length' => ['$lte' => 6]], @@ -520,7 +520,7 @@ public function testGetFileDocumentForStreamUsesCodec(): void public function testGetFileDocumentForStreamWithReadableStream(): void { $metadata = ['foo' => 'bar']; - $id = $this->bucket->uploadFromStream('filename', $this->createStream('foobar'), ['metadata' => $metadata]); + $id = $this->bucket->uploadFromStream('filename', self::createStream('foobar'), ['metadata' => $metadata]); $stream = $this->bucket->openDownloadStream($id); $fileDocument = $this->bucket->getFileDocumentForStream($stream); @@ -550,9 +550,9 @@ public function testGetFileDocumentForStreamShouldRequireGridFSStreamResource($s $this->bucket->getFileDocumentForStream($stream); } - public function provideInvalidGridFSStreamValues() + public static function provideInvalidGridFSStreamValues(): array { - return $this->wrapValuesForDataProvider(array_merge($this->getInvalidStreamValues(), [$this->createStream()])); + return self::wrapValuesForDataProvider(array_merge(self::getInvalidStreamValues(), [self::createStream()])); } public function testGetFileIdForStreamUsesTypeMap(): void @@ -567,7 +567,7 @@ public function testGetFileIdForStreamUsesTypeMap(): void public function testGetFileIdForStreamWithReadableStream(): void { - $id = $this->bucket->uploadFromStream('filename', $this->createStream('foobar')); + $id = $this->bucket->uploadFromStream('filename', self::createStream('foobar')); $stream = $this->bucket->openDownloadStream($id); $this->assertSameObjectId($id, $this->bucket->getFileIdForStream($stream)); @@ -598,7 +598,7 @@ public function testGetFilesCollection(): void /** @dataProvider provideInputDataAndExpectedChunks */ public function testOpenDownloadStream($input): void { - $id = $this->bucket->uploadFromStream('filename', $this->createStream($input)); + $id = $this->bucket->uploadFromStream('filename', self::createStream($input)); $this->assertStreamContents($input, $this->bucket->openDownloadStream($id)); } @@ -606,7 +606,7 @@ public function testOpenDownloadStream($input): void /** @dataProvider provideInputDataAndExpectedChunks */ public function testOpenDownloadStreamAndMultipleReadOperations($input): void { - $id = $this->bucket->uploadFromStream('filename', $this->createStream($input)); + $id = $this->bucket->uploadFromStream('filename', self::createStream($input)); $stream = $this->bucket->openDownloadStream($id); $buffer = ''; @@ -636,9 +636,9 @@ public function testOpenDownloadStreamByNameShouldRequireFilenameToExist(): void public function testOpenDownloadStreamByName(): void { - $this->bucket->uploadFromStream('filename', $this->createStream('foo')); - $this->bucket->uploadFromStream('filename', $this->createStream('bar')); - $this->bucket->uploadFromStream('filename', $this->createStream('baz')); + $this->bucket->uploadFromStream('filename', self::createStream('foo')); + $this->bucket->uploadFromStream('filename', self::createStream('bar')); + $this->bucket->uploadFromStream('filename', self::createStream('baz')); $this->assertStreamContents('baz', $this->bucket->openDownloadStreamByName('filename')); $this->assertStreamContents('foo', $this->bucket->openDownloadStreamByName('filename', ['revision' => -3])); @@ -652,8 +652,8 @@ public function testOpenDownloadStreamByName(): void /** @dataProvider provideNonexistentFilenameAndRevision */ public function testOpenDownloadStreamByNameShouldRequireFilenameAndRevisionToExist($filename, $revision): void { - $this->bucket->uploadFromStream('filename', $this->createStream('foo')); - $this->bucket->uploadFromStream('filename', $this->createStream('bar')); + $this->bucket->uploadFromStream('filename', self::createStream('foo')); + $this->bucket->uploadFromStream('filename', self::createStream('bar')); $this->expectException(FileNotFoundException::class); $this->bucket->openDownloadStreamByName($filename, ['revision' => $revision]); @@ -689,7 +689,7 @@ public function testOpenUploadStreamAndMultipleWriteOperations($input): void public function testRename(): void { - $id = $this->bucket->uploadFromStream('a', $this->createStream('foo')); + $id = $this->bucket->uploadFromStream('a', self::createStream('foo')); $this->bucket->rename($id, 'b'); $fileDocument = $this->filesCollection->findOne( @@ -703,7 +703,7 @@ public function testRename(): void public function testRenameShouldNotRequireFileToBeModified(): void { - $id = $this->bucket->uploadFromStream('a', $this->createStream('foo')); + $id = $this->bucket->uploadFromStream('a', self::createStream('foo')); $this->bucket->rename($id, 'a'); $fileDocument = $this->filesCollection->findOne( @@ -729,7 +729,7 @@ public function testUploadFromStream(): void 'metadata' => ['foo' => 'bar'], ]; - $id = $this->bucket->uploadFromStream('filename', $this->createStream('foobar'), $options); + $id = $this->bucket->uploadFromStream('filename', self::createStream('foobar'), $options); $this->assertCollectionCount($this->filesCollection, 1); $this->assertCollectionCount($this->chunksCollection, 3); @@ -749,8 +749,8 @@ public function testUploadFromStreamShouldRequireSourceStream($source): void public function testUploadingAnEmptyFile(): void { - $id = $this->bucket->uploadFromStream('filename', $this->createStream('')); - $destination = $this->createStream(); + $id = $this->bucket->uploadFromStream('filename', self::createStream('')); + $destination = self::createStream(); $this->bucket->downloadToStream($id, $destination); $this->assertStreamContents('', $destination); @@ -779,7 +779,7 @@ public function testUploadingAnEmptyFile(): void public function testDisableMD5(): void { $options = ['disableMD5' => true]; - $id = $this->bucket->uploadFromStream('filename', $this->createStream('data'), $options); + $id = $this->bucket->uploadFromStream('filename', self::createStream('data'), $options); $fileDocument = $this->filesCollection->findOne( ['_id' => $id], @@ -793,7 +793,7 @@ public function testDisableMD5OptionInConstructor(): void $options = ['disableMD5' => true]; $this->bucket = new Bucket($this->manager, $this->getDatabaseName(), $options); - $id = $this->bucket->uploadFromStream('filename', $this->createStream('data')); + $id = $this->bucket->uploadFromStream('filename', self::createStream('data')); $fileDocument = $this->filesCollection->findOne( ['_id' => $id], @@ -804,7 +804,7 @@ public function testDisableMD5OptionInConstructor(): void public function testUploadingFirstFileCreatesIndexes(): void { - $this->bucket->uploadFromStream('filename', $this->createStream('foo')); + $this->bucket->uploadFromStream('filename', self::createStream('foo')); $this->assertIndexExists($this->filesCollection->getCollectionName(), 'filename_1_uploadDate_1'); $this->assertIndexExists($this->chunksCollection->getCollectionName(), 'files_id_1_n_1', function (IndexInfo $info): void { @@ -817,7 +817,7 @@ public function testExistingIndexIsReused(): void $this->filesCollection->createIndex(['filename' => 1.0, 'uploadDate' => 1], ['name' => 'test']); $this->chunksCollection->createIndex(['files_id' => 1.0, 'n' => 1], ['name' => 'test', 'unique' => true]); - $this->bucket->uploadFromStream('filename', $this->createStream('foo')); + $this->bucket->uploadFromStream('filename', self::createStream('foo')); $this->assertIndexNotExists($this->filesCollection->getCollectionName(), 'filename_1_uploadDate_1'); $this->assertIndexNotExists($this->chunksCollection->getCollectionName(), 'files_id_1_n_1'); @@ -825,7 +825,7 @@ public function testExistingIndexIsReused(): void public function testDownloadToStreamFails(): void { - $this->bucket->uploadFromStream('filename', $this->createStream('foo'), ['_id' => ['foo' => 'bar']]); + $this->bucket->uploadFromStream('filename', self::createStream('foo'), ['_id' => ['foo' => 'bar']]); $this->expectException(StreamException::class); $this->expectExceptionMessageMatches('#^Downloading file from "gridfs://.*/.*/.*" to "php://temp" failed. GridFS identifier: "{ "_id" : { "foo" : "bar" } }"$#'); @@ -834,7 +834,7 @@ public function testDownloadToStreamFails(): void public function testDownloadToStreamByNameFails(): void { - $this->bucket->uploadFromStream('filename', $this->createStream('foo')); + $this->bucket->uploadFromStream('filename', self::createStream('foo')); $this->expectException(StreamException::class); $this->expectExceptionMessageMatches('#^Downloading file from "gridfs://.*/.*/.*" to "php://temp" failed. GridFS filename: "filename"$#'); @@ -1029,7 +1029,7 @@ private function assertIndexNotExists(string $collectionName, string $indexName) /** * Return a list of invalid stream values. */ - private function getInvalidStreamValues(): array + private static function getInvalidStreamValues(): array { return [null, 123, 'foo', [], hash_init('md5')]; } diff --git a/tests/GridFS/FunctionalTestCase.php b/tests/GridFS/FunctionalTestCase.php index 1508fef68..d010ca929 100644 --- a/tests/GridFS/FunctionalTestCase.php +++ b/tests/GridFS/FunctionalTestCase.php @@ -53,7 +53,7 @@ protected function assertStreamContents(string $expectedContents, $stream): void * * @return resource */ - protected function createStream(string $data = '') + protected static function createStream(string $data = '') { $stream = fopen('php://temp', 'w+b'); fwrite($stream, $data); diff --git a/tests/GridFS/ReadableStreamFunctionalTest.php b/tests/GridFS/ReadableStreamFunctionalTest.php index dba7e0016..600181fbb 100644 --- a/tests/GridFS/ReadableStreamFunctionalTest.php +++ b/tests/GridFS/ReadableStreamFunctionalTest.php @@ -57,15 +57,15 @@ public function testConstructorFileDocumentChecks($file): void new ReadableStream($this->collectionWrapper, $file); } - public function provideInvalidConstructorFileDocuments() + public static function provideInvalidConstructorFileDocuments() { $options = []; - foreach ($this->getInvalidIntegerValues() as $value) { + foreach (self::getInvalidIntegerValues() as $value) { $options[][] = (object) ['_id' => 1, 'chunkSize' => $value, 'length' => 0]; } - foreach ($this->getInvalidIntegerValues() as $value) { + foreach (self::getInvalidIntegerValues() as $value) { $options[][] = (object) ['_id' => 1, 'chunkSize' => 1, 'length' => $value]; } @@ -85,7 +85,7 @@ public function testReadBytes($fileId, $length, $expectedBytes): void $this->assertSame($expectedBytes, $stream->readBytes($length)); } - public function provideFileIdAndExpectedBytes() + public static function provideFileIdAndExpectedBytes() { return [ ['length-0', 0, ''], @@ -111,10 +111,10 @@ public function provideFileIdAndExpectedBytes() ]; } - public function provideFilteredFileIdAndExpectedBytes() + public static function provideFilteredFileIdAndExpectedBytes() { return array_filter( - $this->provideFileIdAndExpectedBytes(), + self::provideFileIdAndExpectedBytes(), fn (array $args) => $args[1] > 0, ); } @@ -221,7 +221,7 @@ function (array $event) use (&$commands): void { $this->assertSame(['find'], $commands); } - public function providePreviousChunkSeekOffsetAndBytes() + public static function providePreviousChunkSeekOffsetAndBytes() { return [ [0, 4, 'abcd'], @@ -255,7 +255,7 @@ function (array $event) use (&$commands): void { $this->assertSame([], $commands); } - public function provideSameChunkSeekOffsetAndBytes() + public static function provideSameChunkSeekOffsetAndBytes() { return [ [4, 4, 'efgh'], @@ -287,7 +287,7 @@ function (array $event) use (&$commands): void { $this->assertSame([], $commands); } - public function provideSubsequentChunkSeekOffsetAndBytes() + public static function provideSubsequentChunkSeekOffsetAndBytes() { return [ [4, 4, 'efgh'], diff --git a/tests/GridFS/WritableStreamFunctionalTest.php b/tests/GridFS/WritableStreamFunctionalTest.php index f87eaf1aa..a46741fae 100644 --- a/tests/GridFS/WritableStreamFunctionalTest.php +++ b/tests/GridFS/WritableStreamFunctionalTest.php @@ -39,12 +39,12 @@ public function testConstructorOptionTypeChecks(array $options): void new WritableStream($this->collectionWrapper, 'filename', $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'chunkSizeBytes' => $this->getInvalidIntegerValues(true), - 'disableMD5' => $this->getInvalidBooleanValues(true), - 'metadata' => $this->getInvalidDocumentValues(), + return self::createOptionDataProvider([ + 'chunkSizeBytes' => self::getInvalidIntegerValues(true), + 'disableMD5' => self::getInvalidBooleanValues(true), + 'metadata' => self::getInvalidDocumentValues(), ]); } @@ -86,7 +86,7 @@ public function testWriteBytesCalculatesMD5($input, $expectedMD5): void $this->assertSameDocument(['md5' => $expectedMD5], $fileDocument); } - public function provideInputDataAndExpectedMD5() + public static function provideInputDataAndExpectedMD5() { return [ ['', 'd41d8cd98f00b204e9800998ecf8427e'], diff --git a/tests/Model/BSONIteratorTest.php b/tests/Model/BSONIteratorTest.php index ff1a8bd1c..3ec945e39 100644 --- a/tests/Model/BSONIteratorTest.php +++ b/tests/Model/BSONIteratorTest.php @@ -33,7 +33,7 @@ public function testValidValues(?array $typeMap, array $expectedDocuments): void $this->assertEquals($expectedDocuments, $results); } - public function provideTypeMapOptionsAndExpectedDocuments(): Generator + public static function provideTypeMapOptionsAndExpectedDocuments(): Generator { yield 'No type map' => [ 'typeMap' => null, diff --git a/tests/Model/ChangeStreamIteratorTest.php b/tests/Model/ChangeStreamIteratorTest.php index 890870445..caba4726c 100644 --- a/tests/Model/ChangeStreamIteratorTest.php +++ b/tests/Model/ChangeStreamIteratorTest.php @@ -64,9 +64,9 @@ public function testPostBatchResumeTokenArgumentTypeCheck($postBatchResumeToken) new ChangeStreamIterator($this->collection->find(), 0, null, $postBatchResumeToken); } - public function provideInvalidObjectValues() + public static function provideInvalidObjectValues() { - return $this->wrapValuesForDataProvider([123, 3.14, 'foo', true, []]); + return self::wrapValuesForDataProvider([123, 3.14, 'foo', true, []]); } public function testPostBatchResumeTokenIsReturnedForLastElementInFirstBatch(): void diff --git a/tests/Model/IndexInputTest.php b/tests/Model/IndexInputTest.php index bc0539b51..6b4051e13 100644 --- a/tests/Model/IndexInputTest.php +++ b/tests/Model/IndexInputTest.php @@ -35,9 +35,9 @@ public function testConstructorShouldRequireKeyFieldOrderToBeNumericOrString($or new IndexInput(['key' => ['x' => $order]]); } - public function provideInvalidFieldOrderValues() + public static function provideInvalidFieldOrderValues() { - return $this->wrapValuesForDataProvider([true, [], new stdClass()]); + return self::wrapValuesForDataProvider([true, [], new stdClass()]); } /** @dataProvider provideInvalidStringValues */ @@ -54,7 +54,7 @@ public function testNameGeneration($expectedName, array|object $key): void $this->assertSame($expectedName, (string) new IndexInput(['key' => $key])); } - public function provideExpectedNameAndKey(): array + public static function provideExpectedNameAndKey(): array { return [ ['x_1', ['x' => 1]], diff --git a/tests/Operation/AggregateFunctionalTest.php b/tests/Operation/AggregateFunctionalTest.php index 2b45b49c3..290446f32 100644 --- a/tests/Operation/AggregateFunctionalTest.php +++ b/tests/Operation/AggregateFunctionalTest.php @@ -271,7 +271,7 @@ function (array $event): void { ); } - public function provideTypeMapOptionsAndExpectedDocuments() + public static function provideTypeMapOptionsAndExpectedDocuments() { return [ [ diff --git a/tests/Operation/AggregateTest.php b/tests/Operation/AggregateTest.php index 81128dca4..237ddfc69 100644 --- a/tests/Operation/AggregateTest.php +++ b/tests/Operation/AggregateTest.php @@ -24,24 +24,24 @@ public function testConstructorOptionTypeChecks(array $options): void new Aggregate($this->getDatabaseName(), $this->getCollectionName(), [['$match' => ['x' => 1]]], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'allowDiskUse' => $this->getInvalidBooleanValues(), - 'batchSize' => $this->getInvalidIntegerValues(), - 'bypassDocumentValidation' => $this->getInvalidBooleanValues(), - 'codec' => $this->getInvalidDocumentCodecValues(), - 'collation' => $this->getInvalidDocumentValues(), - 'hint' => $this->getInvalidHintValues(), - 'let' => $this->getInvalidDocumentValues(), - 'explain' => $this->getInvalidBooleanValues(), - 'maxAwaitTimeMS' => $this->getInvalidIntegerValues(), - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'session' => $this->getInvalidSessionValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'allowDiskUse' => self::getInvalidBooleanValues(), + 'batchSize' => self::getInvalidIntegerValues(), + 'bypassDocumentValidation' => self::getInvalidBooleanValues(), + 'codec' => self::getInvalidDocumentCodecValues(), + 'collation' => self::getInvalidDocumentValues(), + 'hint' => self::getInvalidHintValues(), + 'let' => self::getInvalidDocumentValues(), + 'explain' => self::getInvalidBooleanValues(), + 'maxAwaitTimeMS' => self::getInvalidIntegerValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'session' => self::getInvalidSessionValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/BulkWriteFunctionalTest.php b/tests/Operation/BulkWriteFunctionalTest.php index 96006d689..30213cca7 100644 --- a/tests/Operation/BulkWriteFunctionalTest.php +++ b/tests/Operation/BulkWriteFunctionalTest.php @@ -87,7 +87,7 @@ function (array $event) use ($expectedDocument): void { ); } - public function provideDocumentsWithIds(): array + public static function provideDocumentsWithIds(): array { $expectedDocument = (object) ['_id' => 1]; @@ -99,7 +99,7 @@ public function provideDocumentsWithIds(): array ]; } - public function provideDocumentsWithoutIds(): array + public static function provideDocumentsWithoutIds(): array { /* Note: _id placeholders must be replaced with generated ObjectIds. We * also clone the value for each data set since tests may need to modify diff --git a/tests/Operation/BulkWriteTest.php b/tests/Operation/BulkWriteTest.php index e0783ad38..214305e57 100644 --- a/tests/Operation/BulkWriteTest.php +++ b/tests/Operation/BulkWriteTest.php @@ -229,9 +229,9 @@ public function testReplaceOneUpsertOptionTypeCheck($upsert): void ]); } - public function provideInvalidBooleanValues() + public static function provideInvalidBooleanValues() { - return $this->wrapValuesForDataProvider($this->getInvalidBooleanValues()); + return self::wrapValuesForDataProvider(self::getInvalidBooleanValues()); } public function testReplaceOneWithCodecRejectsInvalidDocuments(): void @@ -442,14 +442,14 @@ public function testConstructorOptionTypeChecks(array $options): void ); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'bypassDocumentValidation' => $this->getInvalidBooleanValues(), - 'codec' => $this->getInvalidDocumentCodecValues(), - 'ordered' => $this->getInvalidBooleanValues(true), - 'session' => $this->getInvalidSessionValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'bypassDocumentValidation' => self::getInvalidBooleanValues(), + 'codec' => self::getInvalidDocumentCodecValues(), + 'ordered' => self::getInvalidBooleanValues(true), + 'session' => self::getInvalidSessionValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } } diff --git a/tests/Operation/CountDocumentsTest.php b/tests/Operation/CountDocumentsTest.php index 0a16e4399..65e002a46 100644 --- a/tests/Operation/CountDocumentsTest.php +++ b/tests/Operation/CountDocumentsTest.php @@ -23,17 +23,17 @@ public function testConstructorOptionTypeChecks(array $options): void new CountDocuments($this->getDatabaseName(), $this->getCollectionName(), [], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'collation' => $this->getInvalidDocumentValues(), - 'hint' => $this->getInvalidHintValues(), - 'limit' => $this->getInvalidIntegerValues(), - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'session' => $this->getInvalidSessionValues(), - 'skip' => $this->getInvalidIntegerValues(), + return self::createOptionDataProvider([ + 'collation' => self::getInvalidDocumentValues(), + 'hint' => self::getInvalidHintValues(), + 'limit' => self::getInvalidIntegerValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'session' => self::getInvalidSessionValues(), + 'skip' => self::getInvalidIntegerValues(), ]); } } diff --git a/tests/Operation/CountTest.php b/tests/Operation/CountTest.php index a096859ae..8d109ae81 100644 --- a/tests/Operation/CountTest.php +++ b/tests/Operation/CountTest.php @@ -24,17 +24,17 @@ public function testConstructorOptionTypeChecks(array $options): void new Count($this->getDatabaseName(), $this->getCollectionName(), [], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'collation' => $this->getInvalidDocumentValues(), - 'hint' => $this->getInvalidHintValues(), - 'limit' => $this->getInvalidIntegerValues(), - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'session' => $this->getInvalidSessionValues(), - 'skip' => $this->getInvalidIntegerValues(), + return self::createOptionDataProvider([ + 'collation' => self::getInvalidDocumentValues(), + 'hint' => self::getInvalidHintValues(), + 'limit' => self::getInvalidIntegerValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'session' => self::getInvalidSessionValues(), + 'skip' => self::getInvalidIntegerValues(), ]); } diff --git a/tests/Operation/CreateCollectionTest.php b/tests/Operation/CreateCollectionTest.php index 956fa6ed7..530299ba4 100644 --- a/tests/Operation/CreateCollectionTest.php +++ b/tests/Operation/CreateCollectionTest.php @@ -21,31 +21,31 @@ public function testConstructorOptionTypeChecks(array $options): void new CreateCollection($this->getDatabaseName(), $this->getCollectionName(), $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'autoIndexId' => $this->getInvalidBooleanValues(), - 'capped' => $this->getInvalidBooleanValues(), - 'changeStreamPreAndPostImages' => $this->getInvalidDocumentValues(), - 'clusteredIndex' => $this->getInvalidDocumentValues(), - 'collation' => $this->getInvalidDocumentValues(), - 'encryptedFields' => $this->getInvalidDocumentValues(), - 'expireAfterSeconds' => $this->getInvalidIntegerValues(), - 'flags' => $this->getInvalidIntegerValues(), - 'indexOptionDefaults' => $this->getInvalidDocumentValues(), - 'max' => $this->getInvalidIntegerValues(), - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'pipeline' => $this->getInvalidArrayValues(), - 'session' => $this->getInvalidSessionValues(), - 'size' => $this->getInvalidIntegerValues(), - 'storageEngine' => $this->getInvalidDocumentValues(), - 'timeseries' => $this->getInvalidDocumentValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'validationAction' => $this->getInvalidStringValues(), - 'validationLevel' => $this->getInvalidStringValues(), - 'validator' => $this->getInvalidDocumentValues(), - 'viewOn' => $this->getInvalidStringValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'autoIndexId' => self::getInvalidBooleanValues(), + 'capped' => self::getInvalidBooleanValues(), + 'changeStreamPreAndPostImages' => self::getInvalidDocumentValues(), + 'clusteredIndex' => self::getInvalidDocumentValues(), + 'collation' => self::getInvalidDocumentValues(), + 'encryptedFields' => self::getInvalidDocumentValues(), + 'expireAfterSeconds' => self::getInvalidIntegerValues(), + 'flags' => self::getInvalidIntegerValues(), + 'indexOptionDefaults' => self::getInvalidDocumentValues(), + 'max' => self::getInvalidIntegerValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'pipeline' => self::getInvalidArrayValues(), + 'session' => self::getInvalidSessionValues(), + 'size' => self::getInvalidIntegerValues(), + 'storageEngine' => self::getInvalidDocumentValues(), + 'timeseries' => self::getInvalidDocumentValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'validationAction' => self::getInvalidStringValues(), + 'validationLevel' => self::getInvalidStringValues(), + 'validator' => self::getInvalidDocumentValues(), + 'viewOn' => self::getInvalidStringValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/CreateEncryptedCollectionFunctionalTest.php b/tests/Operation/CreateEncryptedCollectionFunctionalTest.php index dde1f7d4e..4c851edff 100644 --- a/tests/Operation/CreateEncryptedCollectionFunctionalTest.php +++ b/tests/Operation/CreateEncryptedCollectionFunctionalTest.php @@ -73,7 +73,7 @@ public function testCreateDataKeysNopIfFieldsIsMissing($input, array $expectedOu $this->assertSame($expectedOutput, $encryptedFieldsOutput); } - public function provideEncryptedFieldsAndFieldsIsMissing(): array + public static function provideEncryptedFieldsAndFieldsIsMissing(): array { $ef = []; @@ -104,7 +104,7 @@ public function testCreateDataKeysNopIfFieldsHasInvalidType($input, array $expec $this->assertSame($expectedOutput, $encryptedFieldsOutput); } - public function provideEncryptedFieldsAndFieldsHasInvalidType(): array + public static function provideEncryptedFieldsAndFieldsHasInvalidType(): array { $ef = ['fields' => 'not-an-array']; @@ -135,7 +135,7 @@ public function testCreateDataKeysSkipsNonDocumentFields($input, array $expected $this->assertSame($expectedOutput, $encryptedFieldsOutput); } - public function provideEncryptedFieldsElementHasInvalidType(): array + public static function provideEncryptedFieldsElementHasInvalidType(): array { $ef = ['fields' => ['not-an-array-or-object']]; @@ -190,7 +190,7 @@ public function testEncryptedFieldsDocuments($input): void $this->assertInstanceOf(Binary::class, $modifiedEncryptedFields['fields'][0]['keyId'] ?? null); } - public function provideEncryptedFields(): array + public static function provideEncryptedFields(): array { $ef = ['fields' => [['path' => 'ssn', 'bsonType' => 'string', 'keyId' => null]]]; diff --git a/tests/Operation/CreateEncryptedCollectionTest.php b/tests/Operation/CreateEncryptedCollectionTest.php index c3c22f5e3..fc38bc830 100644 --- a/tests/Operation/CreateEncryptedCollectionTest.php +++ b/tests/Operation/CreateEncryptedCollectionTest.php @@ -15,14 +15,14 @@ public function testConstructorOptionTypeChecks(array $options): void new CreateEncryptedCollection($this->getDatabaseName(), $this->getCollectionName(), $options); } - public function provideInvalidConstructorOptions(): Generator + public static function provideInvalidConstructorOptions(): Generator { yield 'encryptedFields is required' => [ [], ]; - yield from $this->createOptionDataProvider([ - 'encryptedFields' => $this->getInvalidDocumentValues(), + yield from self::createOptionDataProvider([ + 'encryptedFields' => self::getInvalidDocumentValues(), ]); } } diff --git a/tests/Operation/CreateIndexesFunctionalTest.php b/tests/Operation/CreateIndexesFunctionalTest.php index 41e92bb44..ba79971ac 100644 --- a/tests/Operation/CreateIndexesFunctionalTest.php +++ b/tests/Operation/CreateIndexesFunctionalTest.php @@ -121,7 +121,7 @@ public function testCreateIndexes(callable $cast): void }); } - public function provideKeyCasts(): array + public static function provideKeyCasts(): array { // phpcs:disable SlevomatCodingStandard.ControlStructures.JumpStatementsSpacing // phpcs:disable Squiz.Functions.MultiLineFunctionDeclaration diff --git a/tests/Operation/CreateIndexesTest.php b/tests/Operation/CreateIndexesTest.php index 04e0e86ba..2647bcc46 100644 --- a/tests/Operation/CreateIndexesTest.php +++ b/tests/Operation/CreateIndexesTest.php @@ -25,14 +25,14 @@ public function testConstructorOptionTypeChecks(array $options): void new CreateIndexes($this->getDatabaseName(), $this->getCollectionName(), [['key' => ['x' => 1]]], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ + return self::createOptionDataProvider([ // commitQuorum is int|string, for which no helper exists 'commitQuorum' => ['float' => 3.14, 'bool' => true, 'array' => [], 'object' => new stdClass()], - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'session' => $this->getInvalidSessionValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'session' => self::getInvalidSessionValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } @@ -51,9 +51,9 @@ public function testConstructorRequiresIndexSpecificationsToBeAnArray($index): v new CreateIndexes($this->getDatabaseName(), $this->getCollectionName(), [$index]); } - public function provideInvalidIndexSpecificationTypes() + public static function provideInvalidIndexSpecificationTypes() { - return $this->wrapValuesForDataProvider($this->getInvalidArrayValues()); + return self::wrapValuesForDataProvider(self::getInvalidArrayValues()); } public function testConstructorRequiresIndexSpecificationKey(): void @@ -79,7 +79,7 @@ public function testConstructorValidatesIndexSpecificationKeyOrder($key): void new CreateIndexes($this->getDatabaseName(), $this->getCollectionName(), [['key' => $key]]); } - public function provideKeyDocumentsWithInvalidOrder(): Generator + public static function provideKeyDocumentsWithInvalidOrder(): Generator { $invalidOrderValues = [true, [], new stdClass(), null]; diff --git a/tests/Operation/DatabaseCommandFunctionalTest.php b/tests/Operation/DatabaseCommandFunctionalTest.php index 6a1410669..861302595 100644 --- a/tests/Operation/DatabaseCommandFunctionalTest.php +++ b/tests/Operation/DatabaseCommandFunctionalTest.php @@ -28,7 +28,7 @@ function (array $event): void { ); } - public function provideCommandDocuments(): array + public static function provideCommandDocuments(): array { return [ 'array' => [['ping' => 1]], diff --git a/tests/Operation/DatabaseCommandTest.php b/tests/Operation/DatabaseCommandTest.php index 74b3d8343..869f0c7c3 100644 --- a/tests/Operation/DatabaseCommandTest.php +++ b/tests/Operation/DatabaseCommandTest.php @@ -23,12 +23,12 @@ public function testConstructorOptionTypeChecks(array $options): void new DatabaseCommand($this->getDatabaseName(), ['ping' => 1], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'session' => $this->getInvalidSessionValues(), - 'typeMap' => $this->getInvalidArrayValues(), + return self::createOptionDataProvider([ + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'session' => self::getInvalidSessionValues(), + 'typeMap' => self::getInvalidArrayValues(), ]); } } diff --git a/tests/Operation/DeleteTest.php b/tests/Operation/DeleteTest.php index 25d3b5ccf..818689610 100644 --- a/tests/Operation/DeleteTest.php +++ b/tests/Operation/DeleteTest.php @@ -37,9 +37,9 @@ public function testConstructorLimitArgumentMustBeOneOrZero($limit): void new Delete($this->getDatabaseName(), $this->getCollectionName(), [], $limit); } - public function provideInvalidLimitValues() + public static function provideInvalidLimitValues() { - return $this->wrapValuesForDataProvider([-1, 2]); + return self::wrapValuesForDataProvider([-1, 2]); } /** @dataProvider provideInvalidConstructorOptions */ @@ -49,14 +49,14 @@ public function testConstructorOptionTypeChecks(array $options): void new Delete($this->getDatabaseName(), $this->getCollectionName(), [], 1, $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'collation' => $this->getInvalidDocumentValues(), - 'hint' => $this->getInvalidHintValues(), - 'let' => $this->getInvalidDocumentValues(), - 'session' => $this->getInvalidSessionValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'collation' => self::getInvalidDocumentValues(), + 'hint' => self::getInvalidHintValues(), + 'let' => self::getInvalidDocumentValues(), + 'session' => self::getInvalidSessionValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/DistinctFunctionalTest.php b/tests/Operation/DistinctFunctionalTest.php index 4293293b0..d9a18307a 100644 --- a/tests/Operation/DistinctFunctionalTest.php +++ b/tests/Operation/DistinctFunctionalTest.php @@ -118,7 +118,7 @@ public function testTypeMapOption(array $typeMap, array $expectedDocuments): voi $this->assertEquals($expectedDocuments, $values); } - public function provideTypeMapOptionsAndExpectedDocuments() + public static function provideTypeMapOptionsAndExpectedDocuments() { return [ 'No type map' => [ diff --git a/tests/Operation/DistinctTest.php b/tests/Operation/DistinctTest.php index 77380f249..e181abb0a 100644 --- a/tests/Operation/DistinctTest.php +++ b/tests/Operation/DistinctTest.php @@ -25,15 +25,15 @@ public function testConstructorOptionTypeChecks(array $options): void new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', [], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'collation' => $this->getInvalidDocumentValues(), - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'session' => $this->getInvalidSessionValues(), - 'typeMap' => $this->getInvalidArrayValues(), + return self::createOptionDataProvider([ + 'collation' => self::getInvalidDocumentValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'session' => self::getInvalidSessionValues(), + 'typeMap' => self::getInvalidArrayValues(), ]); } diff --git a/tests/Operation/DropCollectionTest.php b/tests/Operation/DropCollectionTest.php index eb3ddf76d..dda688686 100644 --- a/tests/Operation/DropCollectionTest.php +++ b/tests/Operation/DropCollectionTest.php @@ -14,12 +14,12 @@ public function testConstructorOptionTypeChecks(array $options): void new DropCollection($this->getDatabaseName(), $this->getCollectionName(), $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'session' => $this->getInvalidSessionValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'session' => self::getInvalidSessionValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } } diff --git a/tests/Operation/DropDatabaseTest.php b/tests/Operation/DropDatabaseTest.php index 04a5c763f..4bdc69192 100644 --- a/tests/Operation/DropDatabaseTest.php +++ b/tests/Operation/DropDatabaseTest.php @@ -14,12 +14,12 @@ public function testConstructorOptionTypeChecks(array $options): void new DropDatabase($this->getDatabaseName(), $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'session' => $this->getInvalidSessionValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'session' => self::getInvalidSessionValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } } diff --git a/tests/Operation/DropEncryptedCollectionTest.php b/tests/Operation/DropEncryptedCollectionTest.php index 4cb0a2401..669cc8590 100644 --- a/tests/Operation/DropEncryptedCollectionTest.php +++ b/tests/Operation/DropEncryptedCollectionTest.php @@ -15,14 +15,14 @@ public function testConstructorOptionTypeChecks(array $options): void new DropEncryptedCollection($this->getDatabaseName(), $this->getCollectionName(), $options); } - public function provideInvalidConstructorOptions(): Generator + public static function provideInvalidConstructorOptions(): Generator { yield 'encryptedFields is required' => [ [], ]; - yield from $this->createOptionDataProvider([ - 'encryptedFields' => $this->getInvalidDocumentValues(), + yield from self::createOptionDataProvider([ + 'encryptedFields' => self::getInvalidDocumentValues(), ]); } } diff --git a/tests/Operation/DropIndexesTest.php b/tests/Operation/DropIndexesTest.php index 5fc021071..c90ca1376 100644 --- a/tests/Operation/DropIndexesTest.php +++ b/tests/Operation/DropIndexesTest.php @@ -20,13 +20,13 @@ public function testConstructorOptionTypeChecks(array $options): void new DropIndexes($this->getDatabaseName(), $this->getCollectionName(), '*', $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'session' => $this->getInvalidSessionValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'session' => self::getInvalidSessionValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } } diff --git a/tests/Operation/EstimatedDocumentCountTest.php b/tests/Operation/EstimatedDocumentCountTest.php index bf090c50d..f1b4c9211 100644 --- a/tests/Operation/EstimatedDocumentCountTest.php +++ b/tests/Operation/EstimatedDocumentCountTest.php @@ -14,13 +14,13 @@ public function testConstructorOptionTypeChecks(array $options): void new EstimatedDocumentCount($this->getDatabaseName(), $this->getCollectionName(), $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'session' => $this->getInvalidSessionValues(), + return self::createOptionDataProvider([ + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'session' => self::getInvalidSessionValues(), ]); } } diff --git a/tests/Operation/ExplainFunctionalTest.php b/tests/Operation/ExplainFunctionalTest.php index 714a3f868..486ee4095 100644 --- a/tests/Operation/ExplainFunctionalTest.php +++ b/tests/Operation/ExplainFunctionalTest.php @@ -360,7 +360,7 @@ public function testAggregateOptimizedToQuery($verbosity, $executionStatsExpecte $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - public function provideVerbosityInformation() + public static function provideVerbosityInformation() { return [ [Explain::VERBOSITY_ALL_PLANS, true, true], diff --git a/tests/Operation/ExplainTest.php b/tests/Operation/ExplainTest.php index 3b202de03..98937a1ca 100644 --- a/tests/Operation/ExplainTest.php +++ b/tests/Operation/ExplainTest.php @@ -16,13 +16,13 @@ public function testConstructorOptionTypeChecks(array $options): void new Explain($this->getDatabaseName(), $explainable, $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'session' => $this->getInvalidSessionValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'verbosity' => $this->getInvalidStringValues(), + return self::createOptionDataProvider([ + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'session' => self::getInvalidSessionValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'verbosity' => self::getInvalidStringValues(), ]); } } diff --git a/tests/Operation/FindAndModifyFunctionalTest.php b/tests/Operation/FindAndModifyFunctionalTest.php index 28c636d7c..c16413218 100644 --- a/tests/Operation/FindAndModifyFunctionalTest.php +++ b/tests/Operation/FindAndModifyFunctionalTest.php @@ -35,7 +35,7 @@ function (array $event) use ($expectedQuery): void { ); } - public function provideQueryDocuments(): array + public static function provideQueryDocuments(): array { $expected = (object) ['x' => 1]; @@ -74,7 +74,7 @@ function (array $event) use ($expectedUpdate): void { ); } - public function provideReplacementDocumentLikePipeline(): array + public static function provideReplacementDocumentLikePipeline(): array { /* Note: this expected value differs from UpdateFunctionalTest because * FindAndModify is not affected by libmongoc's pipeline detection for @@ -251,7 +251,7 @@ public function testTypeMapOption(?array $typeMap, $expectedDocument): void $this->assertEquals($expectedDocument, $document); } - public function provideTypeMapOptionsAndExpectedDocument() + public static function provideTypeMapOptionsAndExpectedDocument() { return [ [ diff --git a/tests/Operation/FindAndModifyTest.php b/tests/Operation/FindAndModifyTest.php index 65676d289..a6049ed78 100644 --- a/tests/Operation/FindAndModifyTest.php +++ b/tests/Operation/FindAndModifyTest.php @@ -15,24 +15,24 @@ public function testConstructorOptionTypeChecks(array $options): void new FindAndModify($this->getDatabaseName(), $this->getCollectionName(), $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'arrayFilters' => $this->getInvalidArrayValues(), - 'bypassDocumentValidation' => $this->getInvalidBooleanValues(), - 'codec' => $this->getInvalidDocumentCodecValues(), - 'collation' => $this->getInvalidDocumentValues(), - 'fields' => $this->getInvalidDocumentValues(), - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'new' => $this->getInvalidBooleanValues(), - 'query' => $this->getInvalidDocumentValues(), - 'remove' => $this->getInvalidBooleanValues(), - 'session' => $this->getInvalidSessionValues(), - 'sort' => $this->getInvalidDocumentValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'update' => $this->getInvalidUpdateValues(), - 'upsert' => $this->getInvalidBooleanValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'arrayFilters' => self::getInvalidArrayValues(), + 'bypassDocumentValidation' => self::getInvalidBooleanValues(), + 'codec' => self::getInvalidDocumentCodecValues(), + 'collation' => self::getInvalidDocumentValues(), + 'fields' => self::getInvalidDocumentValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'new' => self::getInvalidBooleanValues(), + 'query' => self::getInvalidDocumentValues(), + 'remove' => self::getInvalidBooleanValues(), + 'session' => self::getInvalidSessionValues(), + 'sort' => self::getInvalidDocumentValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'update' => self::getInvalidUpdateValues(), + 'upsert' => self::getInvalidBooleanValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/FindFunctionalTest.php b/tests/Operation/FindFunctionalTest.php index 1238cec6e..3b6a90218 100644 --- a/tests/Operation/FindFunctionalTest.php +++ b/tests/Operation/FindFunctionalTest.php @@ -56,7 +56,7 @@ function (array $event) use ($expectedSort): void { ); } - public function provideModifierDocuments(): array + public static function provideModifierDocuments(): array { $expectedSort = (object) ['x' => 1]; @@ -168,7 +168,7 @@ public function testTypeMapOption(array $typeMap, array $expectedDocuments): voi $this->assertEquals($expectedDocuments, $cursor->toArray()); } - public function provideTypeMapOptionsAndExpectedDocuments() + public static function provideTypeMapOptionsAndExpectedDocuments() { return [ [ diff --git a/tests/Operation/FindOneAndDeleteTest.php b/tests/Operation/FindOneAndDeleteTest.php index c4cbda6a8..76f18bda4 100644 --- a/tests/Operation/FindOneAndDeleteTest.php +++ b/tests/Operation/FindOneAndDeleteTest.php @@ -24,10 +24,10 @@ public function testConstructorOptionTypeChecks(array $options): void new FindOneAndDelete($this->getDatabaseName(), $this->getCollectionName(), [], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'projection' => $this->getInvalidDocumentValues(), + return self::createOptionDataProvider([ + 'projection' => self::getInvalidDocumentValues(), ]); } diff --git a/tests/Operation/FindOneAndReplaceTest.php b/tests/Operation/FindOneAndReplaceTest.php index 33681f7d4..c71946ec7 100644 --- a/tests/Operation/FindOneAndReplaceTest.php +++ b/tests/Operation/FindOneAndReplaceTest.php @@ -59,12 +59,12 @@ public function testConstructorOptionTypeChecks(array $options): void new FindOneAndReplace($this->getDatabaseName(), $this->getCollectionName(), [], [], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'codec' => $this->getInvalidDocumentCodecValues(), - 'projection' => $this->getInvalidDocumentValues(), - 'returnDocument' => $this->getInvalidIntegerValues(true), + return self::createOptionDataProvider([ + 'codec' => self::getInvalidDocumentCodecValues(), + 'projection' => self::getInvalidDocumentValues(), + 'returnDocument' => self::getInvalidIntegerValues(true), ]); } @@ -75,9 +75,9 @@ public function testConstructorReturnDocumentOption($returnDocument): void new FindOneAndReplace($this->getDatabaseName(), $this->getCollectionName(), [], [], ['returnDocument' => $returnDocument]); } - public function provideInvalidConstructorReturnDocumentOptions() + public static function provideInvalidConstructorReturnDocumentOptions() { - return $this->wrapValuesForDataProvider([-1, 0, 3]); + return self::wrapValuesForDataProvider([-1, 0, 3]); } public function testExplainableCommandDocument(): void diff --git a/tests/Operation/FindOneAndUpdateTest.php b/tests/Operation/FindOneAndUpdateTest.php index f0917a856..54defcad6 100644 --- a/tests/Operation/FindOneAndUpdateTest.php +++ b/tests/Operation/FindOneAndUpdateTest.php @@ -42,11 +42,11 @@ public function testConstructorOptionTypeChecks(array $options): void new FindOneAndUpdate($this->getDatabaseName(), $this->getCollectionName(), [], ['$set' => ['x' => 1]], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'projection' => $this->getInvalidDocumentValues(), - 'returnDocument' => $this->getInvalidIntegerValues(), + return self::createOptionDataProvider([ + 'projection' => self::getInvalidDocumentValues(), + 'returnDocument' => self::getInvalidIntegerValues(), ]); } @@ -57,9 +57,9 @@ public function testConstructorReturnDocumentOption($returnDocument): void new FindOneAndUpdate($this->getDatabaseName(), $this->getCollectionName(), [], [], ['returnDocument' => $returnDocument]); } - public function provideInvalidConstructorReturnDocumentOptions() + public static function provideInvalidConstructorReturnDocumentOptions() { - return $this->wrapValuesForDataProvider([-1, 0, 3]); + return self::wrapValuesForDataProvider([-1, 0, 3]); } public function testExplainableCommandDocument(): void diff --git a/tests/Operation/FindOneFunctionalTest.php b/tests/Operation/FindOneFunctionalTest.php index 57e01f442..6ae0a28da 100644 --- a/tests/Operation/FindOneFunctionalTest.php +++ b/tests/Operation/FindOneFunctionalTest.php @@ -20,7 +20,7 @@ public function testTypeMapOption(array $typeMap, $expectedDocument): void $this->assertEquals($expectedDocument, $document); } - public function provideTypeMapOptionsAndExpectedDocument() + public static function provideTypeMapOptionsAndExpectedDocument() { return [ [ diff --git a/tests/Operation/FindTest.php b/tests/Operation/FindTest.php index 806b883ce..9e398650d 100644 --- a/tests/Operation/FindTest.php +++ b/tests/Operation/FindTest.php @@ -25,33 +25,33 @@ public function testConstructorOptionTypeChecks(array $options): void new Find($this->getDatabaseName(), $this->getCollectionName(), [], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'allowPartialResults' => $this->getInvalidBooleanValues(), - 'batchSize' => $this->getInvalidIntegerValues(), - 'codec' => $this->getInvalidDocumentCodecValues(), - 'collation' => $this->getInvalidDocumentValues(), - 'cursorType' => $this->getInvalidIntegerValues(), - 'hint' => $this->getInvalidHintValues(), - 'limit' => $this->getInvalidIntegerValues(), - 'max' => $this->getInvalidDocumentValues(), - 'maxAwaitTimeMS' => $this->getInvalidIntegerValues(), - 'maxScan' => $this->getInvalidIntegerValues(), - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'min' => $this->getInvalidDocumentValues(), - 'modifiers' => $this->getInvalidDocumentValues(), - 'oplogReplay' => $this->getInvalidBooleanValues(), - 'projection' => $this->getInvalidDocumentValues(), - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'returnKey' => $this->getInvalidBooleanValues(), - 'session' => $this->getInvalidSessionValues(), - 'showRecordId' => $this->getInvalidBooleanValues(), - 'skip' => $this->getInvalidIntegerValues(), - 'snapshot' => $this->getInvalidBooleanValues(), - 'sort' => $this->getInvalidDocumentValues(), - 'typeMap' => $this->getInvalidArrayValues(), + return self::createOptionDataProvider([ + 'allowPartialResults' => self::getInvalidBooleanValues(), + 'batchSize' => self::getInvalidIntegerValues(), + 'codec' => self::getInvalidDocumentCodecValues(), + 'collation' => self::getInvalidDocumentValues(), + 'cursorType' => self::getInvalidIntegerValues(), + 'hint' => self::getInvalidHintValues(), + 'limit' => self::getInvalidIntegerValues(), + 'max' => self::getInvalidDocumentValues(), + 'maxAwaitTimeMS' => self::getInvalidIntegerValues(), + 'maxScan' => self::getInvalidIntegerValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'min' => self::getInvalidDocumentValues(), + 'modifiers' => self::getInvalidDocumentValues(), + 'oplogReplay' => self::getInvalidBooleanValues(), + 'projection' => self::getInvalidDocumentValues(), + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'returnKey' => self::getInvalidBooleanValues(), + 'session' => self::getInvalidSessionValues(), + 'showRecordId' => self::getInvalidBooleanValues(), + 'skip' => self::getInvalidIntegerValues(), + 'snapshot' => self::getInvalidBooleanValues(), + 'sort' => self::getInvalidDocumentValues(), + 'typeMap' => self::getInvalidArrayValues(), ]); } @@ -80,9 +80,9 @@ public function testConstructorCursorTypeOption($cursorType): void new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['cursorType' => $cursorType]); } - public function provideInvalidConstructorCursorTypeOptions() + public static function provideInvalidConstructorCursorTypeOptions() { - return $this->wrapValuesForDataProvider([-1, 0, 4]); + return self::wrapValuesForDataProvider([-1, 0, 4]); } public function testExplainableCommandDocument(): void diff --git a/tests/Operation/FunctionalTestCase.php b/tests/Operation/FunctionalTestCase.php index c07a35712..98e05c3cb 100644 --- a/tests/Operation/FunctionalTestCase.php +++ b/tests/Operation/FunctionalTestCase.php @@ -22,7 +22,7 @@ public function setUp(): void $this->dropCollection($this->getDatabaseName(), $this->getCollectionName()); } - public function provideFilterDocuments(): array + public static function provideFilterDocuments(): array { $expected = (object) ['x' => 1]; @@ -34,7 +34,7 @@ public function provideFilterDocuments(): array ]; } - public function provideReplacementDocuments(): array + public static function provideReplacementDocuments(): array { $expected = (object) ['x' => 1]; $expectedEmpty = (object) []; @@ -53,7 +53,7 @@ public function provideReplacementDocuments(): array ]; } - public function provideUpdateDocuments(): array + public static function provideUpdateDocuments(): array { $expected = (object) ['$set' => (object) ['x' => 1]]; @@ -65,7 +65,7 @@ public function provideUpdateDocuments(): array ]; } - public function provideUpdatePipelines(): array + public static function provideUpdatePipelines(): array { $expected = [(object) ['$set' => (object) ['x' => 1]]]; diff --git a/tests/Operation/InsertManyTest.php b/tests/Operation/InsertManyTest.php index ece556cae..52f32d2cf 100644 --- a/tests/Operation/InsertManyTest.php +++ b/tests/Operation/InsertManyTest.php @@ -38,14 +38,14 @@ public function testConstructorOptionTypeChecks(array $options): void new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [['x' => 1]], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'bypassDocumentValidation' => $this->getInvalidBooleanValues(), - 'codec' => $this->getInvalidDocumentCodecValues(), - 'ordered' => $this->getInvalidBooleanValues(true), - 'session' => $this->getInvalidSessionValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'bypassDocumentValidation' => self::getInvalidBooleanValues(), + 'codec' => self::getInvalidDocumentCodecValues(), + 'ordered' => self::getInvalidBooleanValues(true), + 'session' => self::getInvalidSessionValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/InsertOneFunctionalTest.php b/tests/Operation/InsertOneFunctionalTest.php index d34a89df1..218a8853c 100644 --- a/tests/Operation/InsertOneFunctionalTest.php +++ b/tests/Operation/InsertOneFunctionalTest.php @@ -53,7 +53,7 @@ function (array $event) use ($expectedDocument): void { ); } - public function provideDocumentsWithIds(): array + public static function provideDocumentsWithIds(): array { $expectedDocument = (object) ['_id' => 1]; @@ -65,7 +65,7 @@ public function provideDocumentsWithIds(): array ]; } - public function provideDocumentsWithoutIds(): array + public static function provideDocumentsWithoutIds(): array { /* Note: _id placeholders must be replaced with generated ObjectIds. We * also clone the value for each data set since tests may need to modify diff --git a/tests/Operation/InsertOneTest.php b/tests/Operation/InsertOneTest.php index ad325cd0b..6f493d9d1 100644 --- a/tests/Operation/InsertOneTest.php +++ b/tests/Operation/InsertOneTest.php @@ -25,13 +25,13 @@ public function testConstructorOptionTypeChecks(array $options): void new InsertOne($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'bypassDocumentValidation' => $this->getInvalidBooleanValues(), - 'codec' => $this->getInvalidDocumentCodecValues(), - 'session' => $this->getInvalidSessionValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'bypassDocumentValidation' => self::getInvalidBooleanValues(), + 'codec' => self::getInvalidDocumentCodecValues(), + 'session' => self::getInvalidSessionValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/ListIndexesTest.php b/tests/Operation/ListIndexesTest.php index 7edda8ef3..690eb4bca 100644 --- a/tests/Operation/ListIndexesTest.php +++ b/tests/Operation/ListIndexesTest.php @@ -14,11 +14,11 @@ public function testConstructorOptionTypeChecks(array $options): void new ListIndexes($this->getDatabaseName(), $this->getCollectionName(), $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'session' => $this->getInvalidSessionValues(), + return self::createOptionDataProvider([ + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'session' => self::getInvalidSessionValues(), ]); } } diff --git a/tests/Operation/ListSearchIndexesTest.php b/tests/Operation/ListSearchIndexesTest.php index 23bc770ef..b07909351 100644 --- a/tests/Operation/ListSearchIndexesTest.php +++ b/tests/Operation/ListSearchIndexesTest.php @@ -20,11 +20,11 @@ public function testConstructorOptionTypeChecks(array $options): void new ListSearchIndexes($this->getDatabaseName(), $this->getCollectionName(), $options); } - public function provideInvalidConstructorOptions(): array + public static function provideInvalidConstructorOptions(): array { $options = []; - foreach ($this->getInvalidIntegerValues() as $value) { + foreach (self::getInvalidIntegerValues() as $value) { $options[][] = ['batchSize' => $value]; } diff --git a/tests/Operation/MapReduceFunctionalTest.php b/tests/Operation/MapReduceFunctionalTest.php index ea1b1e70b..69a39f297 100644 --- a/tests/Operation/MapReduceFunctionalTest.php +++ b/tests/Operation/MapReduceFunctionalTest.php @@ -228,7 +228,7 @@ public function testTypeMapOptionWithInlineResults(?array $typeMap, array $expec $this->assertEquals($this->sortResults($expectedDocuments), $this->sortResults($results)); } - public function provideTypeMapOptionsAndExpectedDocuments() + public static function provideTypeMapOptionsAndExpectedDocuments() { return [ [ diff --git a/tests/Operation/MapReduceTest.php b/tests/Operation/MapReduceTest.php index 92e6881c1..6db1a9b99 100644 --- a/tests/Operation/MapReduceTest.php +++ b/tests/Operation/MapReduceTest.php @@ -25,9 +25,9 @@ public function testConstructorOutArgumentTypeCheck($out): void new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out); } - public function provideInvalidOutValues() + public static function provideInvalidOutValues() { - return $this->wrapValuesForDataProvider([123, 3.14, true]); + return self::wrapValuesForDataProvider([123, 3.14, true]); } /** @dataProvider provideDeprecatedOutValues */ @@ -41,7 +41,7 @@ public function testConstructorOutArgumentDeprecations($out): void }); } - public function provideDeprecatedOutValues(): array + public static function provideDeprecatedOutValues(): array { return [ 'nonAtomic:array' => [['nonAtomic' => false]], @@ -66,28 +66,28 @@ public function testConstructorOptionTypeChecks(array $options): void new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out, $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'bypassDocumentValidation' => $this->getInvalidBooleanValues(), - 'collation' => $this->getInvalidDocumentValues(), - 'finalize' => $this->getInvalidJavascriptValues(), - 'jsMode' => $this->getInvalidBooleanValues(), - 'limit' => $this->getInvalidIntegerValues(), - 'maxTimeMS' => $this->getInvalidIntegerValues(), - 'query' => $this->getInvalidDocumentValues(), - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(), - 'scope' => $this->getInvalidDocumentValues(), - 'session' => $this->getInvalidSessionValues(), - 'sort' => $this->getInvalidDocumentValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'verbose' => $this->getInvalidBooleanValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'bypassDocumentValidation' => self::getInvalidBooleanValues(), + 'collation' => self::getInvalidDocumentValues(), + 'finalize' => self::getInvalidJavascriptValues(), + 'jsMode' => self::getInvalidBooleanValues(), + 'limit' => self::getInvalidIntegerValues(), + 'maxTimeMS' => self::getInvalidIntegerValues(), + 'query' => self::getInvalidDocumentValues(), + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(), + 'scope' => self::getInvalidDocumentValues(), + 'session' => self::getInvalidSessionValues(), + 'sort' => self::getInvalidDocumentValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'verbose' => self::getInvalidBooleanValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } - private function getInvalidJavascriptValues() + private static function getInvalidJavascriptValues() { return [123, 3.14, 'foo', true, [], new stdClass(), new ObjectId()]; } diff --git a/tests/Operation/ModifyCollectionTest.php b/tests/Operation/ModifyCollectionTest.php index dfa8d2584..8369d341e 100644 --- a/tests/Operation/ModifyCollectionTest.php +++ b/tests/Operation/ModifyCollectionTest.php @@ -21,12 +21,12 @@ public function testConstructorOptionTypeChecks(array $options): void new ModifyCollection($this->getDatabaseName(), $this->getCollectionName(), [], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'session' => $this->getInvalidSessionValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'session' => self::getInvalidSessionValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } } diff --git a/tests/Operation/RenameCollectionTest.php b/tests/Operation/RenameCollectionTest.php index f1d0e15d5..a4e190652 100644 --- a/tests/Operation/RenameCollectionTest.php +++ b/tests/Operation/RenameCollectionTest.php @@ -20,13 +20,13 @@ public function testConstructorOptionTypeChecks(array $options): void ); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'dropTarget' => $this->getInvalidBooleanValues(), - 'session' => $this->getInvalidSessionValues(), - 'typeMap' => $this->getInvalidArrayValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'dropTarget' => self::getInvalidBooleanValues(), + 'session' => self::getInvalidSessionValues(), + 'typeMap' => self::getInvalidArrayValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } } diff --git a/tests/Operation/ReplaceOneTest.php b/tests/Operation/ReplaceOneTest.php index 73f659b85..dc6ef2c47 100644 --- a/tests/Operation/ReplaceOneTest.php +++ b/tests/Operation/ReplaceOneTest.php @@ -60,10 +60,10 @@ public function testConstructorOptionsTypeCheck($options): void new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], ['y' => 1], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'codec' => $this->getInvalidDocumentCodecValues(), + return self::createOptionDataProvider([ + 'codec' => self::getInvalidDocumentCodecValues(), ]); } diff --git a/tests/Operation/TestCase.php b/tests/Operation/TestCase.php index 386468ce2..4bb5c5cec 100644 --- a/tests/Operation/TestCase.php +++ b/tests/Operation/TestCase.php @@ -13,7 +13,7 @@ */ abstract class TestCase extends BaseTestCase { - public function provideReplacementDocuments(): array + public static function provideReplacementDocuments(): array { return [ 'replacement:array' => [['x' => 1]], @@ -29,7 +29,7 @@ public function provideReplacementDocuments(): array ]; } - public function provideUpdateDocuments(): array + public static function provideUpdateDocuments(): array { return [ 'update:array' => [['$set' => ['x' => 1]]], @@ -39,7 +39,7 @@ public function provideUpdateDocuments(): array ]; } - public function provideUpdatePipelines(): array + public static function provideUpdatePipelines(): array { return [ 'pipeline:array' => [[['$set' => ['x' => 1]]]], @@ -48,7 +48,7 @@ public function provideUpdatePipelines(): array ]; } - public function provideEmptyUpdatePipelines(): array + public static function provideEmptyUpdatePipelines(): array { /* Empty update pipelines are accepted by the update and findAndModify * commands (as NOPs); however, they are not supported for updates in @@ -65,7 +65,7 @@ public function provideEmptyUpdatePipelines(): array ]; } - public function provideEmptyUpdatePipelinesExcludingArray(): array + public static function provideEmptyUpdatePipelinesExcludingArray(): array { /* This data provider is used for replace operations, which accept empty * arrays as replacement documents for BC. */ @@ -75,12 +75,12 @@ public function provideEmptyUpdatePipelinesExcludingArray(): array ]; } - public function provideInvalidUpdateValues(): array + public static function provideInvalidUpdateValues(): array { - return $this->wrapValuesForDataProvider($this->getInvalidUpdateValues()); + return self::wrapValuesForDataProvider(self::getInvalidUpdateValues()); } - protected function getInvalidUpdateValues(): array + protected static function getInvalidUpdateValues(): array { return [123, 3.14, 'foo', true]; } diff --git a/tests/Operation/UpdateFunctionalTest.php b/tests/Operation/UpdateFunctionalTest.php index d475866ce..2a18f13aa 100644 --- a/tests/Operation/UpdateFunctionalTest.php +++ b/tests/Operation/UpdateFunctionalTest.php @@ -75,7 +75,7 @@ function (array $event) use ($expectedUpdate): void { ); } - public function provideReplacementDocumentLikePipeline(): array + public static function provideReplacementDocumentLikePipeline(): array { /* Note: libmongoc encodes this replacement document as a BSON array * because it resembles an update pipeline (see: CDRIVER-4658). */ diff --git a/tests/Operation/UpdateTest.php b/tests/Operation/UpdateTest.php index bf8d993ca..468bf00bf 100644 --- a/tests/Operation/UpdateTest.php +++ b/tests/Operation/UpdateTest.php @@ -31,17 +31,17 @@ public function testConstructorOptionTypeChecks(array $options): void new Update($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], ['y' => 1], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'arrayFilters' => $this->getInvalidArrayValues(), - 'bypassDocumentValidation' => $this->getInvalidBooleanValues(), - 'collation' => $this->getInvalidDocumentValues(), - 'hint' => $this->getInvalidHintValues(), - 'multi' => $this->getInvalidBooleanValues(), - 'session' => $this->getInvalidSessionValues(), - 'upsert' => $this->getInvalidBooleanValues(), - 'writeConcern' => $this->getInvalidWriteConcernValues(), + return self::createOptionDataProvider([ + 'arrayFilters' => self::getInvalidArrayValues(), + 'bypassDocumentValidation' => self::getInvalidBooleanValues(), + 'collation' => self::getInvalidDocumentValues(), + 'hint' => self::getInvalidHintValues(), + 'multi' => self::getInvalidBooleanValues(), + 'session' => self::getInvalidSessionValues(), + 'upsert' => self::getInvalidBooleanValues(), + 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/WatchFunctionalTest.php b/tests/Operation/WatchFunctionalTest.php index c71818cf2..8715bb6b1 100644 --- a/tests/Operation/WatchFunctionalTest.php +++ b/tests/Operation/WatchFunctionalTest.php @@ -1064,7 +1064,7 @@ public function testTypeMapOption(array $typeMap, $expectedChangeDocument): void $this->assertMatchesDocument($expectedChangeDocument, $changeStream->current()); } - public function provideTypeMapOptionsAndExpectedChangeDocument() + public static function provideTypeMapOptionsAndExpectedChangeDocument() { /* Note: the "_id" and "ns" fields are purposefully omitted because the * resume token's value cannot be anticipated and the collection name, diff --git a/tests/Operation/WatchTest.php b/tests/Operation/WatchTest.php index fd97a1c5e..8a3f4945d 100644 --- a/tests/Operation/WatchTest.php +++ b/tests/Operation/WatchTest.php @@ -39,22 +39,22 @@ public function testConstructorOptionTypeChecks(array $options): void new Watch($this->manager, $this->getDatabaseName(), $this->getCollectionName(), [], $options); } - public function provideInvalidConstructorOptions() + public static function provideInvalidConstructorOptions() { - return $this->createOptionDataProvider([ - 'batchSize' => $this->getInvalidIntegerValues(), - 'codec' => $this->getInvalidDocumentCodecValues(), - 'collation' => $this->getInvalidDocumentValues(), - 'fullDocument' => $this->getInvalidStringValues(true), - 'fullDocumentBeforeChange' => $this->getInvalidStringValues(), - 'maxAwaitTimeMS' => $this->getInvalidIntegerValues(), - 'readConcern' => $this->getInvalidReadConcernValues(), - 'readPreference' => $this->getInvalidReadPreferenceValues(true), - 'resumeAfter' => $this->getInvalidDocumentValues(), - 'session' => $this->getInvalidSessionValues(), - 'startAfter' => $this->getInvalidDocumentValues(), - 'startAtOperationTime' => $this->getInvalidTimestampValues(), - 'typeMap' => $this->getInvalidArrayValues(), + return self::createOptionDataProvider([ + 'batchSize' => self::getInvalidIntegerValues(), + 'codec' => self::getInvalidDocumentCodecValues(), + 'collation' => self::getInvalidDocumentValues(), + 'fullDocument' => self::getInvalidStringValues(true), + 'fullDocumentBeforeChange' => self::getInvalidStringValues(), + 'maxAwaitTimeMS' => self::getInvalidIntegerValues(), + 'readConcern' => self::getInvalidReadConcernValues(), + 'readPreference' => self::getInvalidReadPreferenceValues(true), + 'resumeAfter' => self::getInvalidDocumentValues(), + 'session' => self::getInvalidSessionValues(), + 'startAfter' => self::getInvalidDocumentValues(), + 'startAtOperationTime' => self::getInvalidTimestampValues(), + 'typeMap' => self::getInvalidArrayValues(), ]); } @@ -66,7 +66,7 @@ public function testConstructorRejectsCodecAndTypemap(): void new Watch($this->manager, $this->getDatabaseName(), $this->getCollectionName(), [], $options); } - private function getInvalidTimestampValues() + private static function getInvalidTimestampValues() { return [123, 3.14, 'foo', true, [], new stdClass()]; } diff --git a/tests/PedantryTest.php b/tests/PedantryTest.php index 0faf67c54..5c6b07eed 100644 --- a/tests/PedantryTest.php +++ b/tests/PedantryTest.php @@ -63,7 +63,7 @@ public function testMethodsAreOrderedAlphabeticallyByVisibility($className): voi $this->assertEquals($sortedMethods, $methods); } - public function provideProjectClassNames() + public static function provideProjectClassNames() { $classNames = []; $srcDir = realpath(__DIR__ . '/../src/'); diff --git a/tests/SpecTests/ClientSideEncryptionSpecTest.php b/tests/SpecTests/ClientSideEncryptionSpecTest.php index 1c716edb0..263fc61c2 100644 --- a/tests/SpecTests/ClientSideEncryptionSpecTest.php +++ b/tests/SpecTests/ClientSideEncryptionSpecTest.php @@ -215,7 +215,7 @@ public function testClientSideEncryption(stdClass $test, ?array $runOn, array $d } } - public function provideTests() + public static function provideTests() { $testArgs = []; diff --git a/tests/SpecTests/DocumentsMatchConstraintTest.php b/tests/SpecTests/DocumentsMatchConstraintTest.php index f4959882d..283d05fe8 100644 --- a/tests/SpecTests/DocumentsMatchConstraintTest.php +++ b/tests/SpecTests/DocumentsMatchConstraintTest.php @@ -78,7 +78,7 @@ public function testBSONTypeAssertions($type, $value): void $this->assertResult(true, $constraint, ['x' => $value], 'Type matches'); } - public function provideBSONTypes() + public static function provideBSONTypes() { $undefined = Document::fromJSON('{ "x": {"$undefined": true} }')->toPHP()->x; $symbol = Document::fromJSON('{ "x": {"$symbol": "test"} }')->toPHP()->x; @@ -144,7 +144,7 @@ public function testErrorMessages($expectedMessagePart, DocumentsMatchConstraint } } - public function errorMessageProvider() + public static function errorMessageProvider() { return [ 'Root type mismatch' => [ diff --git a/tests/TestCase.php b/tests/TestCase.php index 53e46fb82..1d965606d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,6 +6,8 @@ use MongoDB\BSON\Document; use MongoDB\BSON\PackedArray; use MongoDB\Codec\Codec; +use MongoDB\Codec\DecodeIfSupported; +use MongoDB\Codec\EncodeIfSupported; use MongoDB\Driver\ReadConcern; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\WriteConcern; @@ -138,24 +140,24 @@ public function dataDescription(): string return is_string($dataName) ? $dataName : ''; } - public function provideInvalidArrayValues(): array + final public static function provideInvalidArrayValues(): array { - return $this->wrapValuesForDataProvider($this->getInvalidArrayValues()); + return self::wrapValuesForDataProvider(self::getInvalidArrayValues()); } - public function provideInvalidDocumentValues(): array + final public static function provideInvalidDocumentValues(): array { - return $this->wrapValuesForDataProvider($this->getInvalidDocumentValues()); + return self::wrapValuesForDataProvider(self::getInvalidDocumentValues()); } - public function provideInvalidIntegerValues(): array + final public static function provideInvalidIntegerValues(): array { - return $this->wrapValuesForDataProvider($this->getInvalidIntegerValues()); + return self::wrapValuesForDataProvider(self::getInvalidIntegerValues()); } - public function provideInvalidStringValues(): array + final public static function provideInvalidStringValues(): array { - return $this->wrapValuesForDataProvider($this->getInvalidStringValues()); + return self::wrapValuesForDataProvider(self::getInvalidStringValues()); } protected function assertDeprecated(callable $execution): void @@ -175,7 +177,7 @@ protected function assertDeprecated(callable $execution): void $this->assertCount(1, $errors); } - protected function createOptionDataProvider(array $options): array + protected static function createOptionDataProvider(array $options): array { $data = []; @@ -206,7 +208,7 @@ protected function getCollectionName(): string /** * Return a list of invalid array values. */ - protected function getInvalidArrayValues(bool $includeNull = false): array + protected static function getInvalidArrayValues(bool $includeNull = false): array { return [123, 3.14, 'foo', true, new stdClass(), ...($includeNull ? [null] : [])]; } @@ -214,7 +216,7 @@ protected function getInvalidArrayValues(bool $includeNull = false): array /** * Return a list of invalid boolean values. */ - protected function getInvalidBooleanValues(bool $includeNull = false): array + protected static function getInvalidBooleanValues(bool $includeNull = false): array { return [123, 3.14, 'foo', [], new stdClass(), ...($includeNull ? [null] : [])]; } @@ -222,25 +224,52 @@ protected function getInvalidBooleanValues(bool $includeNull = false): array /** * Return a list of invalid document values. */ - protected function getInvalidDocumentValues(bool $includeNull = false): array + protected static function getInvalidDocumentValues(bool $includeNull = false): array { return [123, 3.14, 'foo', true, PackedArray::fromPHP([]), ...($includeNull ? [null] : [])]; } - protected function getInvalidObjectValues(bool $includeNull = false): array + protected static function getInvalidObjectValues(bool $includeNull = false): array { return [123, 3.14, 'foo', true, [], new stdClass(), ...($includeNull ? [null] : [])]; } - protected function getInvalidDocumentCodecValues(): array + protected static function getInvalidDocumentCodecValues(): array { - return [123, 3.14, 'foo', true, [], new stdClass(), $this->createMock(Codec::class)]; + $codec = new class implements Codec { + use DecodeIfSupported; + use EncodeIfSupported; + + public function canDecode(mixed $value): bool + { + return true; + } + + public function decode(mixed $value): mixed + { + return $value; + } + + public function canEncode(mixed $value): bool + { + return true; + } + + public function encode(mixed $value): mixed + { + return $value; + } + }; + // @fixme: createStub can be called statically in PHPUnit 10 + // $codec = self::createStub(Codec::class); + + return [123, 3.14, 'foo', true, [], new stdClass(), $codec]; } /** * Return a list of invalid hint values. */ - protected function getInvalidHintValues() + protected static function getInvalidHintValues(): array { return [123, 3.14, true]; } @@ -248,7 +277,7 @@ protected function getInvalidHintValues() /** * Return a list of invalid integer values. */ - protected function getInvalidIntegerValues(bool $includeNull = false): array + protected static function getInvalidIntegerValues(bool $includeNull = false): array { return [3.14, 'foo', true, [], new stdClass(), ...($includeNull ? [null] : [])]; } @@ -256,7 +285,7 @@ protected function getInvalidIntegerValues(bool $includeNull = false): array /** * Return a list of invalid ReadPreference values. */ - protected function getInvalidReadConcernValues(bool $includeNull = false): array + protected static function getInvalidReadConcernValues(bool $includeNull = false): array { return [ 123, @@ -274,7 +303,7 @@ protected function getInvalidReadConcernValues(bool $includeNull = false): array /** * Return a list of invalid ReadPreference values. */ - protected function getInvalidReadPreferenceValues(bool $includeNull = false): array + protected static function getInvalidReadPreferenceValues(bool $includeNull = false): array { return [ 123, @@ -292,7 +321,7 @@ protected function getInvalidReadPreferenceValues(bool $includeNull = false): ar /** * Return a list of invalid Session values. */ - protected function getInvalidSessionValues(bool $includeNull = false): array + protected static function getInvalidSessionValues(bool $includeNull = false): array { return [ 123, @@ -311,7 +340,7 @@ protected function getInvalidSessionValues(bool $includeNull = false): array /** * Return a list of invalid string values. */ - protected function getInvalidStringValues(bool $includeNull = false): array + protected static function getInvalidStringValues(bool $includeNull = false): array { return [123, 3.14, true, [], new stdClass(), ...($includeNull ? [null] : [])]; } @@ -319,7 +348,7 @@ protected function getInvalidStringValues(bool $includeNull = false): array /** * Return a list of invalid WriteConcern values. */ - protected function getInvalidWriteConcernValues(bool $includeNull = false): array + protected static function getInvalidWriteConcernValues(bool $includeNull = false): array { return [ 123, @@ -347,7 +376,7 @@ protected function getNamespace(): string * * @param array $values List of values */ - protected function wrapValuesForDataProvider(array $values): array + final protected static function wrapValuesForDataProvider(array $values): array { return array_map(fn ($value) => [$value], $values); } diff --git a/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php b/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php index 7300ecd1a..248a989b3 100644 --- a/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php +++ b/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php @@ -33,7 +33,7 @@ public function testConstraint($type, $value): void $this->assertResult(true, new IsBsonType($type), $value, $this->dataName() . ' is ' . $type); } - public function provideTypes() + public static function provideTypes() { $undefined = Document::fromJSON('{ "x": {"$undefined": true} }')->toPHP()->x; $symbol = Document::fromJSON('{ "x": {"$symbol": "test"} }')->toPHP()->x; diff --git a/tests/UnifiedSpecTests/Constraint/MatchesTest.php b/tests/UnifiedSpecTests/Constraint/MatchesTest.php index 53a7643a4..472fdd1d1 100644 --- a/tests/UnifiedSpecTests/Constraint/MatchesTest.php +++ b/tests/UnifiedSpecTests/Constraint/MatchesTest.php @@ -182,7 +182,7 @@ public function testErrorMessages($expectedMessageRegex, Matches $constraint, $a } } - public function errorMessageProvider() + public static function errorMessageProvider() { return [ 'assertEquals: type check (root-level)' => [ @@ -262,7 +262,7 @@ public function testOperatorSyntaxValidation($expectedMessage, Matches $constrai $constraint->evaluate(['x' => 1], '', true); } - public function operatorErrorMessageProvider() + public static function operatorErrorMessageProvider() { return [ '$$exists type' => [ diff --git a/tests/UnifiedSpecTests/UnifiedSpecTest.php b/tests/UnifiedSpecTests/UnifiedSpecTest.php index e07e04f43..6b6031948 100644 --- a/tests/UnifiedSpecTests/UnifiedSpecTest.php +++ b/tests/UnifiedSpecTests/UnifiedSpecTest.php @@ -234,9 +234,9 @@ public function testAtlasDataLake(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideAtlasDataLakeTests() + public static function provideAtlasDataLakeTests(): Generator { - return $this->provideTests(__DIR__ . '/atlas-data-lake/*.json'); + return self::provideTests(__DIR__ . '/atlas-data-lake/*.json'); } /** @@ -248,9 +248,9 @@ public function testChangeStreams(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideChangeStreamsTests() + public static function provideChangeStreamsTests(): Generator { - return $this->provideTests(__DIR__ . '/change-streams/*.json'); + return self::provideTests(__DIR__ . '/change-streams/*.json'); } /** @@ -263,9 +263,9 @@ public function testClientSideEncryption(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideClientSideEncryptionTests() + public static function provideClientSideEncryptionTests(): Generator { - return $this->provideTests(__DIR__ . '/client-side-encryption/*.json'); + return self::provideTests(__DIR__ . '/client-side-encryption/*.json'); } /** @@ -277,9 +277,9 @@ public function testCollectionManagement(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideCollectionManagementTests() + public static function provideCollectionManagementTests(): Generator { - return $this->provideTests(__DIR__ . '/collection-management/*.json'); + return self::provideTests(__DIR__ . '/collection-management/*.json'); } /** @@ -291,9 +291,9 @@ public function testCommandMonitoring(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideCommandMonitoringTests() + public static function provideCommandMonitoringTests(): Generator { - return $this->provideTests(__DIR__ . '/command-monitoring/*.json'); + return self::provideTests(__DIR__ . '/command-monitoring/*.json'); } /** @@ -305,9 +305,9 @@ public function testCrud(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideCrudTests() + public static function provideCrudTests(): Generator { - return $this->provideTests(__DIR__ . '/crud/*.json'); + return self::provideTests(__DIR__ . '/crud/*.json'); } /** @@ -319,9 +319,9 @@ public function testGridFS(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideGridFSTests() + public static function provideGridFSTests(): Generator { - return $this->provideTests(__DIR__ . '/gridfs/*.json'); + return self::provideTests(__DIR__ . '/gridfs/*.json'); } /** @@ -333,9 +333,9 @@ public function testLoadBalancers(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideLoadBalancers() + public static function provideLoadBalancers(): Generator { - return $this->provideTests(__DIR__ . '/load-balancers/*.json'); + return self::provideTests(__DIR__ . '/load-balancers/*.json'); } /** @dataProvider provideReadWriteConcernTests */ @@ -344,9 +344,9 @@ public function testReadWriteConcern(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideReadWriteConcernTests() + public static function provideReadWriteConcernTests(): Generator { - return $this->provideTests(__DIR__ . '/read-write-concern/*.json'); + return self::provideTests(__DIR__ . '/read-write-concern/*.json'); } /** @@ -358,9 +358,9 @@ public function testRetryableReads(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideRetryableReadsTests() + public static function provideRetryableReadsTests(): Generator { - return $this->provideTests(__DIR__ . '/retryable-reads/*.json'); + return self::provideTests(__DIR__ . '/retryable-reads/*.json'); } /** @@ -372,9 +372,9 @@ public function testRetryableWrites(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideRetryableWritesTests() + public static function provideRetryableWritesTests(): Generator { - return $this->provideTests(__DIR__ . '/retryable-writes/*.json'); + return self::provideTests(__DIR__ . '/retryable-writes/*.json'); } /** @@ -386,9 +386,9 @@ public function testRunCommand(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideRunCommandTests() + public static function provideRunCommandTests(): Generator { - return $this->provideTests(__DIR__ . '/run-command/*.json'); + return self::provideTests(__DIR__ . '/run-command/*.json'); } /** @@ -400,9 +400,9 @@ public function testSessions(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideSessionsTests() + public static function provideSessionsTests(): Generator { - return $this->provideTests(__DIR__ . '/sessions/*.json'); + return self::provideTests(__DIR__ . '/sessions/*.json'); } /** @@ -414,9 +414,9 @@ public function testTransactions(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideTransactionsTests() + public static function provideTransactionsTests(): Generator { - return $this->provideTests(__DIR__ . '/transactions/*.json'); + return self::provideTests(__DIR__ . '/transactions/*.json'); } /** @dataProvider provideTransactionsConvenientApiTests */ @@ -425,9 +425,9 @@ public function testTransactionsConvenientApi(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideTransactionsConvenientApiTests() + public static function provideTransactionsConvenientApiTests(): Generator { - return $this->provideTests(__DIR__ . '/transactions-convenient-api/*.json'); + return self::provideTests(__DIR__ . '/transactions-convenient-api/*.json'); } /** @@ -440,9 +440,9 @@ public function testVersionedApi(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideVersionedApiTests() + public static function provideVersionedApiTests(): Generator { - return $this->provideTests(__DIR__ . '/versioned-api/*.json'); + return self::provideTests(__DIR__ . '/versioned-api/*.json'); } /** @dataProvider providePassingTests */ @@ -451,9 +451,9 @@ public function testPassingTests(UnifiedTestCase $test): void self::$runner->run($test); } - public function providePassingTests() + public static function providePassingTests(): Generator { - yield from $this->provideTests(__DIR__ . '/valid-pass/*.json'); + yield from self::provideTests(__DIR__ . '/valid-pass/*.json'); } /** @dataProvider provideFailingTests */ @@ -489,9 +489,9 @@ public function testFailingTests(UnifiedTestCase $test): void $this->assertTrue($failed, 'Expected test to throw an exception'); } - public function provideFailingTests() + public static function provideFailingTests(): Generator { - yield from $this->provideTests(__DIR__ . '/valid-fail/*.json'); + yield from self::provideTests(__DIR__ . '/valid-fail/*.json'); } /** @dataProvider provideIndexManagementTests */ @@ -508,12 +508,12 @@ public function testIndexManagement(UnifiedTestCase $test): void self::$runner->run($test); } - public function provideIndexManagementTests() + public static function provideIndexManagementTests(): Generator { - yield from $this->provideTests(__DIR__ . '/index-management/*.json'); + yield from self::provideTests(__DIR__ . '/index-management/*.json'); } - private function provideTests(string $pattern): Generator + private static function provideTests(string $pattern): Generator { foreach (glob($pattern) as $filename) { $group = basename(dirname($filename)); From 7093d9b1887e8f869f060762ec7c5ac219776d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 16 Sep 2024 09:20:30 +0200 Subject: [PATCH 84/95] Replace arrayHasKey with assertArrayHasKey in tests (#1403) --- tests/Operation/WatchFunctionalTest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/Operation/WatchFunctionalTest.php b/tests/Operation/WatchFunctionalTest.php index 8715bb6b1..562192f1b 100644 --- a/tests/Operation/WatchFunctionalTest.php +++ b/tests/Operation/WatchFunctionalTest.php @@ -307,15 +307,15 @@ function (array $event) use (&$events): void { $this->assertCount(3, $events); $this->assertSame('getMore', $events[0]['started']->getCommandName()); - $this->arrayHasKey('failed', $events[0]); + $this->assertArrayHasKey('failed', $events[0]); $this->assertSame('aggregate', $events[1]['started']->getCommandName()); $this->assertResumeAfter($postBatchResumeToken, $events[1]['started']->getCommand()); - $this->arrayHasKey('succeeded', $events[1]); + $this->assertArrayHasKey('succeeded', $events[1]); // Original cursor is freed immediately after the change stream resumes $this->assertSame('killCursors', $events[2]['started']->getCommandName()); - $this->arrayHasKey('succeeded', $events[2]); + $this->assertArrayHasKey('succeeded', $events[2]); $this->assertFalse($changeStream->valid()); } @@ -380,15 +380,15 @@ function (array $event) use (&$events): void { $this->assertCount(3, $events); $this->assertSame('getMore', $events[0]['started']->getCommandName()); - $this->arrayHasKey('failed', $events[0]); + $this->assertArrayHasKey('failed', $events[0]); $this->assertSame('aggregate', $events[1]['started']->getCommandName()); $this->assertStartAtOperationTime($operationTime, $events[1]['started']->getCommand()); - $this->arrayHasKey('succeeded', $events[1]); + $this->assertArrayHasKey('succeeded', $events[1]); // Original cursor is freed immediately after the change stream resumes $this->assertSame('killCursors', $events[2]['started']->getCommandName()); - $this->arrayHasKey('succeeded', $events[2]); + $this->assertArrayHasKey('succeeded', $events[2]); $this->assertFalse($changeStream->valid()); } From f2842bcd8be9cad85a2bdf9ab85d1312e8b7d1ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 16 Sep 2024 10:30:29 +0200 Subject: [PATCH 85/95] Fix types accepted by $round (#1401) --- generator/config/expression/round.yaml | 20 ++++++++++---- src/Builder/Expression/FactoryTrait.php | 6 ++--- src/Builder/Expression/RoundOperator.php | 10 +++---- tests/Builder/Expression/Pipelines.php | 26 +++++++++++++++++++ .../Builder/Expression/RoundOperatorTest.php | 18 +++++++++++++ 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/generator/config/expression/round.yaml b/generator/config/expression/round.yaml index 52e6004a0..9bc6961c7 100644 --- a/generator/config/expression/round.yaml +++ b/generator/config/expression/round.yaml @@ -8,15 +8,12 @@ type: - resolvesToLong encode: array description: | - Rounds a number to to a whole integer or to a specified decimal place. + Rounds a number to a whole integer or to a specified decimal place. arguments: - name: number type: - - resolvesToInt - - resolvesToDouble - - resolvesToDecimal - - resolvesToLong + - resolvesToNumber description: | Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. $round returns an error if the expression resolves to a non-numeric data type. @@ -38,3 +35,16 @@ tests: $round: - '$value' - 1 + - + name: 'Round Average Rating' + pipeline: + - + $project: + roundedAverageRating: + $avg: + - + $round: + - + $avg: + - '$averageRating' + - 2 diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 7828951ea..78f460fa0 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -1546,15 +1546,15 @@ public static function reverseArray(PackedArray|ResolvesToArray|BSONArray|array } /** - * Rounds a number to to a whole integer or to a specified decimal place. + * Rounds a number to a whole integer or to a specified decimal place. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/ - * @param Decimal128|Int64|ResolvesToDecimal|ResolvesToDouble|ResolvesToInt|ResolvesToLong|float|int $number Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. + * @param Decimal128|Int64|ResolvesToNumber|float|int $number Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. * $round returns an error if the expression resolves to a non-numeric data type. * @param Optional|ResolvesToInt|int $place Can be any valid expression that resolves to an integer between -20 and 100, exclusive. */ public static function round( - Decimal128|Int64|ResolvesToDecimal|ResolvesToDouble|ResolvesToInt|ResolvesToLong|float|int $number, + Decimal128|Int64|ResolvesToNumber|float|int $number, Optional|ResolvesToInt|int $place = Optional::Undefined, ): RoundOperator { return new RoundOperator($number, $place); diff --git a/src/Builder/Expression/RoundOperator.php b/src/Builder/Expression/RoundOperator.php index 7f3cad53e..323ae99eb 100644 --- a/src/Builder/Expression/RoundOperator.php +++ b/src/Builder/Expression/RoundOperator.php @@ -15,7 +15,7 @@ use MongoDB\Builder\Type\Optional; /** - * Rounds a number to to a whole integer or to a specified decimal place. + * Rounds a number to a whole integer or to a specified decimal place. * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/round/ */ @@ -24,21 +24,21 @@ class RoundOperator implements ResolvesToInt, ResolvesToDouble, ResolvesToDecima public const ENCODE = Encode::Array; /** - * @var Decimal128|Int64|ResolvesToDecimal|ResolvesToDouble|ResolvesToInt|ResolvesToLong|float|int $number Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. + * @var Decimal128|Int64|ResolvesToNumber|float|int $number Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. * $round returns an error if the expression resolves to a non-numeric data type. */ - public readonly Decimal128|Int64|ResolvesToDecimal|ResolvesToDouble|ResolvesToInt|ResolvesToLong|float|int $number; + public readonly Decimal128|Int64|ResolvesToNumber|float|int $number; /** @var Optional|ResolvesToInt|int $place Can be any valid expression that resolves to an integer between -20 and 100, exclusive. */ public readonly Optional|ResolvesToInt|int $place; /** - * @param Decimal128|Int64|ResolvesToDecimal|ResolvesToDouble|ResolvesToInt|ResolvesToLong|float|int $number Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. + * @param Decimal128|Int64|ResolvesToNumber|float|int $number Can be any valid expression that resolves to a number. Specifically, the expression must resolve to an integer, double, decimal, or long. * $round returns an error if the expression resolves to a non-numeric data type. * @param Optional|ResolvesToInt|int $place Can be any valid expression that resolves to an integer between -20 and 100, exclusive. */ public function __construct( - Decimal128|Int64|ResolvesToDecimal|ResolvesToDouble|ResolvesToInt|ResolvesToLong|float|int $number, + Decimal128|Int64|ResolvesToNumber|float|int $number, Optional|ResolvesToInt|int $place = Optional::Undefined, ) { $this->number = $number; diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index f5a4bcec1..f84d00984 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -4266,6 +4266,32 @@ enum Pipelines: string ] JSON; + /** Round Average Rating */ + case RoundRoundAverageRating = <<<'JSON' + [ + { + "$project": { + "roundedAverageRating": { + "$avg": [ + { + "$round": [ + { + "$avg": [ + "$averageRating" + ] + }, + { + "$numberInt": "2" + } + ] + } + ] + } + } + } + ] + JSON; + /** * Example * diff --git a/tests/Builder/Expression/RoundOperatorTest.php b/tests/Builder/Expression/RoundOperatorTest.php index accae5074..758668818 100644 --- a/tests/Builder/Expression/RoundOperatorTest.php +++ b/tests/Builder/Expression/RoundOperatorTest.php @@ -27,4 +27,22 @@ public function testExample(): void $this->assertSamePipeline(Pipelines::RoundExample, $pipeline); } + + public function testRoundAverageRating(): void + { + $pipeline = new Pipeline( + Stage::project( + roundedAverageRating: Expression::avg( + Expression::round( + Expression::avg( + Expression::doubleFieldPath('averageRating'), + ), + 2, + ), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::RoundRoundAverageRating, $pipeline); + } } From 89fb97ad230985d82d903306f63cae81122acf4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Tue, 17 Sep 2024 12:47:56 +0200 Subject: [PATCH 86/95] Add final annotations to non-internal Operation classes (#1410) --- src/Operation/Aggregate.php | 2 ++ src/Operation/BulkWrite.php | 2 ++ src/Operation/Count.php | 2 ++ src/Operation/CountDocuments.php | 2 ++ src/Operation/CreateCollection.php | 2 ++ src/Operation/CreateIndexes.php | 2 ++ src/Operation/CreateSearchIndexes.php | 2 ++ src/Operation/DatabaseCommand.php | 2 ++ src/Operation/DeleteMany.php | 2 ++ src/Operation/DeleteOne.php | 2 ++ src/Operation/Distinct.php | 2 ++ src/Operation/DropCollection.php | 2 ++ src/Operation/DropDatabase.php | 2 ++ src/Operation/DropIndexes.php | 2 ++ src/Operation/DropSearchIndex.php | 2 ++ src/Operation/EstimatedDocumentCount.php | 2 ++ src/Operation/Explain.php | 2 ++ src/Operation/Find.php | 2 ++ src/Operation/FindOne.php | 2 ++ src/Operation/FindOneAndDelete.php | 2 ++ src/Operation/FindOneAndReplace.php | 2 ++ src/Operation/FindOneAndUpdate.php | 2 ++ src/Operation/InsertMany.php | 2 ++ src/Operation/InsertOne.php | 2 ++ src/Operation/ListCollectionNames.php | 2 ++ src/Operation/ListCollections.php | 2 ++ src/Operation/ListDatabaseNames.php | 2 ++ src/Operation/ListDatabases.php | 4 +++- src/Operation/ListIndexes.php | 2 ++ src/Operation/ListSearchIndexes.php | 2 ++ src/Operation/MapReduce.php | 2 ++ src/Operation/ModifyCollection.php | 2 ++ src/Operation/RenameCollection.php | 2 ++ src/Operation/ReplaceOne.php | 2 ++ src/Operation/UpdateMany.php | 2 ++ src/Operation/UpdateOne.php | 2 ++ src/Operation/UpdateSearchIndex.php | 2 ++ src/Operation/Watch.php | 2 ++ 38 files changed, 77 insertions(+), 1 deletion(-) diff --git a/src/Operation/Aggregate.php b/src/Operation/Aggregate.php index 00d408c18..3543fc73c 100644 --- a/src/Operation/Aggregate.php +++ b/src/Operation/Aggregate.php @@ -48,6 +48,8 @@ * * @see \MongoDB\Collection::aggregate() * @see https://mongodb.com/docs/manual/reference/command/aggregate/ + * + * @final extending this class will not be supported in v2.0.0 */ class Aggregate implements Executable, Explainable { diff --git a/src/Operation/BulkWrite.php b/src/Operation/BulkWrite.php index 6da8a68b3..a5367bf2c 100644 --- a/src/Operation/BulkWrite.php +++ b/src/Operation/BulkWrite.php @@ -45,6 +45,8 @@ * Operation for executing multiple write operations. * * @see \MongoDB\Collection::bulkWrite() + * + * @final extending this class will not be supported in v2.0.0 */ class BulkWrite implements Executable { diff --git a/src/Operation/Count.php b/src/Operation/Count.php index b29aa826a..9f9225942 100644 --- a/src/Operation/Count.php +++ b/src/Operation/Count.php @@ -40,6 +40,8 @@ * * @see \MongoDB\Collection::count() * @see https://mongodb.com/docs/manual/reference/command/count/ + * + * @final extending this class will not be supported in v2.0.0 */ class Count implements Executable, Explainable { diff --git a/src/Operation/CountDocuments.php b/src/Operation/CountDocuments.php index 5d6502fca..954ea7466 100644 --- a/src/Operation/CountDocuments.php +++ b/src/Operation/CountDocuments.php @@ -36,6 +36,8 @@ * * @see \MongoDB\Collection::countDocuments() * @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#countdocuments + * + * @final extending this class will not be supported in v2.0.0 */ class CountDocuments implements Executable { diff --git a/src/Operation/CreateCollection.php b/src/Operation/CreateCollection.php index 3292e6fa7..3f4cc881a 100644 --- a/src/Operation/CreateCollection.php +++ b/src/Operation/CreateCollection.php @@ -40,6 +40,8 @@ * * @see \MongoDB\Database::createCollection() * @see https://mongodb.com/docs/manual/reference/command/create/ + * + * @final extending this class will not be supported in v2.0.0 */ class CreateCollection implements Executable { diff --git a/src/Operation/CreateIndexes.php b/src/Operation/CreateIndexes.php index 396cc8163..e2ac08813 100644 --- a/src/Operation/CreateIndexes.php +++ b/src/Operation/CreateIndexes.php @@ -40,6 +40,8 @@ * @see \MongoDB\Collection::createIndex() * @see \MongoDB\Collection::createIndexes() * @see https://mongodb.com/docs/manual/reference/command/createIndexes/ + * + * @final extending this class will not be supported in v2.0.0 */ class CreateIndexes implements Executable { diff --git a/src/Operation/CreateSearchIndexes.php b/src/Operation/CreateSearchIndexes.php index fd27b043c..d5be61116 100644 --- a/src/Operation/CreateSearchIndexes.php +++ b/src/Operation/CreateSearchIndexes.php @@ -36,6 +36,8 @@ * @see \MongoDB\Collection::createSearchIndex() * @see \MongoDB\Collection::createSearchIndexes() * @see https://mongodb.com/docs/manual/reference/command/createSearchIndexes/ + * + * @final extending this class will not be supported in v2.0.0 */ class CreateSearchIndexes implements Executable { diff --git a/src/Operation/DatabaseCommand.php b/src/Operation/DatabaseCommand.php index 4ae8214f1..7ea406baa 100644 --- a/src/Operation/DatabaseCommand.php +++ b/src/Operation/DatabaseCommand.php @@ -31,6 +31,8 @@ * Operation for executing a database command. * * @see \MongoDB\Database::command() + * + * @final extending this class will not be supported in v2.0.0 */ class DatabaseCommand implements Executable { diff --git a/src/Operation/DeleteMany.php b/src/Operation/DeleteMany.php index 20ca38d1d..d6d443b32 100644 --- a/src/Operation/DeleteMany.php +++ b/src/Operation/DeleteMany.php @@ -28,6 +28,8 @@ * * @see \MongoDB\Collection::deleteOne() * @see https://mongodb.com/docs/manual/reference/command/delete/ + * + * @final extending this class will not be supported in v2.0.0 */ class DeleteMany implements Executable, Explainable { diff --git a/src/Operation/DeleteOne.php b/src/Operation/DeleteOne.php index 1aaad5d27..8bbdb45ce 100644 --- a/src/Operation/DeleteOne.php +++ b/src/Operation/DeleteOne.php @@ -28,6 +28,8 @@ * * @see \MongoDB\Collection::deleteOne() * @see https://mongodb.com/docs/manual/reference/command/delete/ + * + * @final extending this class will not be supported in v2.0.0 */ class DeleteOne implements Executable, Explainable { diff --git a/src/Operation/Distinct.php b/src/Operation/Distinct.php index 1483012f3..171a18269 100644 --- a/src/Operation/Distinct.php +++ b/src/Operation/Distinct.php @@ -39,6 +39,8 @@ * * @see \MongoDB\Collection::distinct() * @see https://mongodb.com/docs/manual/reference/command/distinct/ + * + * @final extending this class will not be supported in v2.0.0 */ class Distinct implements Executable, Explainable { diff --git a/src/Operation/DropCollection.php b/src/Operation/DropCollection.php index a467a7391..d19859de1 100644 --- a/src/Operation/DropCollection.php +++ b/src/Operation/DropCollection.php @@ -35,6 +35,8 @@ * @see \MongoDB\Collection::drop() * @see \MongoDB\Database::dropCollection() * @see https://mongodb.com/docs/manual/reference/command/drop/ + * + * @final extending this class will not be supported in v2.0.0 */ class DropCollection implements Executable { diff --git a/src/Operation/DropDatabase.php b/src/Operation/DropDatabase.php index 15cb0c34e..290887900 100644 --- a/src/Operation/DropDatabase.php +++ b/src/Operation/DropDatabase.php @@ -33,6 +33,8 @@ * @see \MongoDB\Client::dropDatabase() * @see \MongoDB\Database::drop() * @see https://mongodb.com/docs/manual/reference/command/dropDatabase/ + * + * @final extending this class will not be supported in v2.0.0 */ class DropDatabase implements Executable { diff --git a/src/Operation/DropIndexes.php b/src/Operation/DropIndexes.php index c5302e250..8fde88b4d 100644 --- a/src/Operation/DropIndexes.php +++ b/src/Operation/DropIndexes.php @@ -34,6 +34,8 @@ * * @see \MongoDB\Collection::dropIndexes() * @see https://mongodb.com/docs/manual/reference/command/dropIndexes/ + * + * @final extending this class will not be supported in v2.0.0 */ class DropIndexes implements Executable { diff --git a/src/Operation/DropSearchIndex.php b/src/Operation/DropSearchIndex.php index 42fc58baa..e65adee6a 100644 --- a/src/Operation/DropSearchIndex.php +++ b/src/Operation/DropSearchIndex.php @@ -29,6 +29,8 @@ * * @see \MongoDB\Collection::dropSearchIndexes() * @see https://mongodb.com/docs/manual/reference/command/dropSearchIndexes/ + * + * @final extending this class will not be supported in v2.0.0 */ class DropSearchIndex implements Executable { diff --git a/src/Operation/EstimatedDocumentCount.php b/src/Operation/EstimatedDocumentCount.php index e3c240c78..db876c71c 100644 --- a/src/Operation/EstimatedDocumentCount.php +++ b/src/Operation/EstimatedDocumentCount.php @@ -34,6 +34,8 @@ * * @see \MongoDB\Collection::estimatedDocumentCount() * @see https://mongodb.com/docs/manual/reference/command/count/ + * + * @final extending this class will not be supported in v2.0.0 */ class EstimatedDocumentCount implements Executable, Explainable { diff --git a/src/Operation/Explain.php b/src/Operation/Explain.php index 1c132b2f5..ea4572fa6 100644 --- a/src/Operation/Explain.php +++ b/src/Operation/Explain.php @@ -34,6 +34,8 @@ * * @see \MongoDB\Collection::explain() * @see https://mongodb.com/docs/manual/reference/command/explain/ + * + * @final extending this class will not be supported in v2.0.0 */ class Explain implements Executable { diff --git a/src/Operation/Find.php b/src/Operation/Find.php index 13944a682..1d2dfb388 100644 --- a/src/Operation/Find.php +++ b/src/Operation/Find.php @@ -47,6 +47,8 @@ * @see \MongoDB\Collection::find() * @see https://mongodb.com/docs/manual/tutorial/query-documents/ * @see https://mongodb.com/docs/manual/reference/operator/query-modifier/ + * + * @final extending this class will not be supported in v2.0.0 */ class Find implements Executable, Explainable { diff --git a/src/Operation/FindOne.php b/src/Operation/FindOne.php index 6fdff0a29..161d01dd0 100644 --- a/src/Operation/FindOne.php +++ b/src/Operation/FindOne.php @@ -30,6 +30,8 @@ * @see \MongoDB\Collection::findOne() * @see https://mongodb.com/docs/manual/tutorial/query-documents/ * @see https://mongodb.com/docs/manual/reference/operator/query-modifier/ + * + * @final extending this class will not be supported in v2.0.0 */ class FindOne implements Executable, Explainable { diff --git a/src/Operation/FindOneAndDelete.php b/src/Operation/FindOneAndDelete.php index ae5148872..a560ee4c6 100644 --- a/src/Operation/FindOneAndDelete.php +++ b/src/Operation/FindOneAndDelete.php @@ -29,6 +29,8 @@ * * @see \MongoDB\Collection::findOneAndDelete() * @see https://mongodb.com/docs/manual/reference/command/findAndModify/ + * + * @final extending this class will not be supported in v2.0.0 */ class FindOneAndDelete implements Executable, Explainable { diff --git a/src/Operation/FindOneAndReplace.php b/src/Operation/FindOneAndReplace.php index 6f4409406..dfd761305 100644 --- a/src/Operation/FindOneAndReplace.php +++ b/src/Operation/FindOneAndReplace.php @@ -34,6 +34,8 @@ * * @see \MongoDB\Collection::findOneAndReplace() * @see https://mongodb.com/docs/manual/reference/command/findAndModify/ + * + * @final extending this class will not be supported in v2.0.0 */ class FindOneAndReplace implements Executable, Explainable { diff --git a/src/Operation/FindOneAndUpdate.php b/src/Operation/FindOneAndUpdate.php index 0fbe127d0..3566c9822 100644 --- a/src/Operation/FindOneAndUpdate.php +++ b/src/Operation/FindOneAndUpdate.php @@ -33,6 +33,8 @@ * * @see \MongoDB\Collection::findOneAndUpdate() * @see https://mongodb.com/docs/manual/reference/command/findAndModify/ + * + * @final extending this class will not be supported in v2.0.0 */ class FindOneAndUpdate implements Executable, Explainable { diff --git a/src/Operation/InsertMany.php b/src/Operation/InsertMany.php index 01640da2f..5e227cbc2 100644 --- a/src/Operation/InsertMany.php +++ b/src/Operation/InsertMany.php @@ -37,6 +37,8 @@ * * @see \MongoDB\Collection::insertMany() * @see https://mongodb.com/docs/manual/reference/command/insert/ + * + * @final extending this class will not be supported in v2.0.0 */ class InsertMany implements Executable { diff --git a/src/Operation/InsertOne.php b/src/Operation/InsertOne.php index 8626364f8..6d8f58cd8 100644 --- a/src/Operation/InsertOne.php +++ b/src/Operation/InsertOne.php @@ -35,6 +35,8 @@ * * @see \MongoDB\Collection::insertOne() * @see https://mongodb.com/docs/manual/reference/command/insert/ + * + * @final extending this class will not be supported in v2.0.0 */ class InsertOne implements Executable { diff --git a/src/Operation/ListCollectionNames.php b/src/Operation/ListCollectionNames.php index f2124c4d4..fe0ea12c6 100644 --- a/src/Operation/ListCollectionNames.php +++ b/src/Operation/ListCollectionNames.php @@ -29,6 +29,8 @@ * * @see \MongoDB\Database::listCollectionNames() * @see https://mongodb.com/docs/manual/reference/command/listCollections/ + * + * @final extending this class will not be supported in v2.0.0 */ class ListCollectionNames implements Executable { diff --git a/src/Operation/ListCollections.php b/src/Operation/ListCollections.php index 340c11017..29b53df7c 100644 --- a/src/Operation/ListCollections.php +++ b/src/Operation/ListCollections.php @@ -29,6 +29,8 @@ * * @see \MongoDB\Database::listCollections() * @see https://mongodb.com/docs/manual/reference/command/listCollections/ + * + * @final extending this class will not be supported in v2.0.0 */ class ListCollections implements Executable { diff --git a/src/Operation/ListDatabaseNames.php b/src/Operation/ListDatabaseNames.php index 7b6a529b4..f7301c169 100644 --- a/src/Operation/ListDatabaseNames.php +++ b/src/Operation/ListDatabaseNames.php @@ -32,6 +32,8 @@ * * @see \MongoDB\Client::listDatabaseNames() * @see https://mongodb.com/docs/manual/reference/command/listDatabases/#mongodb-dbcommand-dbcmd.listDatabases + * + * @final extending this class will not be supported in v2.0.0 */ class ListDatabaseNames implements Executable { diff --git a/src/Operation/ListDatabases.php b/src/Operation/ListDatabases.php index ee90cca65..2e7e36b22 100644 --- a/src/Operation/ListDatabases.php +++ b/src/Operation/ListDatabases.php @@ -29,7 +29,9 @@ * Operation for the ListDatabases command. * * @see \MongoDB\Client::listDatabases() - * @see https://mongodb.com/docs/manual/reference/command/listDatabases/#mongodb-dbcommand-dbcmd.listDatabases` + * @see https://mongodb.com/docs/manual/reference/command/listDatabases/#mongodb-dbcommand-dbcmd.listDatabases + * + * @final extending this class will not be supported in v2.0.0 */ class ListDatabases implements Executable { diff --git a/src/Operation/ListIndexes.php b/src/Operation/ListIndexes.php index 384a5f498..351b88bac 100644 --- a/src/Operation/ListIndexes.php +++ b/src/Operation/ListIndexes.php @@ -35,6 +35,8 @@ * * @see \MongoDB\Collection::listIndexes() * @see https://mongodb.com/docs/manual/reference/command/listIndexes/ + * + * @final extending this class will not be supported in v2.0.0 */ class ListIndexes implements Executable { diff --git a/src/Operation/ListSearchIndexes.php b/src/Operation/ListSearchIndexes.php index 2dbb3688b..e685da5c2 100644 --- a/src/Operation/ListSearchIndexes.php +++ b/src/Operation/ListSearchIndexes.php @@ -34,6 +34,8 @@ * * @see \MongoDB\Collection::listSearchIndexes() * @see https://mongodb.com/docs/manual/reference/command/listSearchIndexes/ + * + * @final extending this class will not be supported in v2.0.0 */ class ListSearchIndexes implements Executable { diff --git a/src/Operation/MapReduce.php b/src/Operation/MapReduce.php index faf91019c..a62dd2f86 100644 --- a/src/Operation/MapReduce.php +++ b/src/Operation/MapReduce.php @@ -53,6 +53,8 @@ * @see \MongoDB\Collection::mapReduce() * @see https://mongodb.com/docs/manual/reference/command/mapReduce/ * @psalm-import-type MapReduceCallable from MapReduceResult + * + * @final extending this class will not be supported in v2.0.0 */ class MapReduce implements Executable { diff --git a/src/Operation/ModifyCollection.php b/src/Operation/ModifyCollection.php index 53eaa9e09..f8d0c721d 100644 --- a/src/Operation/ModifyCollection.php +++ b/src/Operation/ModifyCollection.php @@ -32,6 +32,8 @@ * * @see \MongoDB\Database::modifyCollection() * @see https://mongodb.com/docs/manual/reference/command/collMod/ + * + * @final extending this class will not be supported in v2.0.0 */ class ModifyCollection implements Executable { diff --git a/src/Operation/RenameCollection.php b/src/Operation/RenameCollection.php index 5b69cce41..245231a13 100644 --- a/src/Operation/RenameCollection.php +++ b/src/Operation/RenameCollection.php @@ -35,6 +35,8 @@ * @see \MongoDB\Collection::rename() * @see \MongoDB\Database::renameCollection() * @see https://mongodb.com/docs/manual/reference/command/renameCollection/ + * + * @final extending this class will not be supported in v2.0.0 */ class RenameCollection implements Executable { diff --git a/src/Operation/ReplaceOne.php b/src/Operation/ReplaceOne.php index 32bf5a06d..3cec99ff0 100644 --- a/src/Operation/ReplaceOne.php +++ b/src/Operation/ReplaceOne.php @@ -33,6 +33,8 @@ * * @see \MongoDB\Collection::replaceOne() * @see https://mongodb.com/docs/manual/reference/command/update/ + * + * @final extending this class will not be supported in v2.0.0 */ class ReplaceOne implements Executable { diff --git a/src/Operation/UpdateMany.php b/src/Operation/UpdateMany.php index e61731622..f600d46e6 100644 --- a/src/Operation/UpdateMany.php +++ b/src/Operation/UpdateMany.php @@ -31,6 +31,8 @@ * * @see \MongoDB\Collection::updateMany() * @see https://mongodb.com/docs/manual/reference/command/update/ + * + * @final extending this class will not be supported in v2.0.0 */ class UpdateMany implements Executable, Explainable { diff --git a/src/Operation/UpdateOne.php b/src/Operation/UpdateOne.php index 40d71addf..17bc4a554 100644 --- a/src/Operation/UpdateOne.php +++ b/src/Operation/UpdateOne.php @@ -31,6 +31,8 @@ * * @see \MongoDB\Collection::updateOne() * @see https://mongodb.com/docs/manual/reference/command/update/ + * + * @final extending this class will not be supported in v2.0.0 */ class UpdateOne implements Executable, Explainable { diff --git a/src/Operation/UpdateSearchIndex.php b/src/Operation/UpdateSearchIndex.php index 31d86ca8e..effb4a942 100644 --- a/src/Operation/UpdateSearchIndex.php +++ b/src/Operation/UpdateSearchIndex.php @@ -30,6 +30,8 @@ * * @see \MongoDB\Collection::updateSearchIndexes() * @see https://mongodb.com/docs/manual/reference/command/updateSearchIndexes/ + * + * @final extending this class will not be supported in v2.0.0 */ class UpdateSearchIndex implements Executable { diff --git a/src/Operation/Watch.php b/src/Operation/Watch.php index 09f8b659b..d8a1a9f27 100644 --- a/src/Operation/Watch.php +++ b/src/Operation/Watch.php @@ -56,6 +56,8 @@ * * @see \MongoDB\Collection::watch() * @see https://mongodb.com/docs/manual/changeStreams/ + * + * @final extending this class will not be supported in v2.0.0 */ class Watch implements Executable, /* @internal */ CommandSubscriber { From b27a61d7c13d4353e8b2acee23beddb1d5539dd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 18 Sep 2024 22:45:53 +0200 Subject: [PATCH 87/95] Performance: Keep collections and indexes between GridFS tests (#1421) --- tests/GridFS/BucketFunctionalTest.php | 13 ++++++++++++ tests/GridFS/FunctionalTestCase.php | 30 ++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/tests/GridFS/BucketFunctionalTest.php b/tests/GridFS/BucketFunctionalTest.php index 2f2d126d4..3456951a9 100644 --- a/tests/GridFS/BucketFunctionalTest.php +++ b/tests/GridFS/BucketFunctionalTest.php @@ -814,9 +814,22 @@ public function testUploadingFirstFileCreatesIndexes(): void public function testExistingIndexIsReused(): void { + // The collections may exist from other tests, ensure they are removed + // before and after to avoid potential conflicts. + $this->dropCollection($this->getDatabaseName(), 'fs.chunks'); + $this->dropCollection($this->getDatabaseName(), 'fs.files'); + + // Create indexes with different numeric types before interacting with + // GridFS to assert that the library respects the existing indexes and + // does not attempt to create its own. $this->filesCollection->createIndex(['filename' => 1.0, 'uploadDate' => 1], ['name' => 'test']); $this->chunksCollection->createIndex(['files_id' => 1.0, 'n' => 1], ['name' => 'test', 'unique' => true]); + $this->assertIndexExists('fs.files', 'test'); + $this->assertIndexExists('fs.chunks', 'test', function (IndexInfo $info): void { + $this->assertTrue($info->isUnique()); + }); + $this->bucket->uploadFromStream('filename', self::createStream('foo')); $this->assertIndexNotExists($this->filesCollection->getCollectionName(), 'filename_1_uploadDate_1'); diff --git a/tests/GridFS/FunctionalTestCase.php b/tests/GridFS/FunctionalTestCase.php index d010ca929..785e1c909 100644 --- a/tests/GridFS/FunctionalTestCase.php +++ b/tests/GridFS/FunctionalTestCase.php @@ -4,6 +4,7 @@ use MongoDB\Collection; use MongoDB\GridFS\Bucket; +use MongoDB\Operation\DropCollection; use MongoDB\Tests\FunctionalTestCase as BaseFunctionalTestCase; use function fopen; @@ -28,10 +29,33 @@ public function setUp(): void parent::setUp(); $this->bucket = new Bucket($this->manager, $this->getDatabaseName()); - $this->bucket->drop(); - $this->chunksCollection = $this->createCollection($this->getDatabaseName(), 'fs.chunks'); - $this->filesCollection = $this->createCollection($this->getDatabaseName(), 'fs.files'); + $this->chunksCollection = new Collection($this->manager, $this->getDatabaseName(), 'fs.chunks'); + $this->filesCollection = new Collection($this->manager, $this->getDatabaseName(), 'fs.files'); + } + + public function tearDown(): void + { + $this->chunksCollection->deleteMany([]); + $this->filesCollection->deleteMany([]); + + parent::tearDown(); + } + + /** + * The bucket's collections are created by the first test that runs and + * kept for all subsequent tests. This is done to avoid creating the + * collections and their indexes for each test, which would be slow. + * + * @beforeClass + * @afterClass + */ + public static function dropCollectionsBeforeAfterClass(): void + { + $manager = static::createTestManager(); + + (new DropCollection(self::getDatabaseName(), 'fs.chunks'))->execute($manager->selectServer()); + (new DropCollection(self::getDatabaseName(), 'fs.files'))->execute($manager->selectServer()); } /** From 64a2736215da74e2fa721d3a0ea5aa5798e9a289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 23 Sep 2024 11:03:06 +0200 Subject: [PATCH 88/95] Change deprecated assertObjectHasAttribute to assertObjectHasProperty (#1432) --- tests/UnifiedSpecTests/ManagesFailPointsTrait.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/UnifiedSpecTests/ManagesFailPointsTrait.php b/tests/UnifiedSpecTests/ManagesFailPointsTrait.php index d88368fa9..a1c313074 100644 --- a/tests/UnifiedSpecTests/ManagesFailPointsTrait.php +++ b/tests/UnifiedSpecTests/ManagesFailPointsTrait.php @@ -7,7 +7,7 @@ use stdClass; use function PHPUnit\Framework\assertIsString; -use function PHPUnit\Framework\assertObjectHasAttribute; +use function PHPUnit\Framework\assertObjectHasProperty; trait ManagesFailPointsTrait { @@ -16,9 +16,9 @@ trait ManagesFailPointsTrait public function configureFailPoint(stdClass $failPoint, Server $server): void { - assertObjectHasAttribute('configureFailPoint', $failPoint); + assertObjectHasProperty('configureFailPoint', $failPoint); assertIsString($failPoint->configureFailPoint); - assertObjectHasAttribute('mode', $failPoint); + assertObjectHasProperty('mode', $failPoint); $operation = new DatabaseCommand('admin', $failPoint); $operation->execute($server); From 0960e1c25a00206d47937a6509d805b675e7ac38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 23 Sep 2024 19:13:00 +0200 Subject: [PATCH 89/95] PHPLIB-1525 Removes dependency to Symfony PHPUnit bridge (#1413) * Remove symfony/phpunit-bridge * Run phpunit directly * Return null when expected --- .evergreen/run-tests.sh | 14 +++++++------- .github/workflows/tests.yml | 2 +- CONTRIBUTING.md | 8 -------- composer.json | 4 ++-- tests/GridFS/BucketFunctionalTest.php | 6 ------ tests/SpecTests/DocumentsMatchConstraint.php | 15 +++++++-------- .../Constraint/IsBsonType.php | 7 ++----- tests/UnifiedSpecTests/Constraint/Matches.php | 19 +++++++------------ 8 files changed, 26 insertions(+), 49 deletions(-) diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index 4ff744cd9..abf6aacb1 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -83,30 +83,30 @@ export MONGODB_MULTI_MONGOS_LB_URI="${MONGODB_MULTI_MONGOS_LB_URI}" # Run the tests, and store the results in a junit result file case "$TESTS" in atlas) - php vendor/bin/simple-phpunit $PHPUNIT_OPTS --group atlas + php vendor/bin/phpunit $PHPUNIT_OPTS --group atlas ;; atlas-data-lake) - php vendor/bin/simple-phpunit $PHPUNIT_OPTS --group atlas-data-lake + php vendor/bin/phpunit $PHPUNIT_OPTS --group atlas-data-lake ;; csfle) - php vendor/bin/simple-phpunit $PHPUNIT_OPTS --group csfle + php vendor/bin/phpunit $PHPUNIT_OPTS --group csfle ;; csfle-without-aws-creds) - php vendor/bin/simple-phpunit $PHPUNIT_OPTS --group csfle-without-aws-creds + php vendor/bin/phpunit $PHPUNIT_OPTS --group csfle-without-aws-creds ;; versioned-api) - php vendor/bin/simple-phpunit $PHPUNIT_OPTS --group versioned-api + php vendor/bin/phpunit $PHPUNIT_OPTS --group versioned-api ;; serverless) - php vendor/bin/simple-phpunit $PHPUNIT_OPTS --group serverless + php vendor/bin/phpunit $PHPUNIT_OPTS --group serverless ;; *) - php vendor/bin/simple-phpunit $PHPUNIT_OPTS + php vendor/bin/phpunit $PHPUNIT_OPTS ;; esac diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5e7497813..803eb476d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -77,7 +77,7 @@ jobs: php-ini-values: "zend.assertions=1" - name: "Run PHPUnit" - run: "vendor/bin/simple-phpunit -v" + run: "vendor/bin/phpunit -v" env: SYMFONY_DEPRECATIONS_HELPER: 999999 MONGODB_URI: ${{ steps.setup-mongodb.outputs.cluster-uri }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee5ee3a3e..fe387e26d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,14 +43,6 @@ credentials in the connection string (i.e. `MONGODB_URI`) or set the Note that `MONGODB_USERNAME` and `MONGODB_PASSWORD` will override any credentials present in the connection string. -By default, the `simple-phpunit` binary chooses the correct PHPUnit version for -the PHP version you are running. To run tests against a specific PHPUnit -version, use the `SYMFONY_PHPUNIT_VERSION` environment variable: - -```console -$ SYMFONY_PHPUNIT_VERSION=8.5 vendor/bin/simple-phpunit -``` - ### Environment Variables The test suite references the following environment variables: diff --git a/composer.json b/composer.json index a856f4e84..3ac90068c 100644 --- a/composer.json +++ b/composer.json @@ -21,9 +21,9 @@ }, "require-dev": { "doctrine/coding-standard": "^12.0", + "phpunit/phpunit": "^9.6", "rector/rector": "^1.1", "squizlabs/php_codesniffer": "^3.7", - "symfony/phpunit-bridge": "^7.1", "vimeo/psalm": "^5.13" }, "replace": { @@ -51,7 +51,7 @@ "fix:cs": "phpcbf", "fix:psalm:baseline": "psalm --set-baseline=psalm-baseline.xml", "fix:rector": "rector process --ansi", - "test": "simple-phpunit" + "test": "phpunit" }, "extra": { "branch-alias": { diff --git a/tests/GridFS/BucketFunctionalTest.php b/tests/GridFS/BucketFunctionalTest.php index 3456951a9..ccbe6bf02 100644 --- a/tests/GridFS/BucketFunctionalTest.php +++ b/tests/GridFS/BucketFunctionalTest.php @@ -871,7 +871,6 @@ public function testDanglingOpenWritableStream(): void } $code = <<<'PHP' - require '%s'; require '%s'; $client = MongoDB\Tests\FunctionalTestCase::createTestClient(); $database = $client->selectDatabase(getenv('MONGODB_DATABASE') ?: 'phplib_test'); @@ -888,8 +887,6 @@ public function testDanglingOpenWritableStream(): void sprintf( $code, __DIR__ . '/../../vendor/autoload.php', - // Include the PHPUnit autoload file to ensure PHPUnit classes can be loaded - __DIR__ . '/../../vendor/bin/.phpunit/phpunit/vendor/autoload.php', ), ), '2>&1', @@ -914,7 +911,6 @@ public function testDanglingOpenWritableStreamWithGlobalStreamWrapperAlias(): vo } $code = <<<'PHP' - require '%s'; require '%s'; $client = MongoDB\Tests\FunctionalTestCase::createTestClient(); $database = $client->selectDatabase(getenv('MONGODB_DATABASE') ?: 'phplib_test'); @@ -931,8 +927,6 @@ public function testDanglingOpenWritableStreamWithGlobalStreamWrapperAlias(): vo sprintf( $code, __DIR__ . '/../../vendor/autoload.php', - // Include the PHPUnit autoload file to ensure PHPUnit classes can be loaded - __DIR__ . '/../../vendor/bin/.phpunit/phpunit/vendor/autoload.php', ), ), '2>&1', diff --git a/tests/SpecTests/DocumentsMatchConstraint.php b/tests/SpecTests/DocumentsMatchConstraint.php index 16e7914a6..e8d4d0de0 100644 --- a/tests/SpecTests/DocumentsMatchConstraint.php +++ b/tests/SpecTests/DocumentsMatchConstraint.php @@ -13,7 +13,6 @@ use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory; use stdClass; -use Symfony\Bridge\PhpUnit\ConstraintTrait; use function array_values; use function get_debug_type; @@ -36,8 +35,6 @@ */ class DocumentsMatchConstraint extends Constraint { - use ConstraintTrait; - /** * TODO: This is not currently used, but was preserved from the design of * TestCase::assertMatchesDocument(), which would sort keys and then compare @@ -65,7 +62,7 @@ public function __construct(array|object $value, private bool $ignoreExtraKeysIn $this->comparatorFactory = Factory::getInstance(); } - private function doEvaluate($other, $description = '', $returnResult = false) + public function evaluate($other, string $description = '', bool $returnResult = false): ?bool { /* TODO: If ignoreExtraKeys and sortKeys are both false, then we may be * able to skip preparation, convert both documents to extended JSON, @@ -100,6 +97,8 @@ private function doEvaluate($other, $description = '', $returnResult = false) if (! $success) { $this->fail($other, $description, $this->lastFailure); } + + return null; } /** @param string|BSONArray[] $expectedType */ @@ -198,7 +197,7 @@ private function assertEquals(ArrayObject $expected, ArrayObject $actual, bool $ } } - private function doAdditionalFailureDescription($other) + protected function additionalFailureDescription($other): string { if ($this->lastFailure === null) { return ''; @@ -207,12 +206,12 @@ private function doAdditionalFailureDescription($other) return $this->lastFailure->getMessage(); } - private function doFailureDescription($other) + protected function failureDescription($other): string { return 'two BSON objects are equal'; } - private function doMatches($other) + protected function matches($other): bool { /* TODO: If ignoreExtraKeys and sortKeys are both false, then we may be * able to skip preparation, convert both documents to extended JSON, @@ -232,7 +231,7 @@ private function doMatches($other) return true; } - private function doToString() + public function toString(): string { return 'matches ' . $this->exporter()->export($this->value); } diff --git a/tests/UnifiedSpecTests/Constraint/IsBsonType.php b/tests/UnifiedSpecTests/Constraint/IsBsonType.php index 7d17a9af7..e81ccb2a3 100644 --- a/tests/UnifiedSpecTests/Constraint/IsBsonType.php +++ b/tests/UnifiedSpecTests/Constraint/IsBsonType.php @@ -23,7 +23,6 @@ use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\Constraint\LogicalOr; use RuntimeException; -use Symfony\Bridge\PhpUnit\ConstraintTrait; use function array_keys; use function array_map; @@ -40,8 +39,6 @@ final class IsBsonType extends Constraint { - use ConstraintTrait; - private static array $types = [ 'double', 'string', @@ -88,7 +85,7 @@ public static function anyOf(string ...$types): Constraint return LogicalOr::fromConstraints(...array_map(fn ($type) => new self($type), $types)); } - private function doMatches($other): bool + protected function matches($other): bool { return match ($this->type) { 'double' => is_float($other), @@ -118,7 +115,7 @@ private function doMatches($other): bool }; } - private function doToString(): string + public function toString(): string { return sprintf('is of BSON type "%s"', $this->type); } diff --git a/tests/UnifiedSpecTests/Constraint/Matches.php b/tests/UnifiedSpecTests/Constraint/Matches.php index 2a80357fd..4346b6dc8 100644 --- a/tests/UnifiedSpecTests/Constraint/Matches.php +++ b/tests/UnifiedSpecTests/Constraint/Matches.php @@ -13,7 +13,6 @@ use RuntimeException; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory; -use Symfony\Bridge\PhpUnit\ConstraintTrait; use function array_keys; use function count; @@ -49,8 +48,6 @@ */ class Matches extends Constraint { - use ConstraintTrait; - private mixed $value; private bool $allowExtraRootKeys; @@ -69,7 +66,7 @@ public function __construct($value, private ?EntityMap $entityMap = null, $allow $this->comparatorFactory = Factory::getInstance(); } - private function doEvaluate($other, $description = '', $returnResult = false) + public function evaluate($other, $description = '', $returnResult = false): ?bool { $other = self::prepare($other); $success = false; @@ -104,6 +101,8 @@ private function doEvaluate($other, $description = '', $returnResult = false) if (! $success) { $this->fail($other, $description, $this->lastFailure); } + + return null; } private function assertEquals($expected, $actual, string $keyPath): void @@ -321,8 +320,7 @@ private function assertMatchesOperator(BSONDocument $operator, $actual, string $ throw new LogicException('unsupported operator: ' . $name); } - /** @see ConstraintTrait */ - private function doAdditionalFailureDescription($other) + protected function additionalFailureDescription($other): string { if ($this->lastFailure === null) { return ''; @@ -331,14 +329,12 @@ private function doAdditionalFailureDescription($other) return $this->lastFailure->getMessage(); } - /** @see ConstraintTrait */ - private function doFailureDescription($other) + protected function failureDescription($other): string { return 'expected value matches actual value'; } - /** @see ConstraintTrait */ - private function doMatches($other) + protected function matches($other): bool { $other = self::prepare($other); @@ -351,8 +347,7 @@ private function doMatches($other) return true; } - /** @see ConstraintTrait */ - private function doToString() + public function toString(): string { return 'matches ' . $this->exporter()->export($this->value); } From ec7b346af5e8f0ead566087845d434fea0e8bf89 Mon Sep 17 00:00:00 2001 From: MongoDB PHP Bot <162451593+mongodb-php-bot@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:12:47 +0200 Subject: [PATCH 90/95] Merge v1.20 into v1.x (#1447) * Retry once when disabling fail points (#1445) The previous refactor in 90dcf186c8664a2cdd57314122d46b16234555c2 changed when we disable fail points. ManagesFailPointTrait now does so immediately after test operations are run instead of during tear down, so we may be hitting a point where the server stream was disconnected and libmongoc is not recovering (possibly related to CDRIVER-4532). Adding an extra retry attempt seems to overcome this. * Use non-capturing catch statement --------- Co-authored-by: Jeremy Mikola Co-authored-by: Andreas Braun --- tests/UnifiedSpecTests/ManagesFailPointsTrait.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/UnifiedSpecTests/ManagesFailPointsTrait.php b/tests/UnifiedSpecTests/ManagesFailPointsTrait.php index a1c313074..2680e89ac 100644 --- a/tests/UnifiedSpecTests/ManagesFailPointsTrait.php +++ b/tests/UnifiedSpecTests/ManagesFailPointsTrait.php @@ -2,6 +2,7 @@ namespace MongoDB\Tests\UnifiedSpecTests; +use MongoDB\Driver\Exception\ConnectionException; use MongoDB\Driver\Server; use MongoDB\Operation\DatabaseCommand; use stdClass; @@ -31,8 +32,14 @@ public function configureFailPoint(stdClass $failPoint, Server $server): void public function disableFailPoints(): void { foreach ($this->failPointsAndServers as [$failPoint, $server]) { - $operation = new DatabaseCommand('admin', ['configureFailPoint' => $failPoint, 'mode' => 'off']); - $operation->execute($server); + try { + $operation = new DatabaseCommand('admin', ['configureFailPoint' => $failPoint, 'mode' => 'off']); + $operation->execute($server); + } catch (ConnectionException) { + // Retry once in case the connection was dropped by the last operation + $operation = new DatabaseCommand('admin', ['configureFailPoint' => $failPoint, 'mode' => 'off']); + $operation->execute($server); + } } $this->failPointsAndServers = []; From 8a544703676c0953eaa757f56e8f1889b8897dbb Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Wed, 25 Sep 2024 15:06:50 +0200 Subject: [PATCH 91/95] Expect BulkWriteException (#1455) --- tests/UnifiedSpecTests/ExpectedError.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/UnifiedSpecTests/ExpectedError.php b/tests/UnifiedSpecTests/ExpectedError.php index 06cd2c902..9d598fa1f 100644 --- a/tests/UnifiedSpecTests/ExpectedError.php +++ b/tests/UnifiedSpecTests/ExpectedError.php @@ -7,7 +7,6 @@ use MongoDB\Driver\Exception\ExecutionTimeoutException; use MongoDB\Driver\Exception\RuntimeException; use MongoDB\Driver\Exception\ServerException; -use MongoDB\Driver\Exception\WriteException; use MongoDB\Tests\UnifiedSpecTests\Constraint\Matches; use PHPUnit\Framework\Assert; use stdClass; @@ -157,14 +156,14 @@ public function assert(?Throwable $e = null): void } if (isset($this->matchesResultDocument)) { - assertThat($e, logicalOr(isInstanceOf(CommandException::class), isInstanceOf(WriteException::class))); + assertThat($e, logicalOr(isInstanceOf(CommandException::class), isInstanceOf(BulkWriteException::class))); if ($e instanceof CommandException) { assertThat($e->getResultDocument(), $this->matchesResultDocument, 'CommandException result document matches'); - } elseif ($e instanceof WriteException) { + } elseif ($e instanceof BulkWriteException) { $writeErrors = $e->getWriteResult()->getErrorReplies(); assertCount(1, $writeErrors); - assertThat($writeErrors[0], $this->matchesResultDocument, 'WriteException result document matches'); + assertThat($writeErrors[0], $this->matchesResultDocument, 'BulkWriteException result document matches'); } } From 1837a7559c705c556cd7a85f953ea1d57fa500a7 Mon Sep 17 00:00:00 2001 From: Andreas Braun Date: Wed, 25 Sep 2024 16:01:46 +0200 Subject: [PATCH 92/95] Deprecate functionality to be removed (#1441) * Remove duplicate deprecation notices for query options * PHPLIB-1533: Mark Collection::mapReduce as deprecated * Update deprecation notice language --- src/Collection.php | 6 ++++++ src/Model/IndexInfo.php | 4 ++-- src/Operation/CreateCollection.php | 2 +- src/Operation/Find.php | 11 ----------- tests/Collection/CollectionFunctionalTest.php | 4 +++- tests/Operation/FindTest.php | 18 ------------------ tests/TestCase.php | 6 ++++-- 7 files changed, 16 insertions(+), 35 deletions(-) diff --git a/src/Collection.php b/src/Collection.php index ab82cca5d..937f22f5b 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -77,7 +77,11 @@ use function array_key_exists; use function current; use function is_array; +use function sprintf; use function strlen; +use function trigger_error; + +use const E_USER_DEPRECATED; class Collection { @@ -952,6 +956,8 @@ public function listSearchIndexes(array $options = []): Iterator */ public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, string|array|object $out, array $options = []) { + @trigger_error(sprintf('The %s method is deprecated and will be removed in a version 2.0.', __METHOD__), E_USER_DEPRECATED); + $hasOutputCollection = ! is_mapreduce_output_inline($out); // Check if the out option is inline because we will want to coerce a primary read preference if not diff --git a/src/Model/IndexInfo.php b/src/Model/IndexInfo.php index 53b7b63f0..062c40856 100644 --- a/src/Model/IndexInfo.php +++ b/src/Model/IndexInfo.php @@ -99,7 +99,7 @@ public function getName() */ public function getNamespace() { - @trigger_error('MongoDB 4.4 drops support for the namespace in indexes, the method "IndexInfo::getNamespace()" will be removed in a future release', E_USER_DEPRECATED); + @trigger_error('MongoDB 4.4 drops support for the namespace in indexes, the method "IndexInfo::getNamespace()" will be removed in version 2.0', E_USER_DEPRECATED); return (string) $this->info['ns']; } @@ -132,7 +132,7 @@ public function is2dSphere() */ public function isGeoHaystack() { - @trigger_error('MongoDB 5.0 removes support for "geoHaystack" indexes, the method "IndexInfo::isGeoHaystack()" will be removed in a future release', E_USER_DEPRECATED); + @trigger_error('MongoDB 5.0 removes support for "geoHaystack" indexes, the method "IndexInfo::isGeoHaystack()" will be removed in version 2.0', E_USER_DEPRECATED); return array_search('geoHaystack', $this->getKey(), true) !== false; } diff --git a/src/Operation/CreateCollection.php b/src/Operation/CreateCollection.php index 3f4cc881a..a633dd682 100644 --- a/src/Operation/CreateCollection.php +++ b/src/Operation/CreateCollection.php @@ -230,7 +230,7 @@ public function __construct(private string $databaseName, private string $collec } if (isset($this->options['autoIndexId'])) { - trigger_error('The "autoIndexId" option is deprecated and will be removed in a future release', E_USER_DEPRECATED); + trigger_error('The "autoIndexId" option is deprecated and will be removed in version 2.0', E_USER_DEPRECATED); } if (isset($this->options['pipeline']) && ! is_pipeline($this->options['pipeline'], true /* allowEmpty */)) { diff --git a/src/Operation/Find.php b/src/Operation/Find.php index 1d2dfb388..52c43c525 100644 --- a/src/Operation/Find.php +++ b/src/Operation/Find.php @@ -37,9 +37,6 @@ use function is_string; use function MongoDB\document_to_array; use function MongoDB\is_document; -use function trigger_error; - -use const E_USER_DEPRECATED; /** * Operation for the find command. @@ -285,14 +282,6 @@ public function __construct(private string $databaseName, private string $collec unset($this->options['readConcern']); } - if (isset($this->options['snapshot'])) { - trigger_error('The "snapshot" option is deprecated and will be removed in a future release', E_USER_DEPRECATED); - } - - if (isset($this->options['maxScan'])) { - trigger_error('The "maxScan" option is deprecated and will be removed in a future release', E_USER_DEPRECATED); - } - if (isset($this->options['codec']) && isset($this->options['typeMap'])) { throw InvalidArgumentException::cannotCombineCodecAndTypeMap(); } diff --git a/tests/Collection/CollectionFunctionalTest.php b/tests/Collection/CollectionFunctionalTest.php index d00ebd84e..d63140ca4 100644 --- a/tests/Collection/CollectionFunctionalTest.php +++ b/tests/Collection/CollectionFunctionalTest.php @@ -433,7 +433,9 @@ public function testMapReduce(): void $reduce = new Javascript('function(key, values) { return Array.sum(values); }'); $out = ['inline' => 1]; - $result = $this->collection->mapReduce($map, $reduce, $out); + $result = $this->assertDeprecated( + fn () => $this->collection->mapReduce($map, $reduce, $out), + ); $this->assertInstanceOf(MapReduceResult::class, $result); $expected = [ diff --git a/tests/Operation/FindTest.php b/tests/Operation/FindTest.php index 9e398650d..1a88cd6a2 100644 --- a/tests/Operation/FindTest.php +++ b/tests/Operation/FindTest.php @@ -55,24 +55,6 @@ public static function provideInvalidConstructorOptions() ]); } - public function testSnapshotOptionIsDeprecated(): void - { - $this->assertDeprecated(function (): void { - new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['snapshot' => true]); - }); - - $this->assertDeprecated(function (): void { - new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['snapshot' => false]); - }); - } - - public function testMaxScanOptionIsDeprecated(): void - { - $this->assertDeprecated(function (): void { - new Find($this->getDatabaseName(), $this->getCollectionName(), [], ['maxScan' => 1]); - }); - } - /** @dataProvider provideInvalidConstructorCursorTypeOptions */ public function testConstructorCursorTypeOption($cursorType): void { diff --git a/tests/TestCase.php b/tests/TestCase.php index 4bff57e0d..07f8219ed 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -161,7 +161,7 @@ final public static function provideInvalidStringValues(): array return self::wrapValuesForDataProvider(self::getInvalidStringValues()); } - protected function assertDeprecated(callable $execution): void + protected function assertDeprecated(callable $execution) { $errors = []; @@ -170,12 +170,14 @@ protected function assertDeprecated(callable $execution): void }, E_USER_DEPRECATED | E_DEPRECATED); try { - call_user_func($execution); + $result = call_user_func($execution); } finally { restore_error_handler(); } $this->assertCount(1, $errors); + + return $result; } protected static function createOptionDataProvider(array $options): array From 62dd5ff087fcffe9d5f8c5dff6e6c6f6c6e58028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Wed, 25 Sep 2024 16:23:06 +0200 Subject: [PATCH 93/95] Fix deprecations in tests (#1458) * Reflection method setAccessible is useless As of PHP 8.1.0, calling this method has no effect; all properties are accessible by default. https://www.php.net/manual/en/reflectionproperty.setaccessible.php * Replace deprecated ObjectHasAttribute with ObjectHasProperty --- tests/GridFS/BucketFunctionalTest.php | 4 ---- tests/Operation/WatchFunctionalTest.php | 19 ++++++++----------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/tests/GridFS/BucketFunctionalTest.php b/tests/GridFS/BucketFunctionalTest.php index ccbe6bf02..163c458c2 100644 --- a/tests/GridFS/BucketFunctionalTest.php +++ b/tests/GridFS/BucketFunctionalTest.php @@ -951,8 +951,6 @@ public function testResolveStreamContextForRead(): void fclose($stream); $method = new ReflectionMethod($this->bucket, 'resolveStreamContext'); - $method->setAccessible(true); - $context = $method->invokeArgs($this->bucket, ['gridfs://bucket/filename', 'rb', []]); $this->assertIsArray($context); @@ -967,8 +965,6 @@ public function testResolveStreamContextForRead(): void public function testResolveStreamContextForWrite(): void { $method = new ReflectionMethod($this->bucket, 'resolveStreamContext'); - $method->setAccessible(true); - $context = $method->invokeArgs($this->bucket, ['gridfs://bucket/filename', 'wb', []]); $this->assertIsArray($context); diff --git a/tests/Operation/WatchFunctionalTest.php b/tests/Operation/WatchFunctionalTest.php index cc6690b0f..df4c98fdc 100644 --- a/tests/Operation/WatchFunctionalTest.php +++ b/tests/Operation/WatchFunctionalTest.php @@ -25,6 +25,7 @@ use MongoDB\Operation\InsertOne; use MongoDB\Operation\Watch; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Constraint\ObjectHasProperty; use PHPUnit\Framework\ExpectationFailedException; use ReflectionClass; use stdClass; @@ -724,10 +725,7 @@ public function testInitialCursorIsNotClosed(): void $this->assertNotEquals('0', (string) $changeStream->getCursorId(true)); $rc = new ReflectionClass(ChangeStream::class); - $rp = $rc->getProperty('iterator'); - $rp->setAccessible(true); - - $iterator = $rp->getValue($changeStream); + $iterator = $rc->getProperty('iterator')->getValue($changeStream); $this->assertInstanceOf('IteratorIterator', $iterator); @@ -1225,7 +1223,6 @@ public function testSessionFreed(): void $rc = new ReflectionClass($changeStream); $rp = $rc->getProperty('resumeCallable'); - $rp->setAccessible(true); $this->assertIsCallable($rp->getValue($changeStream)); @@ -1282,9 +1279,9 @@ function (array $event) use (&$aggregateCommands): void { $aggregateCommands[0]['pipeline'][0]->{'$changeStream'}, $this->logicalNot( $this->logicalOr( - $this->objectHasAttribute('resumeAfter'), - $this->objectHasAttribute('startAfter'), - $this->objectHasAttribute('startAtOperationTime'), + new ObjectHasProperty('resumeAfter'), + new ObjectHasProperty('startAfter'), + new ObjectHasProperty('startAtOperationTime'), ), ), ); @@ -1292,9 +1289,9 @@ function (array $event) use (&$aggregateCommands): void { $this->assertThat( $aggregateCommands[1]['pipeline'][0]->{'$changeStream'}, $this->logicalOr( - $this->objectHasAttribute('resumeAfter'), - $this->objectHasAttribute('startAfter'), - $this->objectHasAttribute('startAtOperationTime'), + new ObjectHasProperty('resumeAfter'), + new ObjectHasProperty('startAfter'), + new ObjectHasProperty('startAtOperationTime'), ), ); From 4af38e630552a518855ad5aa7e7da9da118c0150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Thu, 26 Sep 2024 11:25:00 +0200 Subject: [PATCH 94/95] Higher phpunit version required (#1463) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3ac90068c..e7d51e61e 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ }, "require-dev": { "doctrine/coding-standard": "^12.0", - "phpunit/phpunit": "^9.6", + "phpunit/phpunit": "^9.6.11", "rector/rector": "^1.1", "squizlabs/php_codesniffer": "^3.7", "vimeo/psalm": "^5.13" From cb33c1c92b5d5cead5f18996db5825cc6064e81f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 27 Sep 2024 09:07:17 +0200 Subject: [PATCH 95/95] PHPLIB-1369 Upgrade to PHPUnit 10 (#1412) * Migration from @annotations to #[Attributes] * Replace hasFailed with the method onNotSuccessfulTest in FunctionalTestCase * User createStub in getInvalidDocumentCodecValues * Update custom constraints (TestCase::exporter() is deprecated and ComparisonFailure 5th argument was removed) * Use xxh faster algorithm for temp collection name --- .evergreen/run-tests.sh | 2 +- .github/workflows/tests.yml | 3 +- composer.json | 4 +- rector.php | 8 +- tests/Builder/BuilderEncoderTest.php | 4 +- tests/Builder/FieldPathTest.php | 5 +- tests/Builder/Type/CombinedFieldQueryTest.php | 10 +- tests/Builder/Type/OutputWindowTest.php | 15 +-- tests/Builder/Type/QueryObjectTest.php | 15 +-- tests/Builder/VariableTest.php | 3 +- tests/ClientTest.php | 6 +- .../CodecCollectionFunctionalTest.php | 21 ++-- tests/Collection/CollectionFunctionalTest.php | 26 ++--- tests/Command/ListCollectionsTest.php | 3 +- tests/Command/ListDatabasesTest.php | 3 +- tests/Comparator/Int64Comparator.php | 10 +- tests/Comparator/Int64ComparatorTest.php | 12 +-- tests/Comparator/ServerComparator.php | 3 +- tests/Database/DatabaseFunctionalTest.php | 16 +-- tests/DocumentationExamplesTest.php | 8 +- tests/ExamplesTest.php | 10 +- .../InvalidArgumentExceptionTest.php | 3 +- tests/FunctionalTestCase.php | 12 ++- .../Functions/SelectServerFunctionalTest.php | 3 +- tests/FunctionsTest.php | 23 ++-- tests/GridFS/BucketFunctionalTest.php | 32 +++--- tests/GridFS/FunctionalTestCase.php | 7 +- tests/GridFS/ReadableStreamFunctionalTest.php | 13 +-- tests/GridFS/StreamWrapperFunctionalTest.php | 3 +- tests/GridFS/WritableStreamFunctionalTest.php | 8 +- tests/LogNonGenuineHostTest.php | 7 +- tests/Model/BSONIteratorTest.php | 3 +- tests/Model/CallbackIteratorTest.php | 3 +- tests/Model/ChangeStreamIteratorTest.php | 7 +- tests/Model/CodecCursorFunctionalTest.php | 6 +- tests/Model/IndexInfoFunctionalTest.php | 9 +- tests/Model/IndexInputTest.php | 9 +- tests/Model/SearchIndexInputTest.php | 7 +- tests/Operation/AggregateFunctionalTest.php | 3 +- tests/Operation/AggregateTest.php | 3 +- tests/Operation/BulkWriteFunctionalTest.php | 32 +++--- tests/Operation/BulkWriteTest.php | 84 +++++++-------- .../CountDocumentsFunctionalTest.php | 3 +- tests/Operation/CountDocumentsTest.php | 5 +- tests/Operation/CountFunctionalTest.php | 3 +- tests/Operation/CountTest.php | 5 +- tests/Operation/CreateCollectionTest.php | 3 +- ...reateEncryptedCollectionFunctionalTest.php | 9 +- .../CreateEncryptedCollectionTest.php | 3 +- .../Operation/CreateIndexesFunctionalTest.php | 3 +- tests/Operation/CreateIndexesTest.php | 11 +- tests/Operation/CreateSearchIndexesTest.php | 9 +- .../DatabaseCommandFunctionalTest.php | 3 +- tests/Operation/DatabaseCommandTest.php | 5 +- tests/Operation/DeleteFunctionalTest.php | 6 +- tests/Operation/DeleteTest.php | 9 +- tests/Operation/DistinctFunctionalTest.php | 5 +- tests/Operation/DistinctTest.php | 5 +- .../DropCollectionFunctionalTest.php | 3 +- tests/Operation/DropCollectionTest.php | 3 +- .../Operation/DropDatabaseFunctionalTest.php | 3 +- tests/Operation/DropDatabaseTest.php | 3 +- .../Operation/DropEncryptedCollectionTest.php | 3 +- tests/Operation/DropIndexesTest.php | 3 +- .../Operation/EstimatedDocumentCountTest.php | 3 +- tests/Operation/ExplainFunctionalTest.php | 31 +++--- tests/Operation/ExplainTest.php | 3 +- .../Operation/FindAndModifyFunctionalTest.php | 15 ++- tests/Operation/FindAndModifyTest.php | 3 +- tests/Operation/FindFunctionalTest.php | 13 ++- tests/Operation/FindOneAndDeleteTest.php | 5 +- tests/Operation/FindOneAndReplaceTest.php | 24 ++--- tests/Operation/FindOneAndUpdateTest.php | 15 ++- tests/Operation/FindOneFunctionalTest.php | 3 +- tests/Operation/FindTest.php | 7 +- tests/Operation/InsertManyFunctionalTest.php | 5 +- tests/Operation/InsertManyTest.php | 5 +- tests/Operation/InsertOneFunctionalTest.php | 16 +-- tests/Operation/InsertOneTest.php | 5 +- .../ListCollectionsFunctionalTest.php | 11 +- tests/Operation/ListIndexesTest.php | 3 +- tests/Operation/ListSearchIndexesTest.php | 3 +- tests/Operation/MapReduceFunctionalTest.php | 16 +-- tests/Operation/MapReduceTest.php | 7 +- .../ModifyCollectionFunctionalTest.php | 9 +- tests/Operation/ModifyCollectionTest.php | 3 +- tests/Operation/RenameCollectionTest.php | 3 +- tests/Operation/ReplaceOneTest.php | 22 ++-- tests/Operation/UpdateFunctionalTest.php | 22 ++-- tests/Operation/UpdateManyTest.php | 20 ++-- tests/Operation/UpdateOneTest.php | 20 ++-- tests/Operation/UpdateSearchIndexTest.php | 3 +- tests/Operation/UpdateTest.php | 13 ++- tests/Operation/WatchFunctionalTest.php | 29 +++-- tests/Operation/WatchTest.php | 3 +- tests/PedantryTest.php | 3 +- tests/PsrLogAdapterTest.php | 7 +- tests/SpecTests/AtlasDataLakeSpecTest.php | 3 +- ...rose21_AutomaticDataEncryptionKeysTest.php | 30 +++--- .../Prose22_RangeExplicitEncryptionTest.php | 54 ++++------ .../ClientSideEncryptionSpecTest.php | 39 +++---- tests/SpecTests/DocumentsMatchConstraint.php | 15 ++- .../DocumentsMatchConstraintTest.php | 5 +- .../Prose3_ReturnOriginalErrorTest.php | 3 +- tests/SpecTests/SearchIndexSpecTest.php | 3 +- tests/TestCase.php | 42 ++------ .../Constraint/IsBsonTypeTest.php | 5 +- tests/UnifiedSpecTests/Constraint/Matches.php | 13 +-- .../Constraint/MatchesTest.php | 5 +- tests/UnifiedSpecTests/UnifiedSpecTest.php | 100 +++++++----------- 110 files changed, 609 insertions(+), 627 deletions(-) diff --git a/.evergreen/run-tests.sh b/.evergreen/run-tests.sh index abf6aacb1..d5344791e 100755 --- a/.evergreen/run-tests.sh +++ b/.evergreen/run-tests.sh @@ -43,7 +43,7 @@ if [ "${IS_MATRIX_TESTING}" = "true" ]; then fi # Enable verbose output to see skipped and incomplete tests -PHPUNIT_OPTS="${PHPUNIT_OPTS} -v --configuration phpunit.evergreen.xml" +PHPUNIT_OPTS="${PHPUNIT_OPTS} --configuration phpunit.evergreen.xml" if [ "$SSL" = "yes" ]; then SSL_OPTS="ssl=true&sslallowinvalidcertificates=true" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 825c975b8..343a7f6d5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -75,7 +75,6 @@ jobs: php-ini-values: "zend.assertions=1" - name: "Run PHPUnit" - run: "vendor/bin/phpunit -v" + run: "vendor/bin/phpunit" env: - SYMFONY_DEPRECATIONS_HELPER: 999999 MONGODB_URI: ${{ steps.setup-mongodb.outputs.cluster-uri }} diff --git a/composer.json b/composer.json index e7d51e61e..73327dcbb 100644 --- a/composer.json +++ b/composer.json @@ -21,8 +21,8 @@ }, "require-dev": { "doctrine/coding-standard": "^12.0", - "phpunit/phpunit": "^9.6.11", - "rector/rector": "^1.1", + "phpunit/phpunit": "^10.5.35", + "rector/rector": "^1.2", "squizlabs/php_codesniffer": "^3.7", "vimeo/psalm": "^5.13" }, diff --git a/rector.php b/rector.php index 31f1f56f6..edfe66f14 100644 --- a/rector.php +++ b/rector.php @@ -3,8 +3,10 @@ use Rector\Config\RectorConfig; use Rector\DeadCode\Rector\ClassLike\RemoveAnnotationRector; use Rector\Php70\Rector\StmtsAwareInterface\IfIssetToCoalescingRector; +use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector; use Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector; use Rector\PHPUnit\PHPUnit100\Rector\Class_\StaticDataProviderClassMethodRector; +use Rector\PHPUnit\Set\PHPUnitSetList; use Rector\Set\ValueObject\LevelSetList; return static function (RectorConfig $rectorConfig): void { @@ -16,13 +18,17 @@ ]); // Modernize code - $rectorConfig->sets([LevelSetList::UP_TO_PHP_74]); + $rectorConfig->sets([ + LevelSetList::UP_TO_PHP_74, + PHPUnitSetList::PHPUNIT_100, + ]); $rectorConfig->rule(ChangeSwitchToMatchRector::class); $rectorConfig->rule(StaticDataProviderClassMethodRector::class); // phpcs:disable Squiz.Arrays.ArrayDeclaration.KeySpecified $rectorConfig->skip([ + RemoveExtraParametersRector::class, // Do not use ternaries extensively IfIssetToCoalescingRector::class, ChangeSwitchToMatchRector::class => [ diff --git a/tests/Builder/BuilderEncoderTest.php b/tests/Builder/BuilderEncoderTest.php index 9d3292bb2..4e9cd76f2 100644 --- a/tests/Builder/BuilderEncoderTest.php +++ b/tests/Builder/BuilderEncoderTest.php @@ -14,6 +14,7 @@ use MongoDB\Builder\Stage; use MongoDB\Builder\Type\Sort; use MongoDB\Builder\Variable; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use function array_merge; @@ -153,9 +154,8 @@ public function testPerformCount(): void * * @param list $limit * @param array $expectedLimit - * - * @dataProvider provideExpressionFilterLimit */ + #[DataProvider('provideExpressionFilterLimit')] public function testExpressionFilter(array $limit, array $expectedLimit): void { $pipeline = new Pipeline( diff --git a/tests/Builder/FieldPathTest.php b/tests/Builder/FieldPathTest.php index f8bd3a309..afd1a15de 100644 --- a/tests/Builder/FieldPathTest.php +++ b/tests/Builder/FieldPathTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Type\FieldPathInterface; use MongoDB\Exception\InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; use function is_subclass_of; @@ -15,7 +16,7 @@ class FieldPathTest extends TestCase { - /** @dataProvider provideFieldPath */ + #[DataProvider('provideFieldPath')] public function testFieldPath(string $fieldPathClass, string $resolveClass): void { $fieldPath = Expression::{$fieldPathClass}('foo'); @@ -27,7 +28,7 @@ public function testFieldPath(string $fieldPathClass, string $resolveClass): voi $this->assertTrue(is_subclass_of(Expression\FieldPath::class, $resolveClass), sprintf('%s instanceof %s', Expression\FieldPath::class, $resolveClass)); } - /** @dataProvider provideFieldPath */ + #[DataProvider('provideFieldPath')] public function testRejectDollarPrefix(string $fieldPathClass): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Builder/Type/CombinedFieldQueryTest.php b/tests/Builder/Type/CombinedFieldQueryTest.php index ab326afd5..e37f61630 100644 --- a/tests/Builder/Type/CombinedFieldQueryTest.php +++ b/tests/Builder/Type/CombinedFieldQueryTest.php @@ -9,6 +9,7 @@ use MongoDB\Builder\Query\GtOperator; use MongoDB\Builder\Type\CombinedFieldQuery; use MongoDB\Exception\InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class CombinedFieldQueryTest extends TestCase @@ -47,7 +48,7 @@ public function testFlattenCombinedFieldQueries(): void $this->assertCount(3, $fieldQueries->fieldQueries); } - /** @dataProvider provideInvalidFieldQuery */ + #[DataProvider('provideInvalidFieldQuery')] public function testRejectInvalidFieldQueries(mixed $invalidQuery, string $message = '-'): void { $this->expectException(InvalidArgumentException::class); @@ -71,11 +72,8 @@ public static function provideInvalidFieldQuery(): Generator yield 'object key without $' => [(object) ['eq' => 1], 'Operator must contain exactly one key starting with $, "eq" given']; } - /** - * @param array $fieldQueries - * - * @dataProvider provideDuplicateOperator - */ + /** @param array $fieldQueries */ + #[DataProvider('provideDuplicateOperator')] public function testRejectDuplicateOperator(array $fieldQueries): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Builder/Type/OutputWindowTest.php b/tests/Builder/Type/OutputWindowTest.php index 1527a1826..0a60f3635 100644 --- a/tests/Builder/Type/OutputWindowTest.php +++ b/tests/Builder/Type/OutputWindowTest.php @@ -10,6 +10,7 @@ use MongoDB\Builder\Type\TimeUnit; use MongoDB\Builder\Type\WindowInterface; use MongoDB\Exception\InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class OutputWindowTest extends TestCase @@ -57,11 +58,8 @@ public function testWithUnit(): void $this->assertEquals((object) ['unit' => TimeUnit::Day], $outputWindow->window); } - /** - * @param array $documents - * - * @dataProvider provideInvalidDocuments - */ + /** @param array $documents */ + #[DataProvider('provideInvalidDocuments')] public function testRejectInvalidDocuments(array $documents): void { $this->expectException(InvalidArgumentException::class); @@ -82,11 +80,8 @@ public static function provideInvalidDocuments(): Generator yield 'not a list' => [['foo' => 1, 'bar' => 2]]; } - /** - * @param array $range - * - * @dataProvider provideInvalidRange - */ + /** @param array $range */ + #[DataProvider('provideInvalidRange')] public function testRejectInvalidRange(array $range): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Builder/Type/QueryObjectTest.php b/tests/Builder/Type/QueryObjectTest.php index cf5b67fde..819495805 100644 --- a/tests/Builder/Type/QueryObjectTest.php +++ b/tests/Builder/Type/QueryObjectTest.php @@ -13,6 +13,7 @@ use MongoDB\Builder\Type\CombinedFieldQuery; use MongoDB\Builder\Type\QueryInterface; use MongoDB\Builder\Type\QueryObject; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class QueryObjectTest extends TestCase @@ -32,11 +33,8 @@ public function testShortCutQueryObject(): void $this->assertSame($query, $queryObject); } - /** - * @param array $value - * - * @dataProvider provideQueryObjectValue - */ + /** @param array $value */ + #[DataProvider('provideQueryObjectValue')] public function testCreateQueryObject(array $value, int $expectedCount = 1): void { $queryObject = QueryObject::create($value); @@ -44,11 +42,8 @@ public function testCreateQueryObject(array $value, int $expectedCount = 1): voi $this->assertCount($expectedCount, $queryObject->queries); } - /** - * @param array $value - * - * @dataProvider provideQueryObjectValue - */ + /** @param array $value */ + #[DataProvider('provideQueryObjectValue')] public function testCreateQueryObjectFromArray(array $value, int $expectedCount = 1): void { // $value is wrapped in an array as if the user used an array instead of variadic arguments diff --git a/tests/Builder/VariableTest.php b/tests/Builder/VariableTest.php index dc30134a5..14137b3aa 100644 --- a/tests/Builder/VariableTest.php +++ b/tests/Builder/VariableTest.php @@ -8,6 +8,7 @@ use MongoDB\Builder\Expression; use MongoDB\Builder\Variable; use MongoDB\Exception\InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class VariableTest extends TestCase @@ -27,7 +28,7 @@ public function testVariableRejectDollarPrefix(): void new Expression\Variable('$$foo'); } - /** @dataProvider provideVariableBuilders */ + #[DataProvider('provideVariableBuilders')] public function testSystemVariables($factory): void { $variable = $factory(); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index b830556d8..a2fc917ae 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -10,6 +10,8 @@ use MongoDB\Driver\ReadPreference; use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; /** * Unit tests for the Client class. @@ -23,7 +25,7 @@ public function testConstructorDefaultUri(): void $this->assertEquals('mongodb://127.0.0.1/', (string) $client); } - /** @doesNotPerformAssertions */ + #[DoesNotPerformAssertions] public function testConstructorAutoEncryptionOpts(): void { $autoEncryptionOpts = [ @@ -35,7 +37,7 @@ public function testConstructorAutoEncryptionOpts(): void new Client(static::getUri(), [], ['autoEncryption' => $autoEncryptionOpts]); } - /** @dataProvider provideInvalidConstructorDriverOptions */ + #[DataProvider('provideInvalidConstructorDriverOptions')] public function testConstructorDriverOptionTypeChecks(array $driverOptions, string $exception = InvalidArgumentException::class): void { $this->expectException($exception); diff --git a/tests/Collection/CodecCollectionFunctionalTest.php b/tests/Collection/CodecCollectionFunctionalTest.php index d0d33522c..2447243d0 100644 --- a/tests/Collection/CodecCollectionFunctionalTest.php +++ b/tests/Collection/CodecCollectionFunctionalTest.php @@ -12,6 +12,7 @@ use MongoDB\Operation\FindOneAndReplace; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; use MongoDB\Tests\Fixtures\Document\TestObject; +use PHPUnit\Framework\Attributes\DataProvider; class CodecCollectionFunctionalTest extends FunctionalTestCase { @@ -54,7 +55,7 @@ public static function provideAggregateOptions(): Generator ]; } - /** @dataProvider provideAggregateOptions */ + #[DataProvider('provideAggregateOptions')] public function testAggregate($expected, $options): void { $this->createFixtures(3); @@ -114,7 +115,7 @@ public static function provideBulkWriteOptions(): Generator ]; } - /** @dataProvider provideBulkWriteOptions */ + #[DataProvider('provideBulkWriteOptions')] public function testBulkWrite($expected, $options): void { $this->createFixtures(3); @@ -169,7 +170,7 @@ public static function provideFindOneAndModifyOptions(): Generator ]; } - /** @dataProvider provideFindOneAndModifyOptions */ + #[DataProvider('provideFindOneAndModifyOptions')] public function testFindOneAndDelete($expected, $options): void { $this->createFixtures(1); @@ -190,7 +191,7 @@ public function testFindOneAndDeleteWithCodecAndTypemap(): void $this->collection->findOneAndDelete(['_id' => 1], $options); } - /** @dataProvider provideFindOneAndModifyOptions */ + #[DataProvider('provideFindOneAndModifyOptions')] public function testFindOneAndUpdate($expected, $options): void { $this->createFixtures(1); @@ -235,7 +236,7 @@ public static function provideFindOneAndReplaceOptions(): Generator ]; } - /** @dataProvider provideFindOneAndReplaceOptions */ + #[DataProvider('provideFindOneAndReplaceOptions')] public function testFindOneAndReplace($expected, $options): void { $this->createFixtures(1); @@ -293,7 +294,7 @@ public static function provideFindOptions(): Generator ]; } - /** @dataProvider provideFindOptions */ + #[DataProvider('provideFindOptions')] public function testFind($expected, $options): void { $this->createFixtures(3); @@ -332,7 +333,7 @@ public static function provideFindOneOptions(): Generator ]; } - /** @dataProvider provideFindOneOptions */ + #[DataProvider('provideFindOneOptions')] public function testFindOne($expected, $options): void { $this->createFixtures(1); @@ -383,7 +384,7 @@ public static function provideInsertManyOptions(): Generator ]; } - /** @dataProvider provideInsertManyOptions */ + #[DataProvider('provideInsertManyOptions')] public function testInsertMany($expected, $options): void { $documents = [ @@ -430,7 +431,7 @@ public static function provideInsertOneOptions(): Generator ]; } - /** @dataProvider provideInsertOneOptions */ + #[DataProvider('provideInsertOneOptions')] public function testInsertOne($expected, $options): void { $result = $this->collection->insertOne(TestObject::createForFixture(1), $options); @@ -475,7 +476,7 @@ public static function provideReplaceOneOptions(): Generator ]; } - /** @dataProvider provideReplaceOneOptions */ + #[DataProvider('provideReplaceOneOptions')] public function testReplaceOne($expected, $options): void { $this->createFixtures(1); diff --git a/tests/Collection/CollectionFunctionalTest.php b/tests/Collection/CollectionFunctionalTest.php index d63140ca4..a919e38d2 100644 --- a/tests/Collection/CollectionFunctionalTest.php +++ b/tests/Collection/CollectionFunctionalTest.php @@ -16,6 +16,8 @@ use MongoDB\MapReduceResult; use MongoDB\Operation\Count; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use TypeError; use function array_filter; @@ -33,7 +35,7 @@ */ class CollectionFunctionalTest extends FunctionalTestCase { - /** @dataProvider provideInvalidDatabaseAndCollectionNames */ + #[DataProvider('provideInvalidDatabaseAndCollectionNames')] public function testConstructorDatabaseNameArgument($databaseName, string $expectedExceptionClass): void { $this->expectException($expectedExceptionClass); @@ -41,7 +43,7 @@ public function testConstructorDatabaseNameArgument($databaseName, string $expec new Collection($this->manager, $databaseName, $this->getCollectionName()); } - /** @dataProvider provideInvalidDatabaseAndCollectionNames */ + #[DataProvider('provideInvalidDatabaseAndCollectionNames')] public function testConstructorCollectionNameArgument($collectionName, string $expectedExceptionClass): void { $this->expectException($expectedExceptionClass); @@ -57,7 +59,7 @@ public static function provideInvalidDatabaseAndCollectionNames() ]; } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); @@ -168,7 +170,7 @@ function (array $event): void { ); } - /** @dataProvider provideTypeMapOptionsAndExpectedDocuments */ + #[DataProvider('provideTypeMapOptionsAndExpectedDocuments')] public function testDistinctWithTypeMap(array $typeMap, array $expectedDocuments): void { $bulkWrite = new BulkWrite(['ordered' => true]); @@ -419,12 +421,10 @@ public function testWithOptionsPassesOptions(): void $this->assertSame(WriteConcern::MAJORITY, $debug['writeConcern']->getW()); } - /** - * @group matrix-testing-exclude-server-4.4-driver-4.0 - * @group matrix-testing-exclude-server-4.4-driver-4.2 - * @group matrix-testing-exclude-server-5.0-driver-4.0 - * @group matrix-testing-exclude-server-5.0-driver-4.2 - */ + #[Group('matrix-testing-exclude-server-4.4-driver-4.0')] + #[Group('matrix-testing-exclude-server-4.4-driver-4.2')] + #[Group('matrix-testing-exclude-server-5.0-driver-4.0')] + #[Group('matrix-testing-exclude-server-5.0-driver-4.2')] public function testMapReduce(): void { $this->createFixtures(3); @@ -734,7 +734,7 @@ public static function collectionWriteMethodClosures(): array ); } - /** @dataProvider collectionMethodClosures */ + #[DataProvider('collectionMethodClosures')] public function testMethodDoesNotInheritReadWriteConcernInTransaction(Closure $method): void { $this->skipIfTransactionsAreNotSupported(); @@ -760,7 +760,7 @@ function (array $event): void { ); } - /** @dataProvider collectionWriteMethodClosures */ + #[DataProvider('collectionWriteMethodClosures')] public function testMethodInTransactionWithWriteConcernOption($method): void { $this->skipIfTransactionsAreNotSupported(); @@ -780,7 +780,7 @@ public function testMethodInTransactionWithWriteConcernOption($method): void } } - /** @dataProvider collectionReadMethodClosures */ + #[DataProvider('collectionReadMethodClosures')] public function testMethodInTransactionWithReadConcernOption($method): void { $this->skipIfTransactionsAreNotSupported(); diff --git a/tests/Command/ListCollectionsTest.php b/tests/Command/ListCollectionsTest.php index b1d4b0535..54ab1bbe3 100644 --- a/tests/Command/ListCollectionsTest.php +++ b/tests/Command/ListCollectionsTest.php @@ -5,10 +5,11 @@ use MongoDB\Command\ListCollections; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class ListCollectionsTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Command/ListDatabasesTest.php b/tests/Command/ListDatabasesTest.php index 4043826cb..f436bdb0d 100644 --- a/tests/Command/ListDatabasesTest.php +++ b/tests/Command/ListDatabasesTest.php @@ -5,10 +5,11 @@ use MongoDB\Command\ListDatabases; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class ListDatabasesTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Comparator/Int64Comparator.php b/tests/Comparator/Int64Comparator.php index c786aa919..83cc6621e 100644 --- a/tests/Comparator/Int64Comparator.php +++ b/tests/Comparator/Int64Comparator.php @@ -5,13 +5,14 @@ use MongoDB\BSON\Int64; use SebastianBergmann\Comparator\Comparator; use SebastianBergmann\Comparator\ComparisonFailure; +use SebastianBergmann\Exporter\Exporter; use function is_numeric; use function sprintf; class Int64Comparator extends Comparator { - public function accepts($expected, $actual) + public function accepts($expected, $actual): bool { // Only compare if either value is an Int64 and the other value is numeric return ($expected instanceof Int64 && $this->isComparable($actual)) @@ -24,16 +25,17 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f return; } + $exporter = new Exporter(); + throw new ComparisonFailure( $expected, $actual, '', '', - false, sprintf( 'Failed asserting that %s matches expected %s.', - $this->exporter->export($actual), - $this->exporter->export($expected), + $exporter->export($actual), + $exporter->export($expected), ), ); } diff --git a/tests/Comparator/Int64ComparatorTest.php b/tests/Comparator/Int64ComparatorTest.php index 9e85964c2..308b9cec7 100644 --- a/tests/Comparator/Int64ComparatorTest.php +++ b/tests/Comparator/Int64ComparatorTest.php @@ -4,12 +4,14 @@ use Generator; use MongoDB\BSON\Int64; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use PHPUnit\Framework\TestCase; use SebastianBergmann\Comparator\ComparisonFailure; class Int64ComparatorTest extends TestCase { - /** @dataProvider provideAcceptsValues */ + #[DataProvider('provideAcceptsValues')] public function testAccepts(bool $expectedResult, $expectedValue, $actualValue): void { $this->assertSame($expectedResult, (new Int64Comparator())->accepts($expectedValue, $actualValue)); @@ -96,10 +98,8 @@ public static function provideAcceptsValues(): Generator ]; } - /** - * @dataProvider provideMatchingAssertions - * @doesNotPerformAssertions - */ + #[DataProvider('provideMatchingAssertions')] + #[DoesNotPerformAssertions] public function testMatchingAssertions($expected, $actual): void { (new Int64Comparator())->assertEquals($expected, $actual); @@ -153,7 +153,7 @@ public static function provideMatchingAssertions(): Generator ]; } - /** @dataProvider provideFailingValues */ + #[DataProvider('provideFailingValues')] public function testFailingAssertions($expected, $actual): void { $this->expectException(ComparisonFailure::class); diff --git a/tests/Comparator/ServerComparator.php b/tests/Comparator/ServerComparator.php index 54b109795..28c958593 100644 --- a/tests/Comparator/ServerComparator.php +++ b/tests/Comparator/ServerComparator.php @@ -10,7 +10,7 @@ class ServerComparator extends Comparator { - public function accepts($expected, $actual) + public function accepts($expected, $actual): bool { return $expected instanceof Server && $actual instanceof Server; } @@ -26,7 +26,6 @@ public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = f $actual, '', '', - false, sprintf( 'Failed asserting that Server("%s:%d") matches expected Server("%s:%d").', $actual->getHost(), diff --git a/tests/Database/DatabaseFunctionalTest.php b/tests/Database/DatabaseFunctionalTest.php index 2647f9f8b..d6e3ff5b7 100644 --- a/tests/Database/DatabaseFunctionalTest.php +++ b/tests/Database/DatabaseFunctionalTest.php @@ -12,6 +12,8 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\CreateIndexes; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use TypeError; use function array_key_exists; @@ -22,7 +24,7 @@ */ class DatabaseFunctionalTest extends FunctionalTestCase { - /** @dataProvider provideInvalidDatabaseNames */ + #[DataProvider('provideInvalidDatabaseNames')] public function testConstructorDatabaseNameArgument($databaseName, string $expectedExceptionClass): void { $this->expectException($expectedExceptionClass); @@ -38,7 +40,7 @@ public static function provideInvalidDatabaseNames() ]; } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); @@ -122,7 +124,7 @@ public function testCommandAppliesTypeMapToCursor(): void $this->assertSame(1, (int) $commandResult['ok']); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testCommandCommandArgumentTypeCheck($command): void { $this->expectException($command instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); @@ -170,11 +172,9 @@ public function testGetSelectsCollectionAndInheritsOptions(): void $this->assertSame(WriteConcern::MAJORITY, $debug['writeConcern']->getW()); } - /** - * @group matrix-testing-exclude-server-4.2-driver-4.0-topology-sharded_cluster - * @group matrix-testing-exclude-server-4.4-driver-4.0-topology-sharded_cluster - * @group matrix-testing-exclude-server-5.0-driver-4.0-topology-sharded_cluster - */ + #[Group('matrix-testing-exclude-server-4.2-driver-4.0-topology-sharded_cluster')] + #[Group('matrix-testing-exclude-server-4.4-driver-4.0-topology-sharded_cluster')] + #[Group('matrix-testing-exclude-server-5.0-driver-4.0-topology-sharded_cluster')] public function testModifyCollection(): void { $this->database->createCollection($this->getCollectionName()); diff --git a/tests/DocumentationExamplesTest.php b/tests/DocumentationExamplesTest.php index a8adedffe..c5876bebe 100644 --- a/tests/DocumentationExamplesTest.php +++ b/tests/DocumentationExamplesTest.php @@ -12,6 +12,8 @@ use MongoDB\Driver\Exception\Exception; use MongoDB\Driver\ReadPreference; use MongoDB\Tests\SpecTests\ClientSideEncryptionSpecTest; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; +use PHPUnit\Framework\Attributes\Group; use function base64_decode; use function in_array; @@ -1009,7 +1011,7 @@ public function testExample_55_58(): void $this->assertInventoryCount(0); } - /** @group matrix-testing-exclude-server-5.0-driver-4.0-topology-sharded_cluster */ + #[Group('matrix-testing-exclude-server-5.0-driver-4.0-topology-sharded_cluster')] public function testChangeStreamExample_1_4(): void { $this->skipIfChangeStreamIsNotSupported(); @@ -1715,7 +1717,7 @@ public function testSnapshotQueries(): void $this->assertSame(1, $totalDailySales); } - /** @doesNotPerformAssertions */ + #[DoesNotPerformAssertions] public function testVersionedApi(): void { $uriString = static::getUri(true); @@ -1800,7 +1802,7 @@ public function testVersionedApiMigration(): void // phpcs:enable } - /** @doesNotPerformAssertions */ + #[DoesNotPerformAssertions] public function testWithTransactionExample(): void { $this->skipIfTransactionsAreNotSupported(); diff --git a/tests/ExamplesTest.php b/tests/ExamplesTest.php index 7946622f4..6f26a5f46 100644 --- a/tests/ExamplesTest.php +++ b/tests/ExamplesTest.php @@ -3,6 +3,9 @@ namespace MongoDB\Tests; use Generator; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses; use function bin2hex; use function getenv; @@ -10,7 +13,7 @@ use function random_bytes; use function sprintf; -/** @runTestsInSeparateProcesses */ +#[RunTestsInSeparateProcesses] final class ExamplesTest extends FunctionalTestCase { public function setUp(): void @@ -26,7 +29,7 @@ public function setUp(): void } } - /** @dataProvider provideExamples */ + #[DataProvider('provideExamples')] public function testExamples(string $file, string $expectedOutput): void { $this->assertExampleOutput($file, $expectedOutput); @@ -223,9 +226,8 @@ public static function provideExamples(): Generator /** * MongoDB Atlas Search example requires a MongoDB Atlas M10+ cluster with MongoDB 7.0+ * Tips for insiders: if using a cloud-dev server, append ".mongodb.net" to the MONGODB_URI. - * - * @group atlas */ + #[Group('atlas')] public function testAtlasSearch(): void { $uri = getenv('MONGODB_URI') ?? ''; diff --git a/tests/Exception/InvalidArgumentExceptionTest.php b/tests/Exception/InvalidArgumentExceptionTest.php index a7a13d40a..95ce89061 100644 --- a/tests/Exception/InvalidArgumentExceptionTest.php +++ b/tests/Exception/InvalidArgumentExceptionTest.php @@ -5,10 +5,11 @@ use AssertionError; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class InvalidArgumentExceptionTest extends TestCase { - /** @dataProvider provideExpectedTypes */ + #[DataProvider('provideExpectedTypes')] public function testExpectedTypeFormatting($expectedType, $typeString): void { $e = InvalidArgumentException::invalidType('$arg', null, $expectedType); diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index cdff7ca48..07c03e45e 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -17,6 +17,7 @@ use MongoDB\Operation\DatabaseCommand; use MongoDB\Operation\ListCollections; use stdClass; +use Throwable; use UnexpectedValueException; use function array_intersect_key; @@ -69,12 +70,15 @@ public function setUp(): void $this->configuredFailPoints = []; } - public function tearDown(): void + protected function onNotSuccessfulTest(Throwable $t): never { - if (! $this->hasFailed()) { - $this->cleanupCollections(); - } + $this->cleanupCollections(); + + throw $t; + } + public function tearDown(): void + { $this->disableFailPoints(); parent::tearDown(); diff --git a/tests/Functions/SelectServerFunctionalTest.php b/tests/Functions/SelectServerFunctionalTest.php index 0a3b82a6d..980541b35 100644 --- a/tests/Functions/SelectServerFunctionalTest.php +++ b/tests/Functions/SelectServerFunctionalTest.php @@ -5,12 +5,13 @@ use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Server; use MongoDB\Tests\FunctionalTestCase; +use PHPUnit\Framework\Attributes\DataProvider; use function MongoDB\select_server; class SelectServerFunctionalTest extends FunctionalTestCase { - /** @dataProvider providePinnedOptions */ + #[DataProvider('providePinnedOptions')] public function testSelectServerPrefersPinnedServer(array $options): void { $this->skipIfTransactionsAreNotSupported(); diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index cab0b529a..999c33247 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -9,6 +9,7 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Model\BSONArray; use MongoDB\Model\BSONDocument; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; use function MongoDB\apply_type_map_to_document; @@ -26,7 +27,7 @@ */ class FunctionsTest extends TestCase { - /** @dataProvider provideDocumentAndTypeMap */ + #[DataProvider('provideDocumentAndTypeMap')] public function testApplyTypeMapToDocument($document, array $typeMap, $expectedDocument): void { $this->assertEquals($expectedDocument, apply_type_map_to_document($document, $typeMap)); @@ -96,7 +97,7 @@ public static function provideDocumentAndTypeMap() ]; } - /** @dataProvider provideDocumentsAndExpectedArrays */ + #[DataProvider('provideDocumentsAndExpectedArrays')] public function testDocumentToArray($document, array $expectedArray): void { $this->assertSame($expectedArray, document_to_array($document)); @@ -115,7 +116,7 @@ public static function provideDocumentsAndExpectedArrays(): array ]; } - /** @dataProvider provideInvalidDocumentValuesForChecks */ + #[DataProvider('provideInvalidDocumentValuesForChecks')] public function testDocumentToArrayArgumentTypeCheck($document): void { $this->expectException(TypeError::class); @@ -142,7 +143,7 @@ public static function provideDocumentCasts(): array // phpcs:enable } - /** @dataProvider provideDocumentCasts */ + #[DataProvider('provideDocumentCasts')] public function testIsFirstKeyOperator(callable $cast): void { $this->assertFalse(is_first_key_operator($cast(['y' => 1]))); @@ -153,14 +154,14 @@ public function testIsFirstKeyOperator(callable $cast): void $this->assertFalse(is_first_key_operator($cast(['foo']))); } - /** @dataProvider provideInvalidDocumentValuesForChecks */ + #[DataProvider('provideInvalidDocumentValuesForChecks')] public function testIsFirstKeyOperatorArgumentTypeCheck($document): void { $this->expectException(TypeError::class); is_first_key_operator($document); } - /** @dataProvider provideDocumentCasts */ + #[DataProvider('provideDocumentCasts')] public function testIsMapReduceOutputInlineWithDocumentValues(callable $cast): void { $this->assertTrue(is_mapreduce_output_inline($cast(['inline' => 1]))); @@ -174,7 +175,7 @@ public function testIsMapReduceOutputInlineWithStringValue(): void $this->assertFalse(is_mapreduce_output_inline('collectionName')); } - /** @dataProvider provideTypeMapValues */ + #[DataProvider('provideTypeMapValues')] public function testCreateFieldPathTypeMap(array $expected, array $typeMap, $fieldPath = 'field'): void { $this->assertEquals($expected, create_field_path_type_map($typeMap, $fieldPath)); @@ -234,7 +235,7 @@ public static function provideTypeMapValues() ]; } - /** @dataProvider provideDocumentCasts */ + #[DataProvider('provideDocumentCasts')] public function testIsLastPipelineOperatorWrite(callable $cast): void { $match = ['$match' => ['x' => 1]]; @@ -250,7 +251,7 @@ public function testIsLastPipelineOperatorWrite(callable $cast): void $this->assertFalse(is_last_pipeline_operator_write([$cast($out), $cast($match)])); } - /** @dataProvider providePipelines */ + #[DataProvider('providePipelines')] public function testIsPipeline($expected, $pipeline, $allowEmpty = false): void { $this->assertSame($expected, is_pipeline($pipeline, $allowEmpty)); @@ -314,7 +315,7 @@ public static function providePipelines(): array ]; } - /** @dataProvider provideStagePipelines */ + #[DataProvider('provideStagePipelines')] public function testIsBuilderPipeline($expected, $pipeline): void { $this->assertSame($expected, is_builder_pipeline($pipeline)); @@ -329,7 +330,7 @@ public static function provideStagePipelines(): iterable yield 'stages and operators' => [true, [new MatchStage([]), ['$limit' => 1]]]; } - /** @dataProvider provideWriteConcerns */ + #[DataProvider('provideWriteConcerns')] public function testIsWriteConcernAcknowledged($expected, WriteConcern $writeConcern): void { $this->assertSame($expected, is_write_concern_acknowledged($writeConcern)); diff --git a/tests/GridFS/BucketFunctionalTest.php b/tests/GridFS/BucketFunctionalTest.php index 163c458c2..1c239873c 100644 --- a/tests/GridFS/BucketFunctionalTest.php +++ b/tests/GridFS/BucketFunctionalTest.php @@ -20,6 +20,8 @@ use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; use MongoDB\Tests\Fixtures\Codec\TestFileCodec; use MongoDB\Tests\Fixtures\Document\TestFile; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use ReflectionMethod; use stdClass; @@ -51,7 +53,7 @@ */ class BucketFunctionalTest extends FunctionalTestCase { - /** @doesNotPerformAssertions */ + #[DoesNotPerformAssertions] public function testValidConstructorOptions(): void { new Bucket($this->manager, $this->getDatabaseName(), [ @@ -64,7 +66,7 @@ public function testValidConstructorOptions(): void ]); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); @@ -103,7 +105,7 @@ public function testConstructorWithCodecAndTypeMapOptions(): void new Bucket($this->manager, $this->getDatabaseName(), $options); } - /** @dataProvider provideInputDataAndExpectedChunks */ + #[DataProvider('provideInputDataAndExpectedChunks')] public function testDelete($input, $expectedChunks): void { $id = $this->bucket->uploadFromStream('filename', self::createStream($input)); @@ -139,7 +141,7 @@ public function testDeleteShouldRequireFileToExist(): void $this->bucket->delete('nonexistent-id'); } - /** @dataProvider provideInputDataAndExpectedChunks */ + #[DataProvider('provideInputDataAndExpectedChunks')] public function testDeleteStillRemovesChunksIfFileDoesNotExist($input, $expectedChunks): void { $id = $this->bucket->uploadFromStream('filename', self::createStream($input)); @@ -197,7 +199,7 @@ public function testDownloadingFileWithUnexpectedChunkSize(): void stream_get_contents($this->bucket->openDownloadStream($id)); } - /** @dataProvider provideInputDataAndExpectedChunks */ + #[DataProvider('provideInputDataAndExpectedChunks')] public function testDownloadToStream($input): void { $id = $this->bucket->uploadFromStream('filename', self::createStream($input)); @@ -207,7 +209,7 @@ public function testDownloadToStream($input): void $this->assertStreamContents($input, $destination); } - /** @dataProvider provideInvalidStreamValues */ + #[DataProvider('provideInvalidStreamValues')] public function testDownloadToStreamShouldRequireDestinationStream($destination): void { $this->expectException(InvalidArgumentException::class); @@ -260,14 +262,14 @@ public function testDownloadToStreamByName(): void $this->assertStreamContents('baz', $destination); } - /** @dataProvider provideInvalidStreamValues */ + #[DataProvider('provideInvalidStreamValues')] public function testDownloadToStreamByNameShouldRequireDestinationStream($destination): void { $this->expectException(InvalidArgumentException::class); $this->bucket->downloadToStreamByName('filename', $destination); } - /** @dataProvider provideNonexistentFilenameAndRevision */ + #[DataProvider('provideNonexistentFilenameAndRevision')] public function testDownloadToStreamByNameShouldRequireFilenameAndRevisionToExist($filename, $revision): void { $this->bucket->uploadFromStream('filename', self::createStream('foo')); @@ -543,7 +545,7 @@ public function testGetFileDocumentForStreamWithWritableStream(): void $this->assertSameDocument($metadata, $fileDocument->metadata); } - /** @dataProvider provideInvalidGridFSStreamValues */ + #[DataProvider('provideInvalidGridFSStreamValues')] public function testGetFileDocumentForStreamShouldRequireGridFSStreamResource($stream): void { $this->expectException(InvalidArgumentException::class); @@ -580,7 +582,7 @@ public function testGetFileIdForStreamWithWritableStream(): void $this->assertEquals(1, $this->bucket->getFileIdForStream($stream)); } - /** @dataProvider provideInvalidGridFSStreamValues */ + #[DataProvider('provideInvalidGridFSStreamValues')] public function testGetFileIdForStreamShouldRequireGridFSStreamResource($stream): void { $this->expectException(InvalidArgumentException::class); @@ -595,7 +597,7 @@ public function testGetFilesCollection(): void $this->assertEquals('fs.files', $filesCollection->getCollectionName()); } - /** @dataProvider provideInputDataAndExpectedChunks */ + #[DataProvider('provideInputDataAndExpectedChunks')] public function testOpenDownloadStream($input): void { $id = $this->bucket->uploadFromStream('filename', self::createStream($input)); @@ -603,7 +605,7 @@ public function testOpenDownloadStream($input): void $this->assertStreamContents($input, $this->bucket->openDownloadStream($id)); } - /** @dataProvider provideInputDataAndExpectedChunks */ + #[DataProvider('provideInputDataAndExpectedChunks')] public function testOpenDownloadStreamAndMultipleReadOperations($input): void { $id = $this->bucket->uploadFromStream('filename', self::createStream($input)); @@ -649,7 +651,7 @@ public function testOpenDownloadStreamByName(): void $this->assertStreamContents('baz', $this->bucket->openDownloadStreamByName('filename', ['revision' => 2])); } - /** @dataProvider provideNonexistentFilenameAndRevision */ + #[DataProvider('provideNonexistentFilenameAndRevision')] public function testOpenDownloadStreamByNameShouldRequireFilenameAndRevisionToExist($filename, $revision): void { $this->bucket->uploadFromStream('filename', self::createStream('foo')); @@ -669,7 +671,7 @@ public function testOpenUploadStream(): void $this->assertStreamContents('foobar', $this->bucket->openDownloadStreamByName('filename')); } - /** @dataProvider provideInputDataAndExpectedChunks */ + #[DataProvider('provideInputDataAndExpectedChunks')] public function testOpenUploadStreamAndMultipleWriteOperations($input): void { $stream = $this->bucket->openUploadStream('filename'); @@ -740,7 +742,7 @@ public function testUploadFromStream(): void $this->assertSameDocument(['foo' => 'bar'], $fileDocument['metadata']); } - /** @dataProvider provideInvalidStreamValues */ + #[DataProvider('provideInvalidStreamValues')] public function testUploadFromStreamShouldRequireSourceStream($source): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/GridFS/FunctionalTestCase.php b/tests/GridFS/FunctionalTestCase.php index 785e1c909..ad2ca614a 100644 --- a/tests/GridFS/FunctionalTestCase.php +++ b/tests/GridFS/FunctionalTestCase.php @@ -6,6 +6,8 @@ use MongoDB\GridFS\Bucket; use MongoDB\Operation\DropCollection; use MongoDB\Tests\FunctionalTestCase as BaseFunctionalTestCase; +use PHPUnit\Framework\Attributes\AfterClass; +use PHPUnit\Framework\Attributes\BeforeClass; use function fopen; use function fwrite; @@ -46,10 +48,9 @@ public function tearDown(): void * The bucket's collections are created by the first test that runs and * kept for all subsequent tests. This is done to avoid creating the * collections and their indexes for each test, which would be slow. - * - * @beforeClass - * @afterClass */ + #[BeforeClass] + #[AfterClass] public static function dropCollectionsBeforeAfterClass(): void { $manager = static::createTestManager(); diff --git a/tests/GridFS/ReadableStreamFunctionalTest.php b/tests/GridFS/ReadableStreamFunctionalTest.php index 600181fbb..3a14af698 100644 --- a/tests/GridFS/ReadableStreamFunctionalTest.php +++ b/tests/GridFS/ReadableStreamFunctionalTest.php @@ -8,6 +8,7 @@ use MongoDB\GridFS\Exception\CorruptFileException; use MongoDB\GridFS\ReadableStream; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; use function array_filter; @@ -50,7 +51,7 @@ public function testGetFile(): void $this->assertSame($fileDocument, $stream->getFile()); } - /** @dataProvider provideInvalidConstructorFileDocuments */ + #[DataProvider('provideInvalidConstructorFileDocuments')] public function testConstructorFileDocumentChecks($file): void { $this->expectException(CorruptFileException::class); @@ -76,7 +77,7 @@ public static function provideInvalidConstructorFileDocuments() return $options; } - /** @dataProvider provideFileIdAndExpectedBytes */ + #[DataProvider('provideFileIdAndExpectedBytes')] public function testReadBytes($fileId, $length, $expectedBytes): void { $fileDocument = $this->collectionWrapper->findFileById($fileId); @@ -119,7 +120,7 @@ public static function provideFilteredFileIdAndExpectedBytes() ); } - /** @dataProvider provideFilteredFileIdAndExpectedBytes */ + #[DataProvider('provideFilteredFileIdAndExpectedBytes')] public function testReadBytesCalledMultipleTimes($fileId, $length, $expectedBytes): void { $fileDocument = $this->collectionWrapper->findFileById($fileId); @@ -197,7 +198,7 @@ public function testSeekOutOfRange(): void $stream->seek(11); } - /** @dataProvider providePreviousChunkSeekOffsetAndBytes */ + #[DataProvider('providePreviousChunkSeekOffsetAndBytes')] public function testSeekPreviousChunk($offset, $length, $expectedBytes): void { $fileDocument = $this->collectionWrapper->findFileById('length-10'); @@ -231,7 +232,7 @@ public static function providePreviousChunkSeekOffsetAndBytes() ]; } - /** @dataProvider provideSameChunkSeekOffsetAndBytes */ + #[DataProvider('provideSameChunkSeekOffsetAndBytes')] public function testSeekSameChunk($offset, $length, $expectedBytes): void { $fileDocument = $this->collectionWrapper->findFileById('length-10'); @@ -263,7 +264,7 @@ public static function provideSameChunkSeekOffsetAndBytes() ]; } - /** @dataProvider provideSubsequentChunkSeekOffsetAndBytes */ + #[DataProvider('provideSubsequentChunkSeekOffsetAndBytes')] public function testSeekSubsequentChunk($offset, $length, $expectedBytes): void { $fileDocument = $this->collectionWrapper->findFileById('length-10'); diff --git a/tests/GridFS/StreamWrapperFunctionalTest.php b/tests/GridFS/StreamWrapperFunctionalTest.php index fc8c45523..1960e5518 100644 --- a/tests/GridFS/StreamWrapperFunctionalTest.php +++ b/tests/GridFS/StreamWrapperFunctionalTest.php @@ -7,6 +7,7 @@ use MongoDB\GridFS\Exception\FileNotFoundException; use MongoDB\GridFS\Exception\LogicException; use MongoDB\GridFS\StreamWrapper; +use PHPUnit\Framework\Attributes\DataProvider; use function copy; use function fclose; @@ -232,7 +233,7 @@ public function testWritableStreamWrite(): void $this->assertSame(6, fwrite($stream, 'foobar')); } - /** @dataProvider provideUrl */ + #[DataProvider('provideUrl')] public function testStreamWithContextResolver(string $url, string $expectedFilename): void { $this->bucket->registerGlobalStreamWrapperAlias('bucket'); diff --git a/tests/GridFS/WritableStreamFunctionalTest.php b/tests/GridFS/WritableStreamFunctionalTest.php index a46741fae..98b654b8a 100644 --- a/tests/GridFS/WritableStreamFunctionalTest.php +++ b/tests/GridFS/WritableStreamFunctionalTest.php @@ -5,6 +5,8 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\GridFS\CollectionWrapper; use MongoDB\GridFS\WritableStream; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use function str_repeat; @@ -22,7 +24,7 @@ public function setUp(): void $this->collectionWrapper = new CollectionWrapper($this->manager, $this->getDatabaseName(), 'fs'); } - /** @doesNotPerformAssertions */ + #[DoesNotPerformAssertions] public function testValidConstructorOptions(): void { new WritableStream($this->collectionWrapper, 'filename', [ @@ -32,7 +34,7 @@ public function testValidConstructorOptions(): void ]); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); @@ -71,7 +73,7 @@ public function testWriteBytesAlwaysUpdatesFileSize(): void $this->assertSame(1536, $stream->getSize()); } - /** @dataProvider provideInputDataAndExpectedMD5 */ + #[DataProvider('provideInputDataAndExpectedMD5')] public function testWriteBytesCalculatesMD5($input, $expectedMD5): void { $stream = new WritableStream($this->collectionWrapper, 'filename'); diff --git a/tests/LogNonGenuineHostTest.php b/tests/LogNonGenuineHostTest.php index 4991b2339..2de5e2d95 100644 --- a/tests/LogNonGenuineHostTest.php +++ b/tests/LogNonGenuineHostTest.php @@ -4,6 +4,7 @@ use MongoDB\Client; use MongoDB\Driver\Exception\InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; use Psr\Log\AbstractLogger; use Psr\Log\LoggerInterface; @@ -28,7 +29,7 @@ public function tearDown(): void remove_logger($this->logger); } - /** @dataProvider provideCosmosUris */ + #[DataProvider('provideCosmosUris')] public function testCosmosUriLogsInfoMessage(string $uri): void { $this->createClientAndIgnoreSrvLookupError($uri); @@ -54,7 +55,7 @@ public static function provideCosmosUris(): array ]; } - /** @dataProvider provideDocumentDbUris */ + #[DataProvider('provideDocumentDbUris')] public function testDocumentDbUriLogsInfoMessage(string $uri): void { $this->createClientAndIgnoreSrvLookupError($uri); @@ -83,7 +84,7 @@ public static function provideDocumentDbUris(): array ]; } - /** @dataProvider provideGenuineUris */ + #[DataProvider('provideGenuineUris')] public function testGenuineUriDoesNotLog(string $uri): void { $this->createClientAndIgnoreSrvLookupError($uri); diff --git a/tests/Model/BSONIteratorTest.php b/tests/Model/BSONIteratorTest.php index 3ec945e39..749194c5d 100644 --- a/tests/Model/BSONIteratorTest.php +++ b/tests/Model/BSONIteratorTest.php @@ -7,6 +7,7 @@ use MongoDB\Exception\UnexpectedValueException; use MongoDB\Model\BSONIterator; use MongoDB\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use function array_map; use function implode; @@ -15,7 +16,7 @@ class BSONIteratorTest extends TestCase { - /** @dataProvider provideTypeMapOptionsAndExpectedDocuments */ + #[DataProvider('provideTypeMapOptionsAndExpectedDocuments')] public function testValidValues(?array $typeMap, array $expectedDocuments): void { $binaryString = implode(array_map( diff --git a/tests/Model/CallbackIteratorTest.php b/tests/Model/CallbackIteratorTest.php index 9f71415f7..e38a26c4f 100644 --- a/tests/Model/CallbackIteratorTest.php +++ b/tests/Model/CallbackIteratorTest.php @@ -8,12 +8,13 @@ use IteratorAggregate; use MongoDB\Model\CallbackIterator; use MongoDB\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use function iterator_to_array; class CallbackIteratorTest extends TestCase { - /** @dataProvider provideTests */ + #[DataProvider('provideTests')] public function testIteration($expected, $source, $callback): void { $callbackIterator = new CallbackIterator($source, $callback); diff --git a/tests/Model/ChangeStreamIteratorTest.php b/tests/Model/ChangeStreamIteratorTest.php index caba4726c..84b4390fb 100644 --- a/tests/Model/ChangeStreamIteratorTest.php +++ b/tests/Model/ChangeStreamIteratorTest.php @@ -15,6 +15,7 @@ use MongoDB\Operation\Find; use MongoDB\Tests\CommandObserver; use MongoDB\Tests\FunctionalTestCase; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; use function sprintf; @@ -31,7 +32,7 @@ public function setUp(): void $this->collection = $this->createCollection($this->getDatabaseName(), $this->getCollectionName(), ['capped' => true, 'size' => 8192]); } - /** @dataProvider provideInvalidIntegerValues */ + #[DataProvider('provideInvalidIntegerValues')] public function testFirstBatchArgumentTypeCheck($firstBatchSize): void { $this->expectException(TypeError::class); @@ -50,14 +51,14 @@ public function testInitialResumeToken(): void $this->assertSameDocument((object) ['resumeToken' => 2], $iterator->getResumeToken()); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testInitialResumeTokenArgumentTypeCheck($initialResumeToken): void { $this->expectException($initialResumeToken instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new ChangeStreamIterator($this->collection->find(), 0, $initialResumeToken, null); } - /** @dataProvider provideInvalidObjectValues */ + #[DataProvider('provideInvalidObjectValues')] public function testPostBatchResumeTokenArgumentTypeCheck($postBatchResumeToken): void { $this->expectException(TypeError::class); diff --git a/tests/Model/CodecCursorFunctionalTest.php b/tests/Model/CodecCursorFunctionalTest.php index 432d71732..d2662a138 100644 --- a/tests/Model/CodecCursorFunctionalTest.php +++ b/tests/Model/CodecCursorFunctionalTest.php @@ -13,6 +13,7 @@ use const E_DEPRECATED; use const E_USER_DEPRECATED; +use const E_USER_WARNING; class CodecCursorFunctionalTest extends FunctionalTestCase { @@ -30,10 +31,7 @@ public function testSetTypeMap(): void $codecCursor = CodecCursor::fromCursor($cursor, $this->createMock(DocumentCodec::class)); - $this->expectWarning(); - $this->expectWarningMessage('Discarding type map for MongoDB\Model\CodecCursor::setTypeMap'); - - $codecCursor->setTypeMap(['root' => 'array']); + $this->assertError(E_USER_WARNING, fn () => $codecCursor->setTypeMap(['root' => 'array'])); } public function testGetIdReturnTypeWithoutArgument(): void diff --git a/tests/Model/IndexInfoFunctionalTest.php b/tests/Model/IndexInfoFunctionalTest.php index 65d22259a..33537382f 100644 --- a/tests/Model/IndexInfoFunctionalTest.php +++ b/tests/Model/IndexInfoFunctionalTest.php @@ -4,6 +4,7 @@ use MongoDB\Collection; use MongoDB\Tests\FunctionalTestCase; +use PHPUnit\Framework\Attributes\Group; class IndexInfoFunctionalTest extends FunctionalTestCase { @@ -32,11 +33,9 @@ public function testIs2dSphere(): void $this->assertEquals(3, $index['2dsphereIndexVersion']); } - /** - * @group matrix-testing-exclude-server-5.0-driver-4.0 - * @group matrix-testing-exclude-server-5.0-driver-4.2 - * @group matrix-testing-exclude-server-5.0-driver-4.4 - */ + #[Group('matrix-testing-exclude-server-5.0-driver-4.0')] + #[Group('matrix-testing-exclude-server-5.0-driver-4.2')] + #[Group('matrix-testing-exclude-server-5.0-driver-4.4')] public function testIsGeoHaystack(): void { $this->skipIfGeoHaystackIndexIsNotSupported(); diff --git a/tests/Model/IndexInputTest.php b/tests/Model/IndexInputTest.php index 6b4051e13..a0a44fa1a 100644 --- a/tests/Model/IndexInputTest.php +++ b/tests/Model/IndexInputTest.php @@ -8,6 +8,7 @@ use MongoDB\Model\BSONDocument; use MongoDB\Model\IndexInput; use MongoDB\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use stdClass; class IndexInputTest extends TestCase @@ -19,7 +20,7 @@ public function testConstructorShouldRequireKey(): void new IndexInput([]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorShouldRequireKeyToBeArrayOrObject($key): void { $this->expectException(InvalidArgumentException::class); @@ -27,7 +28,7 @@ public function testConstructorShouldRequireKeyToBeArrayOrObject($key): void new IndexInput(['key' => $key]); } - /** @dataProvider provideInvalidFieldOrderValues */ + #[DataProvider('provideInvalidFieldOrderValues')] public function testConstructorShouldRequireKeyFieldOrderToBeNumericOrString($order): void { $this->expectException(InvalidArgumentException::class); @@ -40,7 +41,7 @@ public static function provideInvalidFieldOrderValues() return self::wrapValuesForDataProvider([true, [], new stdClass()]); } - /** @dataProvider provideInvalidStringValues */ + #[DataProvider('provideInvalidStringValues')] public function testConstructorShouldRequireNameToBeString($name): void { $this->expectException(InvalidArgumentException::class); @@ -48,7 +49,7 @@ public function testConstructorShouldRequireNameToBeString($name): void new IndexInput(['key' => ['x' => 1], 'name' => $name]); } - /** @dataProvider provideExpectedNameAndKey */ + #[DataProvider('provideExpectedNameAndKey')] public function testNameGeneration($expectedName, array|object $key): void { $this->assertSame($expectedName, (string) new IndexInput(['key' => $key])); diff --git a/tests/Model/SearchIndexInputTest.php b/tests/Model/SearchIndexInputTest.php index aef36c8c2..507ced5c9 100644 --- a/tests/Model/SearchIndexInputTest.php +++ b/tests/Model/SearchIndexInputTest.php @@ -6,6 +6,7 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\SearchIndexInput; use MongoDB\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class SearchIndexInputTest extends TestCase { @@ -16,7 +17,7 @@ public function testConstructorIndexDefinitionMustBeDefined(): void new SearchIndexInput([]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorIndexDefinitionMustBeADocument($definition): void { $this->expectException(InvalidArgumentException::class); @@ -24,7 +25,7 @@ public function testConstructorIndexDefinitionMustBeADocument($definition): void new SearchIndexInput(['definition' => $definition]); } - /** @dataProvider provideInvalidStringValues */ + #[DataProvider('provideInvalidStringValues')] public function testConstructorShouldRequireNameToBeString($name): void { $this->expectException(InvalidArgumentException::class); @@ -32,7 +33,7 @@ public function testConstructorShouldRequireNameToBeString($name): void new SearchIndexInput(['definition' => ['mapping' => ['dynamic' => true]], 'name' => $name]); } - /** @dataProvider provideInvalidStringValues */ + #[DataProvider('provideInvalidStringValues')] public function testConstructorShouldRequireTypeToBeString($type): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/AggregateFunctionalTest.php b/tests/Operation/AggregateFunctionalTest.php index 290446f32..2d70ea299 100644 --- a/tests/Operation/AggregateFunctionalTest.php +++ b/tests/Operation/AggregateFunctionalTest.php @@ -11,6 +11,7 @@ use MongoDB\Tests\CommandObserver; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; use MongoDB\Tests\Fixtures\Document\TestObject; +use PHPUnit\Framework\Attributes\DataProvider; use stdClass; use function array_key_exists; @@ -161,7 +162,7 @@ function (array $event): void { ); } - /** @dataProvider provideTypeMapOptionsAndExpectedDocuments */ + #[DataProvider('provideTypeMapOptionsAndExpectedDocuments')] public function testTypeMapOption(?array $typeMap, array $expectedDocuments): void { $this->createFixtures(3); diff --git a/tests/Operation/AggregateTest.php b/tests/Operation/AggregateTest.php index 237ddfc69..f9befaf36 100644 --- a/tests/Operation/AggregateTest.php +++ b/tests/Operation/AggregateTest.php @@ -7,6 +7,7 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Aggregate; +use PHPUnit\Framework\Attributes\DataProvider; class AggregateTest extends TestCase { @@ -17,7 +18,7 @@ public function testConstructorPipelineArgumentMustBeAList(): void new Aggregate($this->getDatabaseName(), $this->getCollectionName(), [1 => ['$match' => ['x' => 1]]]); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/BulkWriteFunctionalTest.php b/tests/Operation/BulkWriteFunctionalTest.php index 30213cca7..7a9cb5df9 100644 --- a/tests/Operation/BulkWriteFunctionalTest.php +++ b/tests/Operation/BulkWriteFunctionalTest.php @@ -14,6 +14,8 @@ use MongoDB\Tests\CommandObserver; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; use MongoDB\Tests\Fixtures\Document\TestObject; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; use stdClass; use function is_array; @@ -60,10 +62,8 @@ public function testInserts(): void $this->assertSameDocuments($expected, $this->collection->find()); } - /** - * @dataProvider provideDocumentsWithIds - * @dataProvider provideDocumentsWithoutIds - */ + #[DataProvider('provideDocumentsWithIds')] + #[DataProvider('provideDocumentsWithoutIds')] public function testInsertDocumentEncoding($document, stdClass $expectedDocument): void { (new CommandObserver())->observe( @@ -150,7 +150,7 @@ public function testUpdates(): void $this->assertSameDocuments($expected, $this->collection->find()); } - /** @dataProvider provideFilterDocuments */ + #[DataProvider('provideFilterDocuments')] public function testUpdateFilterDocuments($filter, stdClass $expectedFilter): void { (new CommandObserver())->observe( @@ -175,7 +175,7 @@ function (array $event) use ($expectedFilter): void { ); } - /** @dataProvider provideReplacementDocuments */ + #[DataProvider('provideReplacementDocuments')] public function testReplacementDocuments($replacement, stdClass $expectedReplacement): void { (new CommandObserver())->observe( @@ -194,10 +194,8 @@ function (array $event) use ($expectedReplacement): void { ); } - /** - * @dataProvider provideUpdateDocuments - * @dataProvider provideUpdatePipelines - */ + #[DataProvider('provideUpdateDocuments')] + #[DataProvider('provideUpdatePipelines')] public function testUpdateDocuments($update, $expectedUpdate): void { if (is_array($expectedUpdate)) { @@ -246,7 +244,7 @@ public function testDeletes(): void $this->assertSameDocuments($expected, $this->collection->find()); } - /** @dataProvider provideFilterDocuments */ + #[DataProvider('provideFilterDocuments')] public function testDeleteFilterDocuments($filter, stdClass $expectedQuery): void { (new CommandObserver())->observe( @@ -317,7 +315,7 @@ public function testUnacknowledgedWriteConcern() return $result; } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesDeletedCount(BulkWriteResult $result): void { $this->expectException(BadMethodCallException::class); @@ -325,7 +323,7 @@ public function testUnacknowledgedWriteConcernAccessesDeletedCount(BulkWriteResu $result->getDeletedCount(); } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesInsertCount(BulkWriteResult $result): void { $this->expectException(BadMethodCallException::class); @@ -333,7 +331,7 @@ public function testUnacknowledgedWriteConcernAccessesInsertCount(BulkWriteResul $result->getInsertedCount(); } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesMatchedCount(BulkWriteResult $result): void { $this->expectException(BadMethodCallException::class); @@ -341,7 +339,7 @@ public function testUnacknowledgedWriteConcernAccessesMatchedCount(BulkWriteResu $result->getMatchedCount(); } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesModifiedCount(BulkWriteResult $result): void { $this->expectException(BadMethodCallException::class); @@ -349,7 +347,7 @@ public function testUnacknowledgedWriteConcernAccessesModifiedCount(BulkWriteRes $result->getModifiedCount(); } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesUpsertedCount(BulkWriteResult $result): void { $this->expectException(BadMethodCallException::class); @@ -357,7 +355,7 @@ public function testUnacknowledgedWriteConcernAccessesUpsertedCount(BulkWriteRes $result->getUpsertedCount(); } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesUpsertedIds(BulkWriteResult $result): void { $this->expectException(BadMethodCallException::class); diff --git a/tests/Operation/BulkWriteTest.php b/tests/Operation/BulkWriteTest.php index 214305e57..d64fc351f 100644 --- a/tests/Operation/BulkWriteTest.php +++ b/tests/Operation/BulkWriteTest.php @@ -7,6 +7,8 @@ use MongoDB\Exception\UnsupportedValueException; use MongoDB\Operation\BulkWrite; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use TypeError; class BulkWriteTest extends TestCase @@ -57,7 +59,7 @@ public function testInsertOneDocumentArgumentMissing(): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testInsertOneDocumentArgumentTypeCheck($document): void { $this->expectException(InvalidArgumentException::class); @@ -88,7 +90,7 @@ public function testDeleteManyFilterArgumentMissing(): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testDeleteManyFilterArgumentTypeCheck($document): void { $this->expectException(InvalidArgumentException::class); @@ -98,7 +100,7 @@ public function testDeleteManyFilterArgumentTypeCheck($document): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testDeleteManyCollationOptionTypeCheck($collation): void { $this->expectException(InvalidArgumentException::class); @@ -117,7 +119,7 @@ public function testDeleteOneFilterArgumentMissing(): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testDeleteOneFilterArgumentTypeCheck($document): void { $this->expectException(InvalidArgumentException::class); @@ -127,7 +129,7 @@ public function testDeleteOneFilterArgumentTypeCheck($document): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testDeleteOneCollationOptionTypeCheck($collation): void { $this->expectException(InvalidArgumentException::class); @@ -146,7 +148,7 @@ public function testReplaceOneFilterArgumentMissing(): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testReplaceOneFilterArgumentTypeCheck($filter): void { $this->expectException(InvalidArgumentException::class); @@ -156,10 +158,8 @@ public function testReplaceOneFilterArgumentTypeCheck($filter): void ]); } - /** - * @dataProvider provideReplacementDocuments - * @doesNotPerformAssertions - */ + #[DataProvider('provideReplacementDocuments')] + #[DoesNotPerformAssertions] public function testReplaceOneReplacementArgument($replacement): void { new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [ @@ -176,7 +176,7 @@ public function testReplaceOneReplacementArgumentMissing(): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testReplaceOneReplacementArgumentTypeCheck($replacement): void { $this->expectException(InvalidArgumentException::class); @@ -186,7 +186,7 @@ public function testReplaceOneReplacementArgumentTypeCheck($replacement): void ]); } - /** @dataProvider provideUpdateDocuments */ + #[DataProvider('provideUpdateDocuments')] public function testReplaceOneReplacementArgumentProhibitsUpdateDocument($replacement): void { $this->expectException(InvalidArgumentException::class); @@ -196,10 +196,8 @@ public function testReplaceOneReplacementArgumentProhibitsUpdateDocument($replac ]); } - /** - * @dataProvider provideUpdatePipelines - * @dataProvider provideEmptyUpdatePipelinesExcludingArray - */ + #[DataProvider('provideUpdatePipelines')] + #[DataProvider('provideEmptyUpdatePipelinesExcludingArray')] public function testReplaceOneReplacementArgumentProhibitsUpdatePipeline($replacement): void { $this->expectException(InvalidArgumentException::class); @@ -209,7 +207,7 @@ public function testReplaceOneReplacementArgumentProhibitsUpdatePipeline($replac ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testReplaceOneCollationOptionTypeCheck($collation): void { $this->expectException(InvalidArgumentException::class); @@ -219,7 +217,7 @@ public function testReplaceOneCollationOptionTypeCheck($collation): void ]); } - /** @dataProvider provideInvalidBooleanValues */ + #[DataProvider('provideInvalidBooleanValues')] public function testReplaceOneUpsertOptionTypeCheck($upsert): void { $this->expectException(InvalidArgumentException::class); @@ -255,7 +253,7 @@ public function testUpdateManyFilterArgumentMissing(): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testUpdateManyFilterArgumentTypeCheck($filter): void { $this->expectException(InvalidArgumentException::class); @@ -265,11 +263,9 @@ public function testUpdateManyFilterArgumentTypeCheck($filter): void ]); } - /** - * @dataProvider provideUpdateDocuments - * @dataProvider provideUpdatePipelines - * @doesNotPerformAssertions - */ + #[DataProvider('provideUpdateDocuments')] + #[DataProvider('provideUpdatePipelines')] + #[DoesNotPerformAssertions] public function testUpdateManyUpdateArgument($update): void { new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [ @@ -286,7 +282,7 @@ public function testUpdateManyUpdateArgumentMissing(): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testUpdateManyUpdateArgumentTypeCheck($update): void { $this->expectException($update instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); @@ -295,10 +291,8 @@ public function testUpdateManyUpdateArgumentTypeCheck($update): void ]); } - /** - * @dataProvider provideReplacementDocuments - * @dataProvider provideEmptyUpdatePipelines - */ + #[DataProvider('provideReplacementDocuments')] + #[DataProvider('provideEmptyUpdatePipelines')] public function testUpdateManyUpdateArgumentProhibitsReplacementDocumentOrEmptyPipeline($update): void { $this->expectException(InvalidArgumentException::class); @@ -308,7 +302,7 @@ public function testUpdateManyUpdateArgumentProhibitsReplacementDocumentOrEmptyP ]); } - /** @dataProvider provideInvalidArrayValues */ + #[DataProvider('provideInvalidArrayValues')] public function testUpdateManyArrayFiltersOptionTypeCheck($arrayFilters): void { $this->expectException(InvalidArgumentException::class); @@ -318,7 +312,7 @@ public function testUpdateManyArrayFiltersOptionTypeCheck($arrayFilters): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testUpdateManyCollationOptionTypeCheck($collation): void { $this->expectException(InvalidArgumentException::class); @@ -328,7 +322,7 @@ public function testUpdateManyCollationOptionTypeCheck($collation): void ]); } - /** @dataProvider provideInvalidBooleanValues */ + #[DataProvider('provideInvalidBooleanValues')] public function testUpdateManyUpsertOptionTypeCheck($upsert): void { $this->expectException(InvalidArgumentException::class); @@ -338,11 +332,9 @@ public function testUpdateManyUpsertOptionTypeCheck($upsert): void ]); } - /** - * @dataProvider provideUpdateDocuments - * @dataProvider provideUpdatePipelines - * @doesNotPerformAssertions - */ + #[DataProvider('provideUpdateDocuments')] + #[DataProvider('provideUpdatePipelines')] + #[DoesNotPerformAssertions] public function testUpdateOneUpdateArgument($update): void { new BulkWrite($this->getDatabaseName(), $this->getCollectionName(), [ @@ -359,7 +351,7 @@ public function testUpdateOneFilterArgumentMissing(): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testUpdateOneFilterArgumentTypeCheck($filter): void { $this->expectException(InvalidArgumentException::class); @@ -378,7 +370,7 @@ public function testUpdateOneUpdateArgumentMissing(): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testUpdateOneUpdateArgumentTypeCheck($update): void { $this->expectException($update instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); @@ -387,10 +379,8 @@ public function testUpdateOneUpdateArgumentTypeCheck($update): void ]); } - /** - * @dataProvider provideReplacementDocuments - * @dataProvider provideEmptyUpdatePipelines - */ + #[DataProvider('provideReplacementDocuments')] + #[DataProvider('provideEmptyUpdatePipelines')] public function testUpdateOneUpdateArgumentProhibitsReplacementDocumentOrEmptyPipeline($update): void { $this->expectException(InvalidArgumentException::class); @@ -400,7 +390,7 @@ public function testUpdateOneUpdateArgumentProhibitsReplacementDocumentOrEmptyPi ]); } - /** @dataProvider provideInvalidArrayValues */ + #[DataProvider('provideInvalidArrayValues')] public function testUpdateOneArrayFiltersOptionTypeCheck($arrayFilters): void { $this->expectException(InvalidArgumentException::class); @@ -410,7 +400,7 @@ public function testUpdateOneArrayFiltersOptionTypeCheck($arrayFilters): void ]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testUpdateOneCollationOptionTypeCheck($collation): void { $this->expectException(InvalidArgumentException::class); @@ -420,7 +410,7 @@ public function testUpdateOneCollationOptionTypeCheck($collation): void ]); } - /** @dataProvider provideInvalidBooleanValues */ + #[DataProvider('provideInvalidBooleanValues')] public function testUpdateOneUpsertOptionTypeCheck($upsert): void { $this->expectException(InvalidArgumentException::class); @@ -430,7 +420,7 @@ public function testUpdateOneUpsertOptionTypeCheck($upsert): void ]); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/CountDocumentsFunctionalTest.php b/tests/Operation/CountDocumentsFunctionalTest.php index e3dc36a53..be6c64bbd 100644 --- a/tests/Operation/CountDocumentsFunctionalTest.php +++ b/tests/Operation/CountDocumentsFunctionalTest.php @@ -5,11 +5,12 @@ use MongoDB\Operation\CountDocuments; use MongoDB\Operation\InsertMany; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; use stdClass; class CountDocumentsFunctionalTest extends FunctionalTestCase { - /** @dataProvider provideFilterDocuments */ + #[DataProvider('provideFilterDocuments')] public function testFilterDocuments($filter, stdClass $expectedMatchStage): void { (new CommandObserver())->observe( diff --git a/tests/Operation/CountDocumentsTest.php b/tests/Operation/CountDocumentsTest.php index 65e002a46..44ee49825 100644 --- a/tests/Operation/CountDocumentsTest.php +++ b/tests/Operation/CountDocumentsTest.php @@ -5,18 +5,19 @@ use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\CountDocuments; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class CountDocumentsTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new CountDocuments($this->getDatabaseName(), $this->getCollectionName(), $filter); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/CountFunctionalTest.php b/tests/Operation/CountFunctionalTest.php index 55435ab98..58326a01a 100644 --- a/tests/Operation/CountFunctionalTest.php +++ b/tests/Operation/CountFunctionalTest.php @@ -6,11 +6,12 @@ use MongoDB\Operation\CreateIndexes; use MongoDB\Operation\InsertMany; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; use stdClass; class CountFunctionalTest extends FunctionalTestCase { - /** @dataProvider provideFilterDocuments */ + #[DataProvider('provideFilterDocuments')] public function testFilterDocuments($filter, stdClass $expectedQuery): void { (new CommandObserver())->observe( diff --git a/tests/Operation/CountTest.php b/tests/Operation/CountTest.php index 8d109ae81..b8a5d357e 100644 --- a/tests/Operation/CountTest.php +++ b/tests/Operation/CountTest.php @@ -6,18 +6,19 @@ use MongoDB\Driver\ReadConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Count; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class CountTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new Count($this->getDatabaseName(), $this->getCollectionName(), $filter); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/CreateCollectionTest.php b/tests/Operation/CreateCollectionTest.php index 530299ba4..21a280032 100644 --- a/tests/Operation/CreateCollectionTest.php +++ b/tests/Operation/CreateCollectionTest.php @@ -4,6 +4,7 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\CreateCollection; +use PHPUnit\Framework\Attributes\DataProvider; class CreateCollectionTest extends TestCase { @@ -14,7 +15,7 @@ public function testConstructorPipelineOptionMustBeAList(): void new CreateCollection($this->getDatabaseName(), $this->getCollectionName(), ['pipeline' => [1 => ['$match' => ['x' => 1]]]]); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/CreateEncryptedCollectionFunctionalTest.php b/tests/Operation/CreateEncryptedCollectionFunctionalTest.php index 4c851edff..8d4978639 100644 --- a/tests/Operation/CreateEncryptedCollectionFunctionalTest.php +++ b/tests/Operation/CreateEncryptedCollectionFunctionalTest.php @@ -10,6 +10,7 @@ use MongoDB\Model\BSONArray; use MongoDB\Model\BSONDocument; use MongoDB\Operation\CreateEncryptedCollection; +use PHPUnit\Framework\Attributes\DataProvider; use function base64_decode; use function getenv; @@ -54,7 +55,7 @@ public function setUp(): void ]); } - /** @dataProvider provideEncryptedFieldsAndFieldsIsMissing */ + #[DataProvider('provideEncryptedFieldsAndFieldsIsMissing')] public function testCreateDataKeysNopIfFieldsIsMissing($input, array $expectedOutput): void { $operation = new CreateEncryptedCollection( @@ -85,7 +86,7 @@ public static function provideEncryptedFieldsAndFieldsIsMissing(): array ]; } - /** @dataProvider provideEncryptedFieldsAndFieldsHasInvalidType */ + #[DataProvider('provideEncryptedFieldsAndFieldsHasInvalidType')] public function testCreateDataKeysNopIfFieldsHasInvalidType($input, array $expectedOutput): void { $operation = new CreateEncryptedCollection( @@ -116,7 +117,7 @@ public static function provideEncryptedFieldsAndFieldsHasInvalidType(): array ]; } - /** @dataProvider provideEncryptedFieldsElementHasInvalidType */ + #[DataProvider('provideEncryptedFieldsElementHasInvalidType')] public function testCreateDataKeysSkipsNonDocumentFields($input, array $expectedOutput): void { $operation = new CreateEncryptedCollection( @@ -171,7 +172,7 @@ public function testCreateDataKeysDoesNotModifyOriginalEncryptedFieldsOption(): $this->assertInstanceOf(Binary::class, $modifiedEncryptedFields['fields'][0]['keyId'] ?? null); } - /** @dataProvider provideEncryptedFields */ + #[DataProvider('provideEncryptedFields')] public function testEncryptedFieldsDocuments($input): void { $operation = new CreateEncryptedCollection( diff --git a/tests/Operation/CreateEncryptedCollectionTest.php b/tests/Operation/CreateEncryptedCollectionTest.php index fc38bc830..b5c8bf4ea 100644 --- a/tests/Operation/CreateEncryptedCollectionTest.php +++ b/tests/Operation/CreateEncryptedCollectionTest.php @@ -5,10 +5,11 @@ use Generator; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\CreateEncryptedCollection; +use PHPUnit\Framework\Attributes\DataProvider; class CreateEncryptedCollectionTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/CreateIndexesFunctionalTest.php b/tests/Operation/CreateIndexesFunctionalTest.php index ba79971ac..a90eaec75 100644 --- a/tests/Operation/CreateIndexesFunctionalTest.php +++ b/tests/Operation/CreateIndexesFunctionalTest.php @@ -12,6 +12,7 @@ use MongoDB\Operation\CreateIndexes; use MongoDB\Operation\ListIndexes; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; use function call_user_func; use function is_callable; @@ -79,7 +80,7 @@ public function testCreateTTLIndex(): void }); } - /** @dataProvider provideKeyCasts */ + #[DataProvider('provideKeyCasts')] public function testCreateIndexes(callable $cast): void { $expectedNames = ['x_1', 'y_-1_z_1', 'g_2dsphere_z_1', 'my_ttl']; diff --git a/tests/Operation/CreateIndexesTest.php b/tests/Operation/CreateIndexesTest.php index 2647bcc46..5db02f67f 100644 --- a/tests/Operation/CreateIndexesTest.php +++ b/tests/Operation/CreateIndexesTest.php @@ -7,6 +7,7 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONDocument; use MongoDB\Operation\CreateIndexes; +use PHPUnit\Framework\Attributes\DataProvider; use stdClass; class CreateIndexesTest extends TestCase @@ -18,7 +19,7 @@ public function testConstructorIndexesArgumentMustBeAList(): void new CreateIndexes($this->getDatabaseName(), $this->getCollectionName(), [1 => ['key' => ['x' => 1]]]); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); @@ -43,7 +44,7 @@ public function testConstructorRequiresAtLeastOneIndex(): void new CreateIndexes($this->getDatabaseName(), $this->getCollectionName(), []); } - /** @dataProvider provideInvalidIndexSpecificationTypes */ + #[DataProvider('provideInvalidIndexSpecificationTypes')] public function testConstructorRequiresIndexSpecificationsToBeAnArray($index): void { $this->expectException(InvalidArgumentException::class); @@ -63,7 +64,7 @@ public function testConstructorRequiresIndexSpecificationKey(): void new CreateIndexes($this->getDatabaseName(), $this->getCollectionName(), [[]]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorRequiresIndexSpecificationKeyToBeADocument($key): void { $this->expectException(InvalidArgumentException::class); @@ -71,7 +72,7 @@ public function testConstructorRequiresIndexSpecificationKeyToBeADocument($key): new CreateIndexes($this->getDatabaseName(), $this->getCollectionName(), [['key' => $key]]); } - /** @dataProvider provideKeyDocumentsWithInvalidOrder */ + #[DataProvider('provideKeyDocumentsWithInvalidOrder')] public function testConstructorValidatesIndexSpecificationKeyOrder($key): void { $this->expectException(InvalidArgumentException::class); @@ -91,7 +92,7 @@ public static function provideKeyDocumentsWithInvalidOrder(): Generator } } - /** @dataProvider provideInvalidStringValues */ + #[DataProvider('provideInvalidStringValues')] public function testConstructorRequiresIndexSpecificationNameToBeString($name): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/CreateSearchIndexesTest.php b/tests/Operation/CreateSearchIndexesTest.php index 27246873b..d55915048 100644 --- a/tests/Operation/CreateSearchIndexesTest.php +++ b/tests/Operation/CreateSearchIndexesTest.php @@ -4,6 +4,7 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\CreateSearchIndexes; +use PHPUnit\Framework\Attributes\DataProvider; class CreateSearchIndexesTest extends TestCase { @@ -14,7 +15,7 @@ public function testConstructorIndexesArgumentMustBeAList(): void new CreateSearchIndexes($this->getDatabaseName(), $this->getCollectionName(), [1 => ['name' => 'index name', 'definition' => ['mappings' => ['dynamic' => true]]]], []); } - /** @dataProvider provideInvalidArrayValues */ + #[DataProvider('provideInvalidArrayValues')] public function testConstructorIndexDefinitionMustBeADocument($index): void { $this->expectException(InvalidArgumentException::class); @@ -22,7 +23,7 @@ public function testConstructorIndexDefinitionMustBeADocument($index): void new CreateSearchIndexes($this->getDatabaseName(), $this->getCollectionName(), [$index], []); } - /** @dataProvider provideInvalidStringValues */ + #[DataProvider('provideInvalidStringValues')] public function testConstructorIndexNameMustBeAString($name): void { $this->expectException(InvalidArgumentException::class); @@ -30,7 +31,7 @@ public function testConstructorIndexNameMustBeAString($name): void new CreateSearchIndexes($this->getDatabaseName(), $this->getCollectionName(), [['name' => $name, 'definition' => ['mappings' => ['dynamic' => true]]]], []); } - /** @dataProvider provideInvalidStringValues */ + #[DataProvider('provideInvalidStringValues')] public function testConstructorIndexTypeMustBeAString($type): void { $this->expectException(InvalidArgumentException::class); @@ -45,7 +46,7 @@ public function testConstructorIndexDefinitionMustBeDefined(): void new CreateSearchIndexes($this->getDatabaseName(), $this->getCollectionName(), [['name' => 'index name']], []); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorIndexDefinitionMustBeAnArray($definition): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/DatabaseCommandFunctionalTest.php b/tests/Operation/DatabaseCommandFunctionalTest.php index 861302595..abed3b2c1 100644 --- a/tests/Operation/DatabaseCommandFunctionalTest.php +++ b/tests/Operation/DatabaseCommandFunctionalTest.php @@ -7,10 +7,11 @@ use MongoDB\Model\BSONDocument; use MongoDB\Operation\DatabaseCommand; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; class DatabaseCommandFunctionalTest extends FunctionalTestCase { - /** @dataProvider provideCommandDocuments */ + #[DataProvider('provideCommandDocuments')] public function testCommandDocuments($command): void { (new CommandObserver())->observe( diff --git a/tests/Operation/DatabaseCommandTest.php b/tests/Operation/DatabaseCommandTest.php index 869f0c7c3..c579ea5b2 100644 --- a/tests/Operation/DatabaseCommandTest.php +++ b/tests/Operation/DatabaseCommandTest.php @@ -5,18 +5,19 @@ use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\DatabaseCommand; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class DatabaseCommandTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorCommandArgumentTypeCheck($command): void { $this->expectException($command instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new DatabaseCommand($this->getDatabaseName(), $command); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/DeleteFunctionalTest.php b/tests/Operation/DeleteFunctionalTest.php index 7ff002f97..b501aa21b 100644 --- a/tests/Operation/DeleteFunctionalTest.php +++ b/tests/Operation/DeleteFunctionalTest.php @@ -10,6 +10,8 @@ use MongoDB\Exception\UnsupportedException; use MongoDB\Operation\Delete; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; use stdClass; class DeleteFunctionalTest extends FunctionalTestCase @@ -23,7 +25,7 @@ public function setUp(): void $this->collection = new Collection($this->manager, $this->getDatabaseName(), $this->getCollectionName()); } - /** @dataProvider provideFilterDocuments */ + #[DataProvider('provideFilterDocuments')] public function testFilterDocuments($filter, stdClass $expectedQuery): void { (new CommandObserver())->observe( @@ -133,7 +135,7 @@ public function testUnacknowledgedWriteConcern() return $result; } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesDeletedCount(DeleteResult $result): void { $this->expectException(BadMethodCallException::class); diff --git a/tests/Operation/DeleteTest.php b/tests/Operation/DeleteTest.php index 818689610..e1cd5bd74 100644 --- a/tests/Operation/DeleteTest.php +++ b/tests/Operation/DeleteTest.php @@ -11,25 +11,26 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Delete; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class DeleteTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new Delete($this->getDatabaseName(), $this->getCollectionName(), $filter, 0); } - /** @dataProvider provideInvalidIntegerValues */ + #[DataProvider('provideInvalidIntegerValues')] public function testConstructorLimitArgumentMustBeInt($limit): void { $this->expectException(TypeError::class); new Delete($this->getDatabaseName(), $this->getCollectionName(), [], $limit); } - /** @dataProvider provideInvalidLimitValues */ + #[DataProvider('provideInvalidLimitValues')] public function testConstructorLimitArgumentMustBeOneOrZero($limit): void { $this->expectException(InvalidArgumentException::class); @@ -42,7 +43,7 @@ public static function provideInvalidLimitValues() return self::wrapValuesForDataProvider([-1, 2]); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/DistinctFunctionalTest.php b/tests/Operation/DistinctFunctionalTest.php index d9a18307a..0a1d61a01 100644 --- a/tests/Operation/DistinctFunctionalTest.php +++ b/tests/Operation/DistinctFunctionalTest.php @@ -5,6 +5,7 @@ use MongoDB\Driver\BulkWrite; use MongoDB\Operation\Distinct; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; use stdClass; use function is_scalar; @@ -15,7 +16,7 @@ class DistinctFunctionalTest extends FunctionalTestCase { - /** @dataProvider provideFilterDocuments */ + #[DataProvider('provideFilterDocuments')] public function testFilterDocuments($filter, stdClass $expectedQuery): void { (new CommandObserver())->observe( @@ -75,7 +76,7 @@ function (array $event): void { ); } - /** @dataProvider provideTypeMapOptionsAndExpectedDocuments */ + #[DataProvider('provideTypeMapOptionsAndExpectedDocuments')] public function testTypeMapOption(array $typeMap, array $expectedDocuments): void { $bulkWrite = new BulkWrite(['ordered' => true]); diff --git a/tests/Operation/DistinctTest.php b/tests/Operation/DistinctTest.php index e181abb0a..ad5012068 100644 --- a/tests/Operation/DistinctTest.php +++ b/tests/Operation/DistinctTest.php @@ -7,18 +7,19 @@ use MongoDB\Driver\ReadPreference; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Distinct; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class DistinctTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', $filter); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/DropCollectionFunctionalTest.php b/tests/Operation/DropCollectionFunctionalTest.php index 602982f5c..a7d317e67 100644 --- a/tests/Operation/DropCollectionFunctionalTest.php +++ b/tests/Operation/DropCollectionFunctionalTest.php @@ -5,6 +5,7 @@ use MongoDB\Operation\DropCollection; use MongoDB\Operation\InsertOne; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\Depends; class DropCollectionFunctionalTest extends FunctionalTestCase { @@ -41,7 +42,7 @@ public function testDropExistingCollection(): void $this->assertCollectionDoesNotExist($this->getCollectionName()); } - /** @depends testDropExistingCollection */ + #[Depends('testDropExistingCollection')] public function testDropNonexistentCollection(): void { $this->assertCollectionDoesNotExist($this->getCollectionName()); diff --git a/tests/Operation/DropCollectionTest.php b/tests/Operation/DropCollectionTest.php index dda688686..19dc28bd4 100644 --- a/tests/Operation/DropCollectionTest.php +++ b/tests/Operation/DropCollectionTest.php @@ -4,10 +4,11 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\DropCollection; +use PHPUnit\Framework\Attributes\DataProvider; class DropCollectionTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/DropDatabaseFunctionalTest.php b/tests/Operation/DropDatabaseFunctionalTest.php index 628c3b0f3..0ef812a10 100644 --- a/tests/Operation/DropDatabaseFunctionalTest.php +++ b/tests/Operation/DropDatabaseFunctionalTest.php @@ -7,6 +7,7 @@ use MongoDB\Operation\InsertOne; use MongoDB\Operation\ListDatabases; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\Depends; use function sprintf; @@ -43,7 +44,7 @@ public function testDropExistingDatabase(): void $this->assertDatabaseDoesNotExist($server, $this->getDatabaseName()); } - /** @depends testDropExistingDatabase */ + #[Depends('testDropExistingDatabase')] public function testDropNonexistentDatabase(): void { $server = $this->getPrimaryServer(); diff --git a/tests/Operation/DropDatabaseTest.php b/tests/Operation/DropDatabaseTest.php index 4bdc69192..cd58e6356 100644 --- a/tests/Operation/DropDatabaseTest.php +++ b/tests/Operation/DropDatabaseTest.php @@ -4,10 +4,11 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\DropDatabase; +use PHPUnit\Framework\Attributes\DataProvider; class DropDatabaseTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/DropEncryptedCollectionTest.php b/tests/Operation/DropEncryptedCollectionTest.php index 669cc8590..a0055104c 100644 --- a/tests/Operation/DropEncryptedCollectionTest.php +++ b/tests/Operation/DropEncryptedCollectionTest.php @@ -5,10 +5,11 @@ use Generator; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\DropEncryptedCollection; +use PHPUnit\Framework\Attributes\DataProvider; class DropEncryptedCollectionTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/DropIndexesTest.php b/tests/Operation/DropIndexesTest.php index c90ca1376..cc2aff405 100644 --- a/tests/Operation/DropIndexesTest.php +++ b/tests/Operation/DropIndexesTest.php @@ -4,6 +4,7 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\DropIndexes; +use PHPUnit\Framework\Attributes\DataProvider; class DropIndexesTest extends TestCase { @@ -13,7 +14,7 @@ public function testDropIndexShouldNotAllowEmptyIndexName(): void new DropIndexes($this->getDatabaseName(), $this->getCollectionName(), ''); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/EstimatedDocumentCountTest.php b/tests/Operation/EstimatedDocumentCountTest.php index f1b4c9211..c530d0e2d 100644 --- a/tests/Operation/EstimatedDocumentCountTest.php +++ b/tests/Operation/EstimatedDocumentCountTest.php @@ -4,10 +4,11 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\EstimatedDocumentCount; +use PHPUnit\Framework\Attributes\DataProvider; class EstimatedDocumentCountTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/ExplainFunctionalTest.php b/tests/Operation/ExplainFunctionalTest.php index 486ee4095..e40024bff 100644 --- a/tests/Operation/ExplainFunctionalTest.php +++ b/tests/Operation/ExplainFunctionalTest.php @@ -20,13 +20,14 @@ use MongoDB\Operation\UpdateMany; use MongoDB\Operation\UpdateOne; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; use function array_key_exists; use function array_key_first; class ExplainFunctionalTest extends FunctionalTestCase { - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testCount($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $this->createFixtures(3); @@ -39,7 +40,7 @@ public function testCount($verbosity, $executionStatsExpected, $allPlansExecutio $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testDelete($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $this->createFixtures(3); @@ -54,7 +55,7 @@ public function testDelete($verbosity, $executionStatsExpected, $allPlansExecuti $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testDeleteMany($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $this->createFixtures(3); @@ -69,7 +70,7 @@ public function testDeleteMany($verbosity, $executionStatsExpected, $allPlansExe $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testDeleteOne($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $this->createFixtures(3); @@ -84,7 +85,7 @@ public function testDeleteOne($verbosity, $executionStatsExpected, $allPlansExec $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testDistinct($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $operation = new Distinct($this->getDatabaseName(), $this->getCollectionName(), 'x', []); @@ -95,7 +96,7 @@ public function testDistinct($verbosity, $executionStatsExpected, $allPlansExecu $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testFindAndModify($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $operation = new FindAndModify($this->getDatabaseName(), $this->getCollectionName(), ['remove' => true]); @@ -106,7 +107,7 @@ public function testFindAndModify($verbosity, $executionStatsExpected, $allPlans $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testFind($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $this->createFixtures(3); @@ -181,7 +182,7 @@ function (array $event): void { ); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testFindOne($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $this->createFixtures(1); @@ -194,7 +195,7 @@ public function testFindOne($verbosity, $executionStatsExpected, $allPlansExecut $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testFindOneAndDelete($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $operation = new FindOneAndDelete($this->getDatabaseName(), $this->getCollectionName(), []); @@ -205,7 +206,7 @@ public function testFindOneAndDelete($verbosity, $executionStatsExpected, $allPl $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testFindOneAndReplace($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $operation = new FindOneAndReplace($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1.1], ['x' => 5]); @@ -216,7 +217,7 @@ public function testFindOneAndReplace($verbosity, $executionStatsExpected, $allP $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testFindOneAndUpdate($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $operation = new FindOneAndUpdate($this->getDatabaseName(), $this->getCollectionName(), [], ['$rename' => ['x' => 'y']]); @@ -227,7 +228,7 @@ public function testFindOneAndUpdate($verbosity, $executionStatsExpected, $allPl $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testUpdate($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $this->createFixtures(3); @@ -296,7 +297,7 @@ function (array $event): void { ); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testUpdateMany($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $this->createFixtures(3); @@ -312,7 +313,7 @@ public function testUpdateMany($verbosity, $executionStatsExpected, $allPlansExe $this->assertExplainResult($result, $executionStatsExpected, $allPlansExecutionExpected); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testUpdateOne($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $this->createFixtures(3); @@ -344,7 +345,7 @@ public function testAggregate(): void $this->assertExplainResult($result, false, false); } - /** @dataProvider provideVerbosityInformation */ + #[DataProvider('provideVerbosityInformation')] public function testAggregateOptimizedToQuery($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { $this->skipIfServerVersion('<', '4.2.0', 'MongoDB < 4.2 does not optimize simple aggregation pipelines'); diff --git a/tests/Operation/ExplainTest.php b/tests/Operation/ExplainTest.php index 98937a1ca..1255d787c 100644 --- a/tests/Operation/ExplainTest.php +++ b/tests/Operation/ExplainTest.php @@ -5,10 +5,11 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Explain; use MongoDB\Operation\Explainable; +use PHPUnit\Framework\Attributes\DataProvider; class ExplainTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $explainable = $this->getMockBuilder(Explainable::class)->getMock(); diff --git a/tests/Operation/FindAndModifyFunctionalTest.php b/tests/Operation/FindAndModifyFunctionalTest.php index c16413218..576b0474c 100644 --- a/tests/Operation/FindAndModifyFunctionalTest.php +++ b/tests/Operation/FindAndModifyFunctionalTest.php @@ -12,11 +12,12 @@ use MongoDB\Tests\CommandObserver; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; use MongoDB\Tests\Fixtures\Document\TestObject; +use PHPUnit\Framework\Attributes\DataProvider; use stdClass; class FindAndModifyFunctionalTest extends FunctionalTestCase { - /** @dataProvider provideQueryDocuments */ + #[DataProvider('provideQueryDocuments')] public function testQueryDocuments($query, stdClass $expectedQuery): void { (new CommandObserver())->observe( @@ -47,12 +48,10 @@ public static function provideQueryDocuments(): array ]; } - /** - * @dataProvider provideReplacementDocuments - * @dataProvider provideUpdateDocuments - * @dataProvider provideUpdatePipelines - * @dataProvider provideReplacementDocumentLikePipeline - */ + #[DataProvider('provideReplacementDocuments')] + #[DataProvider('provideUpdateDocuments')] + #[DataProvider('provideUpdatePipelines')] + #[DataProvider('provideReplacementDocumentLikePipeline')] public function testUpdateDocuments($update, $expectedUpdate): void { (new CommandObserver())->observe( @@ -233,7 +232,7 @@ function (array $event): void { ); } - /** @dataProvider provideTypeMapOptionsAndExpectedDocument */ + #[DataProvider('provideTypeMapOptionsAndExpectedDocument')] public function testTypeMapOption(?array $typeMap, $expectedDocument): void { $this->createFixtures(1); diff --git a/tests/Operation/FindAndModifyTest.php b/tests/Operation/FindAndModifyTest.php index a6049ed78..7abc5bfb9 100644 --- a/tests/Operation/FindAndModifyTest.php +++ b/tests/Operation/FindAndModifyTest.php @@ -5,10 +5,11 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\FindAndModify; +use PHPUnit\Framework\Attributes\DataProvider; class FindAndModifyTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/FindFunctionalTest.php b/tests/Operation/FindFunctionalTest.php index 2a64a7d71..81745c23f 100644 --- a/tests/Operation/FindFunctionalTest.php +++ b/tests/Operation/FindFunctionalTest.php @@ -11,13 +11,15 @@ use MongoDB\Tests\CommandObserver; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; use MongoDB\Tests\Fixtures\Document\TestObject; +use PHPUnit\Framework\Attributes\DataProvider; use stdClass; +use function is_array; use function microtime; class FindFunctionalTest extends FunctionalTestCase { - /** @dataProvider provideFilterDocuments */ + #[DataProvider('provideFilterDocuments')] public function testFilterDocuments($filter, stdClass $expectedQuery): void { (new CommandObserver())->observe( @@ -36,11 +38,16 @@ function (array $event) use ($expectedQuery): void { ); } - /** @dataProvider provideModifierDocuments */ + #[DataProvider('provideModifierDocuments')] public function testModifierDocuments($modifiers, stdClass $expectedSort): void { (new CommandObserver())->observe( function () use ($modifiers): void { + // @todo revert this lines after PHPC-2457 + if (is_array($modifiers)) { + $modifiers = [...$modifiers]; + } + $operation = new Find( $this->getDatabaseName(), $this->getCollectionName(), @@ -159,7 +166,7 @@ function (array $event): void { ); } - /** @dataProvider provideTypeMapOptionsAndExpectedDocuments */ + #[DataProvider('provideTypeMapOptionsAndExpectedDocuments')] public function testTypeMapOption(array $typeMap, array $expectedDocuments): void { $this->createFixtures(3); diff --git a/tests/Operation/FindOneAndDeleteTest.php b/tests/Operation/FindOneAndDeleteTest.php index 76f18bda4..9f4cefd1f 100644 --- a/tests/Operation/FindOneAndDeleteTest.php +++ b/tests/Operation/FindOneAndDeleteTest.php @@ -6,18 +6,19 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\FindOneAndDelete; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class FindOneAndDeleteTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new FindOneAndDelete($this->getDatabaseName(), $this->getCollectionName(), $filter); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/FindOneAndReplaceTest.php b/tests/Operation/FindOneAndReplaceTest.php index c71946ec7..dd273a46d 100644 --- a/tests/Operation/FindOneAndReplaceTest.php +++ b/tests/Operation/FindOneAndReplaceTest.php @@ -6,34 +6,34 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\FindOneAndReplace; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use TypeError; class FindOneAndReplaceTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new FindOneAndReplace($this->getDatabaseName(), $this->getCollectionName(), $filter, []); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorReplacementArgumentTypeCheck($replacement): void { $this->expectException($replacement instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new FindOneAndReplace($this->getDatabaseName(), $this->getCollectionName(), [], $replacement); } - /** - * @dataProvider provideReplacementDocuments - * @doesNotPerformAssertions - */ + #[DataProvider('provideReplacementDocuments')] + #[DoesNotPerformAssertions] public function testConstructorReplacementArgument($replacement): void { new FindOneAndReplace($this->getDatabaseName(), $this->getCollectionName(), [], $replacement); } - /** @dataProvider provideUpdateDocuments */ + #[DataProvider('provideUpdateDocuments')] public function testConstructorReplacementArgumentProhibitsUpdateDocument($replacement): void { $this->expectException(InvalidArgumentException::class); @@ -41,10 +41,8 @@ public function testConstructorReplacementArgumentProhibitsUpdateDocument($repla new FindOneAndReplace($this->getDatabaseName(), $this->getCollectionName(), [], $replacement); } - /** - * @dataProvider provideUpdatePipelines - * @dataProvider provideEmptyUpdatePipelinesExcludingArray - */ + #[DataProvider('provideUpdatePipelines')] + #[DataProvider('provideEmptyUpdatePipelinesExcludingArray')] public function testConstructorReplacementArgumentProhibitsUpdatePipeline($replacement): void { $this->expectException(InvalidArgumentException::class); @@ -52,7 +50,7 @@ public function testConstructorReplacementArgumentProhibitsUpdatePipeline($repla new FindOneAndReplace($this->getDatabaseName(), $this->getCollectionName(), [], $replacement); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); @@ -68,7 +66,7 @@ public static function provideInvalidConstructorOptions() ]); } - /** @dataProvider provideInvalidConstructorReturnDocumentOptions */ + #[DataProvider('provideInvalidConstructorReturnDocumentOptions')] public function testConstructorReturnDocumentOption($returnDocument): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/FindOneAndUpdateTest.php b/tests/Operation/FindOneAndUpdateTest.php index 54defcad6..b20ab59ac 100644 --- a/tests/Operation/FindOneAndUpdateTest.php +++ b/tests/Operation/FindOneAndUpdateTest.php @@ -6,28 +6,27 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\FindOneAndUpdate; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class FindOneAndUpdateTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new FindOneAndUpdate($this->getDatabaseName(), $this->getCollectionName(), $filter, []); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorUpdateArgumentTypeCheck($update): void { $this->expectException($update instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new FindOneAndUpdate($this->getDatabaseName(), $this->getCollectionName(), [], $update); } - /** - * @dataProvider provideReplacementDocuments - * @dataProvider provideEmptyUpdatePipelines - */ + #[DataProvider('provideReplacementDocuments')] + #[DataProvider('provideEmptyUpdatePipelines')] public function testConstructorUpdateArgumentProhibitsReplacementDocumentOrEmptyPipeline($update): void { $this->expectException(InvalidArgumentException::class); @@ -35,7 +34,7 @@ public function testConstructorUpdateArgumentProhibitsReplacementDocumentOrEmpty new FindOneAndUpdate($this->getDatabaseName(), $this->getCollectionName(), [], $update); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); @@ -50,7 +49,7 @@ public static function provideInvalidConstructorOptions() ]); } - /** @dataProvider provideInvalidConstructorReturnDocumentOptions */ + #[DataProvider('provideInvalidConstructorReturnDocumentOptions')] public function testConstructorReturnDocumentOption($returnDocument): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/FindOneFunctionalTest.php b/tests/Operation/FindOneFunctionalTest.php index 6ae0a28da..de9058b7c 100644 --- a/tests/Operation/FindOneFunctionalTest.php +++ b/tests/Operation/FindOneFunctionalTest.php @@ -6,10 +6,11 @@ use MongoDB\Operation\FindOne; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; use MongoDB\Tests\Fixtures\Document\TestObject; +use PHPUnit\Framework\Attributes\DataProvider; class FindOneFunctionalTest extends FunctionalTestCase { - /** @dataProvider provideTypeMapOptionsAndExpectedDocument */ + #[DataProvider('provideTypeMapOptionsAndExpectedDocument')] public function testTypeMapOption(array $typeMap, $expectedDocument): void { $this->createFixtures(1); diff --git a/tests/Operation/FindTest.php b/tests/Operation/FindTest.php index 1a88cd6a2..d7eccf7f3 100644 --- a/tests/Operation/FindTest.php +++ b/tests/Operation/FindTest.php @@ -7,18 +7,19 @@ use MongoDB\Driver\ReadPreference; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Find; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class FindTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new Find($this->getDatabaseName(), $this->getCollectionName(), $filter); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); @@ -55,7 +56,7 @@ public static function provideInvalidConstructorOptions() ]); } - /** @dataProvider provideInvalidConstructorCursorTypeOptions */ + #[DataProvider('provideInvalidConstructorCursorTypeOptions')] public function testConstructorCursorTypeOption($cursorType): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/InsertManyFunctionalTest.php b/tests/Operation/InsertManyFunctionalTest.php index d4a786e71..68de6dfce 100644 --- a/tests/Operation/InsertManyFunctionalTest.php +++ b/tests/Operation/InsertManyFunctionalTest.php @@ -13,6 +13,7 @@ use MongoDB\Tests\CommandObserver; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; use MongoDB\Tests\Fixtures\Document\TestObject; +use PHPUnit\Framework\Attributes\Depends; class InsertManyFunctionalTest extends FunctionalTestCase { @@ -189,7 +190,7 @@ public function testUnacknowledgedWriteConcern() return $result; } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesInsertedCount(InsertManyResult $result): void { $this->expectException(BadMethodCallException::class); @@ -197,7 +198,7 @@ public function testUnacknowledgedWriteConcernAccessesInsertedCount(InsertManyRe $result->getInsertedCount(); } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesInsertedId(InsertManyResult $result): void { $this->assertInstanceOf(ObjectId::class, $result->getInsertedIds()[0]); diff --git a/tests/Operation/InsertManyTest.php b/tests/Operation/InsertManyTest.php index 52f32d2cf..8ee11ff5d 100644 --- a/tests/Operation/InsertManyTest.php +++ b/tests/Operation/InsertManyTest.php @@ -6,6 +6,7 @@ use MongoDB\Exception\UnsupportedValueException; use MongoDB\Operation\InsertMany; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; +use PHPUnit\Framework\Attributes\DataProvider; class InsertManyTest extends TestCase { @@ -23,7 +24,7 @@ public function testConstructorDocumentsMustBeAList(): void new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [1 => ['x' => 1]]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorDocumentsArgumentElementTypeChecks($document): void { $this->expectException(InvalidArgumentException::class); @@ -31,7 +32,7 @@ public function testConstructorDocumentsArgumentElementTypeChecks($document): vo new InsertMany($this->getDatabaseName(), $this->getCollectionName(), [$document]); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/InsertOneFunctionalTest.php b/tests/Operation/InsertOneFunctionalTest.php index 218a8853c..9cef81f04 100644 --- a/tests/Operation/InsertOneFunctionalTest.php +++ b/tests/Operation/InsertOneFunctionalTest.php @@ -13,6 +13,8 @@ use MongoDB\Tests\CommandObserver; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; use MongoDB\Tests\Fixtures\Document\TestObject; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; use stdClass; class InsertOneFunctionalTest extends FunctionalTestCase @@ -26,10 +28,8 @@ public function setUp(): void $this->collection = new Collection($this->manager, $this->getDatabaseName(), $this->getCollectionName()); } - /** - * @dataProvider provideDocumentsWithIds - * @dataProvider provideDocumentsWithoutIds - */ + #[DataProvider('provideDocumentsWithIds')] + #[DataProvider('provideDocumentsWithoutIds')] public function testDocumentEncoding($document, stdClass $expectedDocument): void { (new CommandObserver())->observe( @@ -80,7 +80,7 @@ public static function provideDocumentsWithoutIds(): array ]; } - /** @dataProvider provideDocumentsWithIds */ + #[DataProvider('provideDocumentsWithIds')] public function testInsertOneWithExistingId($document, stdClass $expectedDocument): void { $operation = new InsertOne($this->getDatabaseName(), $this->getCollectionName(), $document); @@ -93,7 +93,7 @@ public function testInsertOneWithExistingId($document, stdClass $expectedDocumen $this->assertSameDocuments([$expectedDocument], $this->collection->find()); } - /** @dataProvider provideDocumentsWithoutIds */ + #[DataProvider('provideDocumentsWithoutIds')] public function testInsertOneWithGeneratedId($document, stdClass $expectedDocument): void { $operation = new InsertOne($this->getDatabaseName(), $this->getCollectionName(), $document); @@ -180,7 +180,7 @@ public function testUnacknowledgedWriteConcern() return $result; } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesInsertedCount(InsertOneResult $result): void { $this->expectException(BadMethodCallException::class); @@ -188,7 +188,7 @@ public function testUnacknowledgedWriteConcernAccessesInsertedCount(InsertOneRes $result->getInsertedCount(); } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesInsertedId(InsertOneResult $result): void { $this->assertInstanceOf(ObjectId::class, $result->getInsertedId()); diff --git a/tests/Operation/InsertOneTest.php b/tests/Operation/InsertOneTest.php index 6f493d9d1..1f641ea88 100644 --- a/tests/Operation/InsertOneTest.php +++ b/tests/Operation/InsertOneTest.php @@ -7,18 +7,19 @@ use MongoDB\Exception\UnsupportedValueException; use MongoDB\Operation\InsertOne; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class InsertOneTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorDocumentArgumentTypeCheck($document): void { $this->expectException($document instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new InsertOne($this->getDatabaseName(), $this->getCollectionName(), $document); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/ListCollectionsFunctionalTest.php b/tests/Operation/ListCollectionsFunctionalTest.php index 9f03e14fe..259e6cbdb 100644 --- a/tests/Operation/ListCollectionsFunctionalTest.php +++ b/tests/Operation/ListCollectionsFunctionalTest.php @@ -8,6 +8,7 @@ use MongoDB\Operation\InsertOne; use MongoDB\Operation\ListCollections; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\Group; class ListCollectionsFunctionalTest extends FunctionalTestCase { @@ -35,12 +36,10 @@ public function testListCollectionsForNewlyCreatedDatabase(): void } } - /** - * @group matrix-testing-exclude-server-4.4-driver-4.0 - * @group matrix-testing-exclude-server-4.4-driver-4.2 - * @group matrix-testing-exclude-server-5.0-driver-4.0 - * @group matrix-testing-exclude-server-5.0-driver-4.2 - */ + #[Group('matrix-testing-exclude-server-4.4-driver-4.0')] + #[Group('matrix-testing-exclude-server-4.4-driver-4.2')] + #[Group('matrix-testing-exclude-server-5.0-driver-4.0')] + #[Group('matrix-testing-exclude-server-5.0-driver-4.2')] public function testIdIndexAndInfo(): void { $server = $this->getPrimaryServer(); diff --git a/tests/Operation/ListIndexesTest.php b/tests/Operation/ListIndexesTest.php index 690eb4bca..f6c7a095b 100644 --- a/tests/Operation/ListIndexesTest.php +++ b/tests/Operation/ListIndexesTest.php @@ -4,10 +4,11 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\ListIndexes; +use PHPUnit\Framework\Attributes\DataProvider; class ListIndexesTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/ListSearchIndexesTest.php b/tests/Operation/ListSearchIndexesTest.php index b07909351..d47e5e893 100644 --- a/tests/Operation/ListSearchIndexesTest.php +++ b/tests/Operation/ListSearchIndexesTest.php @@ -4,6 +4,7 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\ListSearchIndexes; +use PHPUnit\Framework\Attributes\DataProvider; class ListSearchIndexesTest extends TestCase { @@ -13,7 +14,7 @@ public function testConstructorIndexNameMustNotBeEmpty(): void new ListSearchIndexes($this->getDatabaseName(), $this->getCollectionName(), ['name' => '']); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/MapReduceFunctionalTest.php b/tests/Operation/MapReduceFunctionalTest.php index 69a39f297..5dbe45f3a 100644 --- a/tests/Operation/MapReduceFunctionalTest.php +++ b/tests/Operation/MapReduceFunctionalTest.php @@ -8,18 +8,18 @@ use MongoDB\Operation\Find; use MongoDB\Operation\MapReduce; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use function is_object; use function iterator_to_array; use function usort; use function version_compare; -/** - * @group matrix-testing-exclude-server-4.4-driver-4.0 - * @group matrix-testing-exclude-server-4.4-driver-4.2 - * @group matrix-testing-exclude-server-5.0-driver-4.0 - * @group matrix-testing-exclude-server-5.0-driver-4.2 - */ +#[Group('matrix-testing-exclude-server-4.4-driver-4.0')] +#[Group('matrix-testing-exclude-server-4.4-driver-4.2')] +#[Group('matrix-testing-exclude-server-5.0-driver-4.0')] +#[Group('matrix-testing-exclude-server-5.0-driver-4.2')] class MapReduceFunctionalTest extends FunctionalTestCase { public function testDefaultReadConcernIsOmitted(): void @@ -213,7 +213,7 @@ function (array $event): void { ); } - /** @dataProvider provideTypeMapOptionsAndExpectedDocuments */ + #[DataProvider('provideTypeMapOptionsAndExpectedDocuments')] public function testTypeMapOptionWithInlineResults(?array $typeMap, array $expectedDocuments): void { $this->createFixtures(3); @@ -258,7 +258,7 @@ public static function provideTypeMapOptionsAndExpectedDocuments() ]; } - /** @dataProvider provideTypeMapOptionsAndExpectedDocuments */ + #[DataProvider('provideTypeMapOptionsAndExpectedDocuments')] public function testTypeMapOptionWithOutputCollection(?array $typeMap, array $expectedDocuments): void { $this->createFixtures(3); diff --git a/tests/Operation/MapReduceTest.php b/tests/Operation/MapReduceTest.php index 6db1a9b99..86af81197 100644 --- a/tests/Operation/MapReduceTest.php +++ b/tests/Operation/MapReduceTest.php @@ -10,12 +10,13 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\BSONDocument; use MongoDB\Operation\MapReduce; +use PHPUnit\Framework\Attributes\DataProvider; use stdClass; use TypeError; class MapReduceTest extends TestCase { - /** @dataProvider provideInvalidOutValues */ + #[DataProvider('provideInvalidOutValues')] public function testConstructorOutArgumentTypeCheck($out): void { $map = new Javascript('function() { emit(this.x, this.y); }'); @@ -30,7 +31,7 @@ public static function provideInvalidOutValues() return self::wrapValuesForDataProvider([123, 3.14, true]); } - /** @dataProvider provideDeprecatedOutValues */ + #[DataProvider('provideDeprecatedOutValues')] public function testConstructorOutArgumentDeprecations($out): void { $map = new Javascript('function() { emit(this.x, this.y); }'); @@ -55,7 +56,7 @@ public static function provideDeprecatedOutValues(): array ]; } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $map = new Javascript('function() { emit(this.x, this.y); }'); diff --git a/tests/Operation/ModifyCollectionFunctionalTest.php b/tests/Operation/ModifyCollectionFunctionalTest.php index c92cdb0e8..9c6e3a42d 100644 --- a/tests/Operation/ModifyCollectionFunctionalTest.php +++ b/tests/Operation/ModifyCollectionFunctionalTest.php @@ -4,14 +4,13 @@ use MongoDB\Operation\CreateIndexes; use MongoDB\Operation\ModifyCollection; +use PHPUnit\Framework\Attributes\Group; class ModifyCollectionFunctionalTest extends FunctionalTestCase { - /** - * @group matrix-testing-exclude-server-4.2-driver-4.0-topology-sharded_cluster - * @group matrix-testing-exclude-server-4.4-driver-4.0-topology-sharded_cluster - * @group matrix-testing-exclude-server-5.0-driver-4.0-topology-sharded_cluster - */ + #[Group('matrix-testing-exclude-server-4.2-driver-4.0-topology-sharded_cluster')] + #[Group('matrix-testing-exclude-server-4.4-driver-4.0-topology-sharded_cluster')] + #[Group('matrix-testing-exclude-server-5.0-driver-4.0-topology-sharded_cluster')] public function testCollMod(): void { if ($this->isShardedCluster()) { diff --git a/tests/Operation/ModifyCollectionTest.php b/tests/Operation/ModifyCollectionTest.php index 8369d341e..c835d6a6d 100644 --- a/tests/Operation/ModifyCollectionTest.php +++ b/tests/Operation/ModifyCollectionTest.php @@ -4,6 +4,7 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\ModifyCollection; +use PHPUnit\Framework\Attributes\DataProvider; class ModifyCollectionTest extends TestCase { @@ -14,7 +15,7 @@ public function testConstructorEmptyCollectionOptions(): void new ModifyCollection($this->getDatabaseName(), $this->getCollectionName(), []); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/RenameCollectionTest.php b/tests/Operation/RenameCollectionTest.php index a4e190652..bae80e9ee 100644 --- a/tests/Operation/RenameCollectionTest.php +++ b/tests/Operation/RenameCollectionTest.php @@ -4,10 +4,11 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\RenameCollection; +use PHPUnit\Framework\Attributes\DataProvider; class RenameCollectionTest extends TestCase { - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/ReplaceOneTest.php b/tests/Operation/ReplaceOneTest.php index dc6ef2c47..d74cd7566 100644 --- a/tests/Operation/ReplaceOneTest.php +++ b/tests/Operation/ReplaceOneTest.php @@ -7,34 +7,34 @@ use MongoDB\Exception\UnsupportedValueException; use MongoDB\Operation\ReplaceOne; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use TypeError; class ReplaceOneTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), $filter, ['y' => 1]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorReplacementArgumentTypeCheck($replacement): void { $this->expectException($replacement instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $replacement); } - /** - * @dataProvider provideReplacementDocuments - * @doesNotPerformAssertions - */ + #[DataProvider('provideReplacementDocuments')] + #[DoesNotPerformAssertions] public function testConstructorReplacementArgument($replacement): void { new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $replacement); } - /** @dataProvider provideUpdateDocuments */ + #[DataProvider('provideUpdateDocuments')] public function testConstructorReplacementArgumentProhibitsUpdateDocument($replacement): void { $this->expectException(InvalidArgumentException::class); @@ -42,10 +42,8 @@ public function testConstructorReplacementArgumentProhibitsUpdateDocument($repla new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $replacement); } - /** - * @dataProvider provideUpdatePipelines - * @dataProvider provideEmptyUpdatePipelinesExcludingArray - */ + #[DataProvider('provideUpdatePipelines')] + #[DataProvider('provideEmptyUpdatePipelinesExcludingArray')] public function testConstructorReplacementArgumentProhibitsUpdatePipeline($replacement): void { $this->expectException(InvalidArgumentException::class); @@ -53,7 +51,7 @@ public function testConstructorReplacementArgumentProhibitsUpdatePipeline($repla new ReplaceOne($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $replacement); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionsTypeCheck($options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/UpdateFunctionalTest.php b/tests/Operation/UpdateFunctionalTest.php index 2a18f13aa..9415960f5 100644 --- a/tests/Operation/UpdateFunctionalTest.php +++ b/tests/Operation/UpdateFunctionalTest.php @@ -11,6 +11,8 @@ use MongoDB\Operation\Update; use MongoDB\Tests\CommandObserver; use MongoDB\UpdateResult; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Depends; use stdClass; use function is_array; @@ -26,7 +28,7 @@ public function setUp(): void $this->collection = new Collection($this->manager, $this->getDatabaseName(), $this->getCollectionName()); } - /** @dataProvider provideFilterDocuments */ + #[DataProvider('provideFilterDocuments')] public function testFilterDocuments($filter, stdClass $expectedFilter): void { (new CommandObserver())->observe( @@ -46,12 +48,10 @@ function (array $event) use ($expectedFilter): void { ); } - /** - * @dataProvider provideReplacementDocuments - * @dataProvider provideUpdateDocuments - * @dataProvider provideUpdatePipelines - * @dataProvider provideReplacementDocumentLikePipeline - */ + #[DataProvider('provideReplacementDocuments')] + #[DataProvider('provideUpdateDocuments')] + #[DataProvider('provideUpdatePipelines')] + #[DataProvider('provideReplacementDocumentLikePipeline')] public function testUpdateDocuments($update, $expectedUpdate): void { if (is_array($expectedUpdate)) { @@ -284,7 +284,7 @@ public function testUnacknowledgedWriteConcern() return $result; } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesMatchedCount(UpdateResult $result): void { $this->expectException(BadMethodCallException::class); @@ -292,7 +292,7 @@ public function testUnacknowledgedWriteConcernAccessesMatchedCount(UpdateResult $result->getMatchedCount(); } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesModifiedCount(UpdateResult $result): void { $this->expectException(BadMethodCallException::class); @@ -300,7 +300,7 @@ public function testUnacknowledgedWriteConcernAccessesModifiedCount(UpdateResult $result->getModifiedCount(); } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesUpsertedCount(UpdateResult $result): void { $this->expectException(BadMethodCallException::class); @@ -308,7 +308,7 @@ public function testUnacknowledgedWriteConcernAccessesUpsertedCount(UpdateResult $result->getUpsertedCount(); } - /** @depends testUnacknowledgedWriteConcern */ + #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesUpsertedId(UpdateResult $result): void { $this->expectException(BadMethodCallException::class); diff --git a/tests/Operation/UpdateManyTest.php b/tests/Operation/UpdateManyTest.php index 99acecf33..30c0a0836 100644 --- a/tests/Operation/UpdateManyTest.php +++ b/tests/Operation/UpdateManyTest.php @@ -5,38 +5,36 @@ use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\UpdateMany; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use TypeError; class UpdateManyTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new UpdateMany($this->getDatabaseName(), $this->getCollectionName(), $filter, ['$set' => ['x' => 1]]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorUpdateArgumentTypeCheck($update): void { $this->expectException($update instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new UpdateMany($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $update); } - /** - * @dataProvider provideUpdateDocuments - * @dataProvider provideUpdatePipelines - * @doesNotPerformAssertions - */ + #[DataProvider('provideUpdateDocuments')] + #[DataProvider('provideUpdatePipelines')] + #[DoesNotPerformAssertions] public function testConstructorUpdateArgument($update): void { new UpdateMany($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $update); } - /** - * @dataProvider provideReplacementDocuments - * @dataProvider provideEmptyUpdatePipelines - */ + #[DataProvider('provideReplacementDocuments')] + #[DataProvider('provideEmptyUpdatePipelines')] public function testConstructorUpdateArgumentProhibitsReplacementDocumentOrEmptyPipeline($update): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/UpdateOneTest.php b/tests/Operation/UpdateOneTest.php index e0c4086a0..25d67f45c 100644 --- a/tests/Operation/UpdateOneTest.php +++ b/tests/Operation/UpdateOneTest.php @@ -5,38 +5,36 @@ use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\UpdateOne; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\DoesNotPerformAssertions; use TypeError; class UpdateOneTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new UpdateOne($this->getDatabaseName(), $this->getCollectionName(), $filter, ['$set' => ['x' => 1]]); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorUpdateArgumentTypeCheck($update): void { $this->expectException($update instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new UpdateOne($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $update); } - /** - * @dataProvider provideUpdateDocuments - * @dataProvider provideUpdatePipelines - * @doesNotPerformAssertions - */ + #[DataProvider('provideUpdateDocuments')] + #[DataProvider('provideUpdatePipelines')] + #[DoesNotPerformAssertions] public function testConstructorUpdateArgument($update): void { new UpdateOne($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $update); } - /** - * @dataProvider provideReplacementDocuments - * @dataProvider provideEmptyUpdatePipelines - */ + #[DataProvider('provideReplacementDocuments')] + #[DataProvider('provideEmptyUpdatePipelines')] public function testConstructorUpdateArgumentProhibitsReplacementDocumentOrEmptyPipeline($update): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/UpdateSearchIndexTest.php b/tests/Operation/UpdateSearchIndexTest.php index befe07eb1..f61db793e 100644 --- a/tests/Operation/UpdateSearchIndexTest.php +++ b/tests/Operation/UpdateSearchIndexTest.php @@ -5,6 +5,7 @@ use MongoDB\BSON\PackedArray; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\UpdateSearchIndex; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class UpdateSearchIndexTest extends TestCase @@ -15,7 +16,7 @@ public function testConstructorIndexNameMustNotBeEmpty(): void new UpdateSearchIndex($this->getDatabaseName(), $this->getCollectionName(), '', []); } - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorIndexDefinitionMustBeADocument($definition): void { $this->expectException($definition instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); diff --git a/tests/Operation/UpdateTest.php b/tests/Operation/UpdateTest.php index 468bf00bf..6c6f590d5 100644 --- a/tests/Operation/UpdateTest.php +++ b/tests/Operation/UpdateTest.php @@ -6,25 +6,26 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Update; +use PHPUnit\Framework\Attributes\DataProvider; use TypeError; class UpdateTest extends TestCase { - /** @dataProvider provideInvalidDocumentValues */ + #[DataProvider('provideInvalidDocumentValues')] public function testConstructorFilterArgumentTypeCheck($filter): void { $this->expectException($filter instanceof PackedArray ? InvalidArgumentException::class : TypeError::class); new Update($this->getDatabaseName(), $this->getCollectionName(), $filter, ['$set' => ['x' => 1]]); } - /** @dataProvider provideInvalidUpdateValues */ + #[DataProvider('provideInvalidUpdateValues')] public function testConstructorUpdateArgumentTypeCheck($update): void { $this->expectException(TypeError::class); new Update($this->getDatabaseName(), $this->getCollectionName(), ['x' => 1], $update); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); @@ -45,10 +46,8 @@ public static function provideInvalidConstructorOptions() ]); } - /** - * @dataProvider provideReplacementDocuments - * @dataProvider provideEmptyUpdatePipelines - */ + #[DataProvider('provideReplacementDocuments')] + #[DataProvider('provideEmptyUpdatePipelines')] public function testConstructorMultiOptionProhibitsReplacementDocumentOrEmptyPipeline($update): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/Operation/WatchFunctionalTest.php b/tests/Operation/WatchFunctionalTest.php index df4c98fdc..15326f696 100644 --- a/tests/Operation/WatchFunctionalTest.php +++ b/tests/Operation/WatchFunctionalTest.php @@ -25,6 +25,8 @@ use MongoDB\Operation\InsertOne; use MongoDB\Operation\Watch; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\Constraint\ObjectHasProperty; use PHPUnit\Framework\ExpectationFailedException; use ReflectionClass; @@ -37,11 +39,9 @@ use function microtime; use function sprintf; -/** - * @group matrix-testing-exclude-server-4.2-driver-4.0-topology-sharded_cluster - * @group matrix-testing-exclude-server-4.4-driver-4.0-topology-sharded_cluster - * @group matrix-testing-exclude-server-5.0-driver-4.0-topology-sharded_cluster - */ +#[Group('matrix-testing-exclude-server-4.2-driver-4.0-topology-sharded_cluster')] +#[Group('matrix-testing-exclude-server-4.4-driver-4.0-topology-sharded_cluster')] +#[Group('matrix-testing-exclude-server-5.0-driver-4.0-topology-sharded_cluster')] class WatchFunctionalTest extends FunctionalTestCase { public const INTERRUPTED = 11601; @@ -109,9 +109,8 @@ public function encode($value): Document /** * Prose test 1: "ChangeStream must continuously track the last seen * resumeToken" - * - * @dataProvider provideCodecOptions */ + #[DataProvider('provideCodecOptions')] public function testGetResumeToken(array $options, Closure $getIdentifier): void { $this->skipIfServerVersion('>=', '4.0.7', 'postBatchResumeToken is supported'); @@ -162,9 +161,8 @@ public function testGetResumeToken(array $options, Closure $getIdentifier): void * - The batch has been iterated up to but not including the last element. * Expected result: getResumeToken must return the _id of the previous * document returned. - * - * @dataProvider provideCodecOptions */ + #[DataProvider('provideCodecOptions')] public function testGetResumeTokenWithPostBatchResumeToken(array $options, Closure $getIdentifier): void { $this->skipIfServerVersion('<', '4.0.7', 'postBatchResumeToken is not supported'); @@ -489,7 +487,7 @@ public function testRewindMultipleTimesWithNoResults(): void $this->assertNull($changeStream->current()); } - /** @dataProvider provideCodecOptions */ + #[DataProvider('provideCodecOptions')] public function testNoChangeAfterResumeBeforeInsert(array $options): void { $operation = new Watch( @@ -895,7 +893,7 @@ public function testMaxAwaitTimeMS(): void } } - /** @dataProvider provideCodecOptions */ + #[DataProvider('provideCodecOptions')] public function testRewindExtractsResumeTokenAndNextResumes(array $options, Closure $getIdentifier): void { $operation = new Watch( @@ -953,7 +951,7 @@ public function testRewindExtractsResumeTokenAndNextResumes(array $options, Clos $this->assertMatchesDocument(['_id' => 3, 'x' => 'baz'], $changeStream->current()->fullDocument); } - /** @dataProvider provideCodecOptions */ + #[DataProvider('provideCodecOptions')] public function testResumeAfterOption(array $options, Closure $getIdentifier): void { $operation = new Watch( @@ -999,7 +997,7 @@ public function testResumeAfterOption(array $options, Closure $getIdentifier): v $this->assertMatchesDocument(['_id' => 2, 'x' => 'bar'], $changeStream->current()->fullDocument); } - /** @dataProvider provideCodecOptions */ + #[DataProvider('provideCodecOptions')] public function testStartAfterOption(array $options, Closure $getIdentifier): void { $this->skipIfServerVersion('<', '4.1.1', 'startAfter is not supported'); @@ -1047,7 +1045,7 @@ public function testStartAfterOption(array $options, Closure $getIdentifier): vo $this->assertMatchesDocument(['_id' => 2, 'x' => 'bar'], $changeStream->current()->fullDocument); } - /** @dataProvider provideTypeMapOptionsAndExpectedChangeDocument */ + #[DataProvider('provideTypeMapOptionsAndExpectedChangeDocument')] public function testTypeMapOption(array $typeMap, $expectedChangeDocument): void { $operation = new Watch($this->manager, $this->getDatabaseName(), $this->getCollectionName(), [], ['typeMap' => $typeMap] + $this->defaultOptions); @@ -1427,9 +1425,8 @@ public function testGetResumeTokenReturnsOriginalResumeTokenOnEmptyBatch(): void * - getResumeToken must return startAfter from the initial aggregate if the option was specified. * - getResumeToken must return resumeAfter from the initial aggregate if the option was specified. * - If neither the startAfter nor resumeAfter options were specified, the getResumeToken result must be empty. - * - * @dataProvider provideCodecOptions */ + #[DataProvider('provideCodecOptions')] public function testResumeTokenBehaviour(array $options): void { $this->skipIfServerVersion('<', '4.1.1', 'Testing resumeAfter and startAfter can only be tested on servers >= 4.1.1'); diff --git a/tests/Operation/WatchTest.php b/tests/Operation/WatchTest.php index 8a3f4945d..9e874c484 100644 --- a/tests/Operation/WatchTest.php +++ b/tests/Operation/WatchTest.php @@ -5,6 +5,7 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Operation\Watch; use MongoDB\Tests\Fixtures\Codec\TestDocumentCodec; +use PHPUnit\Framework\Attributes\DataProvider; use stdClass; /** @@ -32,7 +33,7 @@ public function testConstructorPipelineArgumentMustBeAList(): void new Watch($this->manager, $this->getDatabaseName(), $this->getCollectionName(), ['foo' => ['$match' => ['x' => 1]]]); } - /** @dataProvider provideInvalidConstructorOptions */ + #[DataProvider('provideInvalidConstructorOptions')] public function testConstructorOptionTypeChecks(array $options): void { $this->expectException(InvalidArgumentException::class); diff --git a/tests/PedantryTest.php b/tests/PedantryTest.php index 5c6b07eed..3d89f259a 100644 --- a/tests/PedantryTest.php +++ b/tests/PedantryTest.php @@ -3,6 +3,7 @@ namespace MongoDB\Tests; use MongoDB; +use PHPUnit\Framework\Attributes\DataProvider; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use ReflectionClass; @@ -32,7 +33,7 @@ class PedantryTest extends TestCase MongoDB\Builder\Stage\FluentFactoryTrait::class, ]; - /** @dataProvider provideProjectClassNames */ + #[DataProvider('provideProjectClassNames')] public function testMethodsAreOrderedAlphabeticallyByVisibility($className): void { $class = new ReflectionClass($className); diff --git a/tests/PsrLogAdapterTest.php b/tests/PsrLogAdapterTest.php index 5e945dd62..1907b8532 100644 --- a/tests/PsrLogAdapterTest.php +++ b/tests/PsrLogAdapterTest.php @@ -5,6 +5,7 @@ use MongoDB\Driver\Monitoring\LogSubscriber; use MongoDB\Exception\UnexpectedValueException; use MongoDB\PsrLogAdapter; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase as BaseTestCase; use Psr\Log\AbstractLogger; use Psr\Log\LoggerInterface; @@ -84,10 +85,8 @@ public function testLog(): void $this->assertSame($expectedLogs, $this->logger->logs); } - /** - * @testWith [-1] - * [9] - */ + #[TestWith([-1])] + #[TestWith([9])] public function testWriteLogWithInvalidLevel(int $level): void { $this->expectException(UnexpectedValueException::class); diff --git a/tests/SpecTests/AtlasDataLakeSpecTest.php b/tests/SpecTests/AtlasDataLakeSpecTest.php index 3d008093c..da8af86df 100644 --- a/tests/SpecTests/AtlasDataLakeSpecTest.php +++ b/tests/SpecTests/AtlasDataLakeSpecTest.php @@ -4,6 +4,7 @@ use MongoDB\Driver\Cursor; use MongoDB\Tests\CommandObserver; +use PHPUnit\Framework\Attributes\Group; use function current; use function explode; @@ -13,8 +14,8 @@ * Atlas Data Lake spec tests. * * @see https://github.com/mongodb/specifications/tree/master/source/atlas-data-lake-testing/tests - * @group atlas-data-lake */ +#[Group('atlas-data-lake')] class AtlasDataLakeSpecTest extends FunctionalTestCase { public function setUp(): void diff --git a/tests/SpecTests/ClientSideEncryption/Prose21_AutomaticDataEncryptionKeysTest.php b/tests/SpecTests/ClientSideEncryption/Prose21_AutomaticDataEncryptionKeysTest.php index ddea08972..874671642 100644 --- a/tests/SpecTests/ClientSideEncryption/Prose21_AutomaticDataEncryptionKeysTest.php +++ b/tests/SpecTests/ClientSideEncryption/Prose21_AutomaticDataEncryptionKeysTest.php @@ -10,6 +10,8 @@ use MongoDB\Driver\Exception\CommandException; use MongoDB\Exception\CreateEncryptedCollectionException; use MongoDB\Exception\InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use function base64_decode; @@ -17,9 +19,9 @@ * Prose test 21: Automatic Data Encryption Keys * * @see https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#automatic-data-encryption-keys - * @group csfle - * @group serverless */ +#[Group('csfle')] +#[Group('serverless')] class Prose21_AutomaticDataEncryptionKeysTest extends FunctionalTestCase { public const SERVER_ERROR_TYPEMISMATCH = 14; @@ -63,10 +65,8 @@ public function tearDown(): void $this->database = null; } - /** - * @see https://github.com/mongodb/specifications/blob/bc37892f360cab9df4082922384e0f4d4233f6d3/source/client-side-encryption/tests/README.rst#case-1-simple-creation-and-validation - * @dataProvider provideKmsProviderAndMasterKey - */ + /** @see https://github.com/mongodb/specifications/blob/bc37892f360cab9df4082922384e0f4d4233f6d3/source/client-side-encryption/tests/README.rst#case-1-simple-creation-and-validation */ + #[DataProvider('provideKmsProviderAndMasterKey')] public function testCase1_SimpleCreationAndValidation(string $kmsProvider, ?array $masterKey): void { [$result, $encryptedFields] = $this->database->createEncryptedCollection( @@ -98,10 +98,8 @@ public static function provideKmsProviderAndMasterKey(): Generator ]; } - /** - * @see https://github.com/mongodb/specifications/blob/bc37892f360cab9df4082922384e0f4d4233f6d3/source/client-side-encryption/tests/README.rst#case-2-missing-encryptedfields - * @dataProvider provideKmsProviderAndMasterKey - */ + /** @see https://github.com/mongodb/specifications/blob/bc37892f360cab9df4082922384e0f4d4233f6d3/source/client-side-encryption/tests/README.rst#case-2-missing-encryptedfields */ + #[DataProvider('provideKmsProviderAndMasterKey')] public function testCase2_MissingEncryptedFields(string $kmsProvider, ?array $masterKey): void { $this->expectException(InvalidArgumentException::class); @@ -115,10 +113,8 @@ public function testCase2_MissingEncryptedFields(string $kmsProvider, ?array $ma ); } - /** - * @see https://github.com/mongodb/specifications/blob/bc37892f360cab9df4082922384e0f4d4233f6d3/source/client-side-encryption/tests/README.rst#case-3-invalid-keyid - * @dataProvider provideKmsProviderAndMasterKey - */ + /** @see https://github.com/mongodb/specifications/blob/bc37892f360cab9df4082922384e0f4d4233f6d3/source/client-side-encryption/tests/README.rst#case-3-invalid-keyid */ + #[DataProvider('provideKmsProviderAndMasterKey')] public function testCase3_InvalidKeyId(string $kmsProvider, ?array $masterKey): void { try { @@ -140,10 +136,8 @@ public function testCase3_InvalidKeyId(string $kmsProvider, ?array $masterKey): } } - /** - * @see https://github.com/mongodb/specifications/blob/bc37892f360cab9df4082922384e0f4d4233f6d3/source/client-side-encryption/tests/README.rst#case-4-insert-encrypted-value - * @dataProvider provideKmsProviderAndMasterKey - */ + /** @see https://github.com/mongodb/specifications/blob/bc37892f360cab9df4082922384e0f4d4233f6d3/source/client-side-encryption/tests/README.rst#case-4-insert-encrypted-value */ + #[DataProvider('provideKmsProviderAndMasterKey')] public function testCase4_InsertEncryptedValue(string $kmsProvider, ?array $masterKey): void { [$result, $encryptedFields] = $this->database->createEncryptedCollection( diff --git a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php b/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php index f805df3c0..a4c869866 100644 --- a/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php +++ b/tests/SpecTests/ClientSideEncryption/Prose22_RangeExplicitEncryptionTest.php @@ -16,6 +16,8 @@ use MongoDB\Driver\ClientEncryption; use MongoDB\Driver\Exception\EncryptionException; use MultipleIterator; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use function base64_decode; use function file_get_contents; @@ -26,9 +28,9 @@ * Prose test 22: Range Explicit Encryption * * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#22-range-explicit-encryption - * @group csfle - * @group serverless */ +#[Group('csfle')] +#[Group('serverless')] class Prose22_RangeExplicitEncryptionTest extends FunctionalTestCase { private ?ClientEncryption $clientEncryption = null; @@ -178,10 +180,8 @@ public static function provideTypeAndRangeOpts(): Generator ]; } - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-1-can-decrypt-a-payload - * @dataProvider provideTypeAndRangeOpts - */ + /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-1-can-decrypt-a-payload */ + #[DataProvider('provideTypeAndRangeOpts')] public function testCase1_CanDecryptAPayload(string $type, array $rangeOpts): void { $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); @@ -211,10 +211,8 @@ public function testCase1_CanDecryptAPayload(string $type, array $rangeOpts): vo $this->assertEquals($originalValue, $decryptedValue); } - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-2-can-find-encrypted-range-and-return-the-maximum - * @dataProvider provideTypeAndRangeOpts - */ + /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-2-can-find-encrypted-range-and-return-the-maximum */ + #[DataProvider('provideTypeAndRangeOpts')] public function testCase2_CanFindEncryptedRangeAndReturnTheMaximum(string $type, array $rangeOpts): void { $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); @@ -248,10 +246,8 @@ public function testCase2_CanFindEncryptedRangeAndReturnTheMaximum(string $type, $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); } - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-3-can-find-encrypted-range-and-return-the-minimum - * @dataProvider provideTypeAndRangeOpts - */ + /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-3-can-find-encrypted-range-and-return-the-minimum */ + #[DataProvider('provideTypeAndRangeOpts')] public function testCase3_CanFindEncryptedRangeAndReturnTheMinimum(string $type, array $rangeOpts): void { $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); @@ -284,10 +280,8 @@ public function testCase3_CanFindEncryptedRangeAndReturnTheMinimum(string $type, $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); } - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-4-can-find-encrypted-range-with-an-open-range-query - * @dataProvider provideTypeAndRangeOpts - */ + /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-4-can-find-encrypted-range-with-an-open-range-query */ + #[DataProvider('provideTypeAndRangeOpts')] public function testCase4_CanFindEncryptedRangeWithAnOpenRangeQuery(string $type, array $rangeOpts): void { $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); @@ -311,10 +305,8 @@ public function testCase4_CanFindEncryptedRangeWithAnOpenRangeQuery(string $type $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); } - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-5-can-run-an-aggregation-expression-inside-expr - * @dataProvider provideTypeAndRangeOpts - */ + /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-5-can-run-an-aggregation-expression-inside-expr */ + #[DataProvider('provideTypeAndRangeOpts')] public function testCase5_CanRunAnAggregationExpressionInsideExpr(string $type, array $rangeOpts): void { $this->setUpWithTypeAndRangeOpts($type, $rangeOpts); @@ -343,10 +335,8 @@ public function testCase5_CanRunAnAggregationExpressionInsideExpr(string $type, $this->assertMultipleDocumentsMatch($expectedDocuments, $cursor); } - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-6-encrypting-a-document-greater-than-the-maximum-errors - * @dataProvider provideTypeAndRangeOpts - */ + /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-6-encrypting-a-document-greater-than-the-maximum-errors */ + #[DataProvider('provideTypeAndRangeOpts')] public function testCase6_EncryptingADocumentGreaterThanTheMaximumErrors(string $type, array $rangeOpts): void { if ($type === 'DecimalNoPrecision' || $type === 'DoubleNoPrecision') { @@ -367,10 +357,8 @@ public function testCase6_EncryptingADocumentGreaterThanTheMaximumErrors(string $this->clientEncryption->encrypt(self::cast($type, 201), $encryptOpts); } - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-7-encrypting-a-value-of-a-different-type-errors - * @dataProvider provideTypeAndRangeOpts - */ + /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-7-encrypting-a-value-of-a-different-type-errors */ + #[DataProvider('provideTypeAndRangeOpts')] public function testCase7_EncryptingAValueOfADifferentTypeErrors(string $type, array $rangeOpts): void { if ($type === 'DecimalNoPrecision' || $type === 'DoubleNoPrecision') { @@ -395,10 +383,8 @@ public function testCase7_EncryptingAValueOfADifferentTypeErrors(string $type, a $this->clientEncryption->encrypt($value, $encryptOpts); } - /** - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-8-setting-precision-errors-if-the-type-is-not-double-or-decimal128 - * @dataProvider provideTypeAndRangeOpts - */ + /** @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-8-setting-precision-errors-if-the-type-is-not-double-or-decimal128 */ + #[DataProvider('provideTypeAndRangeOpts')] public function testCase8_SettingPrecisionErrorsIfTheTypeIsNotDoubleOrDecimal128(string $type, array $rangeOpts): void { if ($type === 'DecimalNoPrecision' || $type === 'DecimalPrecision' || $type === 'DoubleNoPrecision' || $type === 'DoublePrecision') { diff --git a/tests/SpecTests/ClientSideEncryptionSpecTest.php b/tests/SpecTests/ClientSideEncryptionSpecTest.php index 263fc61c2..b18eff642 100644 --- a/tests/SpecTests/ClientSideEncryptionSpecTest.php +++ b/tests/SpecTests/ClientSideEncryptionSpecTest.php @@ -24,6 +24,9 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Tests\CommandObserver; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\SkippedTestError; use stdClass; use Throwable; @@ -48,9 +51,9 @@ * Client-side encryption spec tests. * * @see https://github.com/mongodb/specifications/tree/master/source/client-side-encryption - * @group csfle - * @group serverless */ +#[Group('csfle')] +#[Group('serverless')] class ClientSideEncryptionSpecTest extends FunctionalTestCase { public const LOCAL_MASTERKEY = 'Mng0NCt4ZHVUYUJCa1kxNkVyNUR1QURhZ2h2UzR2d2RrZzh0cFBwM3R6NmdWMDFBMUN3YkQ5aXRRMkhGRGdQV09wOGVNYUMxT2k3NjZKelhaQmRCZGJkTXVyZG9uSjFk'; @@ -149,7 +152,6 @@ public static function createTestClient(?string $uri = null, array $options = [] /** * Execute an individual test case from the specification. * - * @dataProvider provideTests * @param stdClass $test Individual "tests[]" document * @param array $runOn Top-level "runOn" array with server requirements * @param array $data Top-level "data" array to initialize collection @@ -158,6 +160,7 @@ public static function createTestClient(?string $uri = null, array $options = [] * @param string $databaseName Name of database under test * @param string $collectionName Name of collection under test */ + #[DataProvider('provideTests')] public function testClientSideEncryption(stdClass $test, ?array $runOn, array $data, ?stdClass $encryptedFields = null, ?array $keyVaultData = null, ?stdClass $jsonSchema = null, ?string $databaseName = null, ?string $collectionName = null): void { if (isset(self::$incompleteTests[$this->dataDescription()])) { @@ -270,8 +273,8 @@ public static function provideTests() * Prose test 2: Data Key and Double Encryption * * @see https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#data-key-and-double-encryption - * @dataProvider dataKeyProvider */ + #[DataProvider('dataKeyProvider')] public function testDataKeyAndDoubleEncryption(string $providerName, $masterKey): void { $client = static::createTestClient(); @@ -409,9 +412,9 @@ public static function dataKeyProvider() * Prose test 3: External Key Vault * * @see https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#external-key-vault-test - * @testWith [false] - * [true] */ + #[TestWith([false])] + #[TestWith([true])] public function testExternalKeyVault($withExternalKeyVault): void { $client = static::createTestClient(); @@ -557,8 +560,8 @@ static function (self $test, Collection $collection, array $document): void { * Prose test 4: BSON Size Limits and Batch Splitting * * @see https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#bson-size-limits-and-batch-splitting - * @dataProvider provideBSONSizeLimitsAndBatchSplittingTests */ + #[DataProvider('provideBSONSizeLimitsAndBatchSplittingTests')] public function testBSONSizeLimitsAndBatchSplitting(Closure $test): void { $client = static::createTestClient(); @@ -623,9 +626,9 @@ public function testViewsAreProhibited(): void * Prose test 6: BSON Corpus * * @see https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#corpus-test - * @testWith [true] - * [false] */ + #[TestWith([true])] + #[TestWith([false])] public function testCorpus($schemaMap = true): void { $client = static::createTestClient(); @@ -732,8 +735,8 @@ public function testCorpus($schemaMap = true): void * Prose test 7: Custom Endpoint * * @see https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#custom-endpoint-test - * @dataProvider customEndpointProvider */ + #[DataProvider('customEndpointProvider')] public function testCustomEndpoint(Closure $test): void { $client = static::createTestClient(); @@ -1103,8 +1106,8 @@ public function testInvalidHostnameInKmsCertificate(): void * Prose test 11: KMS TLS Options * * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#kms-tls-options-tests - * @dataProvider provideKmsTlsOptionsTests */ + #[DataProvider('provideKmsTlsOptionsTests')] public function testKmsTlsOptions(Closure $test): void { $client = static::createTestClient(); @@ -1321,8 +1324,8 @@ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, Cli * Prose test 12: Explicit Encryption * * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#explicit-encryption - * @dataProvider provideExplicitEncryptionTests */ + #[DataProvider('provideExplicitEncryptionTests')] public function testExplicitEncryption(Closure $test): void { if ($this->isStandalone()) { @@ -1500,8 +1503,8 @@ static function (self $test, ClientEncryption $clientEncryption, Client $encrypt * Prose test 13: Unique Index on keyAltNames * * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#unique-index-on-keyaltnames - * @dataProvider provideUniqueIndexOnKeyAltNamesTests */ + #[DataProvider('provideUniqueIndexOnKeyAltNamesTests')] public function testUniqueIndexOnKeyAltNames(Closure $test): void { // Test setup @@ -1587,8 +1590,8 @@ static function (self $test, Client $client, ClientEncryption $clientEncryption) * Prose test 14: Decryption Events * * @see https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#decryption-events - * @dataProvider provideDecryptionEventsTests */ + #[DataProvider('provideDecryptionEventsTests')] public function testDecryptionEvents(Closure $test): void { // Test setup @@ -1736,10 +1739,10 @@ static function (self $test, Client $setupClient, ClientEncryption $clientEncryp * Prose test 15: On-demand AWS Credentials * * @see https://github.com/mongodb/specifications/tree/master/source/client-side-encryption/tests#on-demand-aws-credentials - * @group csfle-without-aws-creds - * @testWith [true] - * [false] */ + #[TestWith([true])] + #[TestWith([false])] + #[Group('csfle-without-aws-creds')] public function testOnDemandAwsCredentials(bool $shouldSucceed): void { $hasCredentials = (getenv('AWS_ACCESS_KEY_ID') && getenv('AWS_SECRET_ACCESS_KEY')); @@ -1779,8 +1782,8 @@ public function testOnDemandAwsCredentials(bool $shouldSucceed): void * Prose test 16: RewrapManyDataKey * * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#rewrap - * @dataProvider provideRewrapManyDataKeySrcAndDstProviders */ + #[DataProvider('provideRewrapManyDataKeySrcAndDstProviders')] public function testRewrapManyDataKey(string $srcProvider, string $dstProvider): void { $providerMasterKeys = [ diff --git a/tests/SpecTests/DocumentsMatchConstraint.php b/tests/SpecTests/DocumentsMatchConstraint.php index e8d4d0de0..82f4885d0 100644 --- a/tests/SpecTests/DocumentsMatchConstraint.php +++ b/tests/SpecTests/DocumentsMatchConstraint.php @@ -12,6 +12,7 @@ use RuntimeException; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory; +use SebastianBergmann\Exporter\Exporter; use stdClass; use function array_values; @@ -80,12 +81,12 @@ public function evaluate($other, string $description = '', bool $returnResult = $this->assertEquals($this->value, $other, $this->ignoreExtraKeysInRoot); $success = true; } catch (RuntimeException $e) { + $exporter = new Exporter(); $this->lastFailure = new ComparisonFailure( $this->value, $other, - $this->exporter()->export($this->value), - $this->exporter()->export($other), - false, + $exporter->export($this->value), + $exporter->export($other), $e->getMessage(), ); } @@ -123,7 +124,7 @@ private function assertEquals(ArrayObject $expected, ArrayObject $actual, bool $ if ($expected::class !== $actual::class) { throw new RuntimeException(sprintf( '%s is not instance of expected class "%s"', - $this->exporter()->shortenedExport($actual), + (new Exporter())->shortenedExport($actual), $expected::class, )); } @@ -162,11 +163,10 @@ private function assertEquals(ArrayObject $expected, ArrayObject $actual, bool $ $actualValue, '', '', - false, sprintf( 'Field path "%s": %s is not instance of expected type "%s".', $keyPrefix . $key, - $this->exporter()->shortenedExport($actualValue), + (new Exporter())->shortenedExport($actualValue), $expectedType, ), ); @@ -180,7 +180,6 @@ private function assertEquals(ArrayObject $expected, ArrayObject $actual, bool $ $actualValue, '', '', - false, sprintf('Field path "%s": %s', $keyPrefix . $key, $failure->getMessage()), ); } @@ -233,7 +232,7 @@ protected function matches($other): bool public function toString(): string { - return 'matches ' . $this->exporter()->export($this->value); + return 'matches ' . (new Exporter())->export($this->value); } private static function isNumeric($value): bool diff --git a/tests/SpecTests/DocumentsMatchConstraintTest.php b/tests/SpecTests/DocumentsMatchConstraintTest.php index 283d05fe8..f410591bf 100644 --- a/tests/SpecTests/DocumentsMatchConstraintTest.php +++ b/tests/SpecTests/DocumentsMatchConstraintTest.php @@ -16,6 +16,7 @@ use MongoDB\Model\BSONArray; use MongoDB\Model\BSONDocument; use MongoDB\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\ExpectationFailedException; use const PHP_INT_SIZE; @@ -70,7 +71,7 @@ public function testIgnoreExtraKeysInEmbedded(): void $this->assertResult(false, $c, [1, ['a' => 2]], 'Keys must have the correct value'); } - /** @dataProvider provideBSONTypes */ + #[DataProvider('provideBSONTypes')] public function testBSONTypeAssertions($type, $value): void { $constraint = new DocumentsMatchConstraint(['x' => ['$$type' => $type]]); @@ -132,7 +133,7 @@ public function testBSONTypeAssertionsWithMultipleTypes(): void $this->assertResult(false, $c2, ['x' => true], 'bool is not number or string'); } - /** @dataProvider errorMessageProvider */ + #[DataProvider('errorMessageProvider')] public function testErrorMessages($expectedMessagePart, DocumentsMatchConstraint $constraint, $actualValue): void { try { diff --git a/tests/SpecTests/RetryableWrites/Prose3_ReturnOriginalErrorTest.php b/tests/SpecTests/RetryableWrites/Prose3_ReturnOriginalErrorTest.php index 7e573fc8c..06c35c825 100644 --- a/tests/SpecTests/RetryableWrites/Prose3_ReturnOriginalErrorTest.php +++ b/tests/SpecTests/RetryableWrites/Prose3_ReturnOriginalErrorTest.php @@ -8,13 +8,14 @@ use MongoDB\Driver\Monitoring\CommandSubscriber; use MongoDB\Driver\Monitoring\CommandSucceededEvent; use MongoDB\Tests\SpecTests\FunctionalTestCase; +use PHPUnit\Framework\Attributes\Group; /** * Prose test 3: Return Original Error * * @see https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md - * @group serverless */ +#[Group('serverless')] class Prose3_ReturnOriginalErrorTest extends FunctionalTestCase { public const NOT_WRITABLE_PRIMARY = 10107; diff --git a/tests/SpecTests/SearchIndexSpecTest.php b/tests/SpecTests/SearchIndexSpecTest.php index d5ed619ef..31a8a85a6 100644 --- a/tests/SpecTests/SearchIndexSpecTest.php +++ b/tests/SpecTests/SearchIndexSpecTest.php @@ -6,6 +6,7 @@ use MongoDB\Collection; use MongoDB\Model\CachingIterator; use MongoDB\Tests\FunctionalTestCase; +use PHPUnit\Framework\Attributes\Group; use function bin2hex; use function count; @@ -19,8 +20,8 @@ * Functional tests for the Atlas Search index management. * * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#search-index-management-helpers - * @group atlas */ +#[Group('atlas')] class SearchIndexSpecTest extends FunctionalTestCase { private const WAIT_TIMEOUT_SEC = 300; diff --git a/tests/TestCase.php b/tests/TestCase.php index 07f8219ed..c8c7db09d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -6,8 +6,6 @@ use MongoDB\BSON\Document; use MongoDB\BSON\PackedArray; use MongoDB\Codec\Codec; -use MongoDB\Codec\DecodeIfSupported; -use MongoDB\Codec\EncodeIfSupported; use MongoDB\Driver\ReadConcern; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\WriteConcern; @@ -161,13 +159,18 @@ final public static function provideInvalidStringValues(): array return self::wrapValuesForDataProvider(self::getInvalidStringValues()); } - protected function assertDeprecated(callable $execution) + protected function assertDeprecated(callable $execution): mixed + { + return $this->assertError(E_USER_DEPRECATED | E_DEPRECATED, $execution); + } + + protected function assertError(int $levels, callable $execution): mixed { $errors = []; set_error_handler(function ($errno, $errstr) use (&$errors): void { $errors[] = $errstr; - }, E_USER_DEPRECATED | E_DEPRECATED); + }, $levels); try { $result = call_user_func($execution); @@ -205,7 +208,7 @@ protected function getCollectionName(): string { $class = new ReflectionClass($this); - return sprintf('%s.%s', $class->getShortName(), hash('crc32b', $this->getName())); + return sprintf('%s.%s', $class->getShortName(), hash('xxh3', $this->name())); } /** @@ -239,34 +242,7 @@ protected static function getInvalidObjectValues(bool $includeNull = false): arr protected static function getInvalidDocumentCodecValues(): array { - $codec = new class implements Codec { - use DecodeIfSupported; - use EncodeIfSupported; - - public function canDecode(mixed $value): bool - { - return true; - } - - public function decode(mixed $value): mixed - { - return $value; - } - - public function canEncode(mixed $value): bool - { - return true; - } - - public function encode(mixed $value): mixed - { - return $value; - } - }; - // @fixme: createStub can be called statically in PHPUnit 10 - // $codec = self::createStub(Codec::class); - - return [123, 3.14, 'foo', true, [], new stdClass(), $codec]; + return [123, 3.14, 'foo', true, [], new stdClass(), self::createStub(Codec::class)]; } /** diff --git a/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php b/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php index 248a989b3..dd3c6e3e3 100644 --- a/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php +++ b/tests/UnifiedSpecTests/Constraint/IsBsonTypeTest.php @@ -17,6 +17,7 @@ use MongoDB\Model\BSONArray; use MongoDB\Model\BSONDocument; use MongoDB\Tests\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Constraint\Constraint; use PHPUnit\Framework\ExpectationFailedException; use stdClass; @@ -27,7 +28,7 @@ class IsBsonTypeTest extends TestCase { - /** @dataProvider provideTypes */ + #[DataProvider('provideTypes')] public function testConstraint($type, $value): void { $this->assertResult(true, new IsBsonType($type), $value, $this->dataName() . ' is ' . $type); @@ -76,7 +77,7 @@ public static function provideTypes() ]; } - /** @dataProvider provideTypes */ + #[DataProvider('provideTypes')] public function testAny($type, $value): void { $this->assertResult(true, IsBsonType::any(), $value, $this->dataName() . ' is a BSON type'); diff --git a/tests/UnifiedSpecTests/Constraint/Matches.php b/tests/UnifiedSpecTests/Constraint/Matches.php index 4346b6dc8..e1b659a3e 100644 --- a/tests/UnifiedSpecTests/Constraint/Matches.php +++ b/tests/UnifiedSpecTests/Constraint/Matches.php @@ -13,6 +13,7 @@ use RuntimeException; use SebastianBergmann\Comparator\ComparisonFailure; use SebastianBergmann\Comparator\Factory; +use SebastianBergmann\Exporter\Exporter; use function array_keys; use function count; @@ -82,14 +83,14 @@ public function evaluate($other, $description = '', $returnResult = false): ?boo } catch (RuntimeException $e) { /* This will generally catch internal errors from failAt(), which * include a key path to pinpoint the failure. */ + $exporter = new Exporter(); $this->lastFailure = new ComparisonFailure( $this->value, $other, /* TODO: Improve the exporter to canonicalize documents by * sorting keys and remove spl_object_hash from output. */ - $this->exporter()->export($this->value), - $this->exporter()->export($other), - false, + $exporter->export($this->value), + $exporter->export($other), $e->getMessage(), ); } @@ -256,7 +257,7 @@ private function assertMatchesOperator(BSONDocument $operator, $actual, string $ $constraint = IsBsonType::anyOf(...(array) $operator['$$type']); if (! $constraint->evaluate($actual, '', true)) { - self::failAt(sprintf('%s is not an expected BSON type: %s', $this->exporter()->shortenedExport($actual), implode(', ', $types)), $keyPath); + self::failAt(sprintf('%s is not an expected BSON type: %s', (new Exporter())->shortenedExport($actual), implode(', ', (array) $operator['$$type'])), $keyPath); } return; @@ -284,7 +285,7 @@ private function assertMatchesOperator(BSONDocument $operator, $actual, string $ assertIsString($actual); if ($actual !== hex2bin($operator['$$matchesHexBytes'])) { - self::failAt(sprintf('%s does not match expected hex bytes: %s', $this->exporter()->shortenedExport($actual), $operator['$$matchesHexBytes']), $keyPath); + self::failAt(sprintf('%s does not match expected hex bytes: %s', (new Exporter())->shortenedExport($actual), $operator['$$matchesHexBytes']), $keyPath); } return; @@ -349,7 +350,7 @@ protected function matches($other): bool public function toString(): string { - return 'matches ' . $this->exporter()->export($this->value); + return 'matches ' . (new Exporter())->export($this->value); } /** @psalm-return never-return */ diff --git a/tests/UnifiedSpecTests/Constraint/MatchesTest.php b/tests/UnifiedSpecTests/Constraint/MatchesTest.php index 472fdd1d1..0de7aa9cb 100644 --- a/tests/UnifiedSpecTests/Constraint/MatchesTest.php +++ b/tests/UnifiedSpecTests/Constraint/MatchesTest.php @@ -5,6 +5,7 @@ use MongoDB\BSON\Binary; use MongoDB\Tests\FunctionalTestCase; use MongoDB\Tests\UnifiedSpecTests\EntityMap; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\ExpectationFailedException; use stdClass; @@ -170,7 +171,7 @@ public function testOperatorSessionLsid(): void $this->assertResult(false, $c, ['x' => 1], 'session LSID does not match (embedded)'); } - /** @dataProvider errorMessageProvider */ + #[DataProvider('errorMessageProvider')] public function testErrorMessages($expectedMessageRegex, Matches $constraint, $actualValue): void { try { @@ -253,7 +254,7 @@ public static function errorMessageProvider() ]; } - /** @dataProvider operatorErrorMessageProvider */ + #[DataProvider('operatorErrorMessageProvider')] public function testOperatorSyntaxValidation($expectedMessage, Matches $constraint): void { $this->expectException(ExpectationFailedException::class); diff --git a/tests/UnifiedSpecTests/UnifiedSpecTest.php b/tests/UnifiedSpecTests/UnifiedSpecTest.php index 6b6031948..f34696be3 100644 --- a/tests/UnifiedSpecTests/UnifiedSpecTest.php +++ b/tests/UnifiedSpecTests/UnifiedSpecTest.php @@ -5,6 +5,8 @@ use Exception; use Generator; use MongoDB\Tests\FunctionalTestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\SkippedTest; use PHPUnit\Framework\Warning; @@ -221,10 +223,8 @@ public function setUp(): void } } - /** - * @dataProvider provideAtlasDataLakeTests - * @group atlas-data-lake - */ + #[DataProvider('provideAtlasDataLakeTests')] + #[Group('atlas-data-lake')] public function testAtlasDataLake(UnifiedTestCase $test): void { if (! $this->isAtlasDataLake()) { @@ -239,10 +239,8 @@ public static function provideAtlasDataLakeTests(): Generator return self::provideTests(__DIR__ . '/atlas-data-lake/*.json'); } - /** - * @dataProvider provideChangeStreamsTests - * @group serverless - */ + #[DataProvider('provideChangeStreamsTests')] + #[Group('serverless')] public function testChangeStreams(UnifiedTestCase $test): void { self::$runner->run($test); @@ -253,11 +251,9 @@ public static function provideChangeStreamsTests(): Generator return self::provideTests(__DIR__ . '/change-streams/*.json'); } - /** - * @dataProvider provideClientSideEncryptionTests - * @group csfle - * @group serverless - */ + #[DataProvider('provideClientSideEncryptionTests')] + #[Group('csfle')] + #[Group('serverless')] public function testClientSideEncryption(UnifiedTestCase $test): void { self::$runner->run($test); @@ -268,10 +264,8 @@ public static function provideClientSideEncryptionTests(): Generator return self::provideTests(__DIR__ . '/client-side-encryption/*.json'); } - /** - * @dataProvider provideCollectionManagementTests - * @group serverless - */ + #[DataProvider('provideCollectionManagementTests')] + #[Group('serverless')] public function testCollectionManagement(UnifiedTestCase $test): void { self::$runner->run($test); @@ -282,10 +276,8 @@ public static function provideCollectionManagementTests(): Generator return self::provideTests(__DIR__ . '/collection-management/*.json'); } - /** - * @dataProvider provideCommandMonitoringTests - * @group serverless - */ + #[DataProvider('provideCommandMonitoringTests')] + #[Group('serverless')] public function testCommandMonitoring(UnifiedTestCase $test): void { self::$runner->run($test); @@ -296,10 +288,8 @@ public static function provideCommandMonitoringTests(): Generator return self::provideTests(__DIR__ . '/command-monitoring/*.json'); } - /** - * @dataProvider provideCrudTests - * @group serverless - */ + #[DataProvider('provideCrudTests')] + #[Group('serverless')] public function testCrud(UnifiedTestCase $test): void { self::$runner->run($test); @@ -310,10 +300,8 @@ public static function provideCrudTests(): Generator return self::provideTests(__DIR__ . '/crud/*.json'); } - /** - * @dataProvider provideGridFSTests - * @group serverless - */ + #[DataProvider('provideGridFSTests')] + #[Group('serverless')] public function testGridFS(UnifiedTestCase $test): void { self::$runner->run($test); @@ -324,10 +312,8 @@ public static function provideGridFSTests(): Generator return self::provideTests(__DIR__ . '/gridfs/*.json'); } - /** - * @dataProvider provideLoadBalancers - * @group serverless - */ + #[DataProvider('provideLoadBalancers')] + #[Group('serverless')] public function testLoadBalancers(UnifiedTestCase $test): void { self::$runner->run($test); @@ -338,7 +324,7 @@ public static function provideLoadBalancers(): Generator return self::provideTests(__DIR__ . '/load-balancers/*.json'); } - /** @dataProvider provideReadWriteConcernTests */ + #[DataProvider('provideReadWriteConcernTests')] public function testReadWriteConcern(UnifiedTestCase $test): void { self::$runner->run($test); @@ -349,10 +335,8 @@ public static function provideReadWriteConcernTests(): Generator return self::provideTests(__DIR__ . '/read-write-concern/*.json'); } - /** - * @dataProvider provideRetryableReadsTests - * @group serverless - */ + #[DataProvider('provideRetryableReadsTests')] + #[Group('serverless')] public function testRetryableReads(UnifiedTestCase $test): void { self::$runner->run($test); @@ -363,10 +347,8 @@ public static function provideRetryableReadsTests(): Generator return self::provideTests(__DIR__ . '/retryable-reads/*.json'); } - /** - * @dataProvider provideRetryableWritesTests - * @group serverless - */ + #[DataProvider('provideRetryableWritesTests')] + #[Group('serverless')] public function testRetryableWrites(UnifiedTestCase $test): void { self::$runner->run($test); @@ -377,10 +359,8 @@ public static function provideRetryableWritesTests(): Generator return self::provideTests(__DIR__ . '/retryable-writes/*.json'); } - /** - * @dataProvider provideRunCommandTests - * @group serverless - */ + #[DataProvider('provideRunCommandTests')] + #[Group('serverless')] public function testRunCommand(UnifiedTestCase $test): void { self::$runner->run($test); @@ -391,10 +371,8 @@ public static function provideRunCommandTests(): Generator return self::provideTests(__DIR__ . '/run-command/*.json'); } - /** - * @dataProvider provideSessionsTests - * @group serverless - */ + #[DataProvider('provideSessionsTests')] + #[Group('serverless')] public function testSessions(UnifiedTestCase $test): void { self::$runner->run($test); @@ -405,10 +383,8 @@ public static function provideSessionsTests(): Generator return self::provideTests(__DIR__ . '/sessions/*.json'); } - /** - * @dataProvider provideTransactionsTests - * @group serverless - */ + #[DataProvider('provideTransactionsTests')] + #[Group('serverless')] public function testTransactions(UnifiedTestCase $test): void { self::$runner->run($test); @@ -419,7 +395,7 @@ public static function provideTransactionsTests(): Generator return self::provideTests(__DIR__ . '/transactions/*.json'); } - /** @dataProvider provideTransactionsConvenientApiTests */ + #[DataProvider('provideTransactionsConvenientApiTests')] public function testTransactionsConvenientApi(UnifiedTestCase $test): void { self::$runner->run($test); @@ -430,11 +406,9 @@ public static function provideTransactionsConvenientApiTests(): Generator return self::provideTests(__DIR__ . '/transactions-convenient-api/*.json'); } - /** - * @dataProvider provideVersionedApiTests - * @group serverless - * @group versioned-api - */ + #[DataProvider('provideVersionedApiTests')] + #[Group('serverless')] + #[Group('versioned-api')] public function testVersionedApi(UnifiedTestCase $test): void { self::$runner->run($test); @@ -445,7 +419,7 @@ public static function provideVersionedApiTests(): Generator return self::provideTests(__DIR__ . '/versioned-api/*.json'); } - /** @dataProvider providePassingTests */ + #[DataProvider('providePassingTests')] public function testPassingTests(UnifiedTestCase $test): void { self::$runner->run($test); @@ -456,7 +430,7 @@ public static function providePassingTests(): Generator yield from self::provideTests(__DIR__ . '/valid-pass/*.json'); } - /** @dataProvider provideFailingTests */ + #[DataProvider('provideFailingTests')] public function testFailingTests(UnifiedTestCase $test): void { // Cannot use expectException(), as it ignores PHPUnit Exceptions @@ -494,7 +468,7 @@ public static function provideFailingTests(): Generator yield from self::provideTests(__DIR__ . '/valid-fail/*.json'); } - /** @dataProvider provideIndexManagementTests */ + #[DataProvider('provideIndexManagementTests')] public function testIndexManagement(UnifiedTestCase $test): void { if (self::isAtlas()) {