SERVER-53888: Remove CST code (#25976)

GitOrigin-RevId: 65399cc37ddf15d2df63c568bd1d6f8b38a39bbb
This commit is contained in:
Matt Olma 2024-08-14 11:07:38 -07:00 committed by MongoDB Bot
parent fe18adb137
commit 3aeb26bdd0
52 changed files with 2 additions and 32930 deletions

4
.github/CODEOWNERS vendored
View File

@ -301,7 +301,6 @@ WORKSPACE.bazel @10gen/devprod-build
/src/mongo/db/commands/query_cmd/**/* @10gen/query
/src/mongo/db/commands/query_cmd/**/analyze_cmd* @10gen/query-optimization
/src/mongo/db/commands/query_cmd/**/change_stream_state_command.cpp @10gen/query-execution
/src/mongo/db/commands/query_cmd/**/cst_command.cpp @10gen/query-optimization
/src/mongo/db/commands/query_cmd/**/index_filter_commands.* @10gen/query-optimization
/src/mongo/db/commands/query_cmd/**/explain.idl @10gen/query-optimization
/src/mongo/db/commands/query_cmd/**/explain_test.cpp @10gen/query-optimization
@ -312,9 +311,6 @@ WORKSPACE.bazel @10gen/devprod-build
# The following patterns are parsed from ./src/mongo/db/concurrency/OWNERS.yml
/src/mongo/db/concurrency/**/exception_util* @10gen/server-service-architecture
# The following patterns are parsed from ./src/mongo/db/cst/OWNERS.yml
/src/mongo/db/cst/**/* @10gen/query-optimization
# The following patterns are parsed from ./src/mongo/db/exec/OWNERS.yml
/src/mongo/db/exec/**/* @10gen/query-execution
/src/mongo/db/exec/**/*timeseries* @10gen/query-integration

View File

@ -29,8 +29,6 @@ def is_interesting_file(file_name: str) -> bool:
and not file_name.startswith("src/mongo/gotools/")
and not file_name.startswith("src/streams/third_party")
and not file_name.startswith("src/mongo/db/modules/enterprise/src/streams/third_party")
# TODO SERVER-49805: These files should be generated at compile time.
and not file_name == "src/mongo/db/cst/parser_gen.cpp"
) and FILES_RE.search(file_name)

View File

@ -40,8 +40,6 @@ selector:
- build/install/bin/chunk_manager_refresh_bm*
- build/install/bin/migration_chunk_cloner_source_legacy_bm*
- build/install/bin/sharding_write_router_bm*
# These benchmarks included in the benchmarks_cst.yml test suite are disabled under SERVER-64949.
# - build/install/bin/cst_bm*
# These benchmarks are being run as part of the benchmarks_bsoncolumn.yml test suite.
- build/install/bin/bsoncolumn_bm*
- build/install/bin/simple8b_bm*

View File

@ -42,8 +42,6 @@ selector:
- build/install/bin/chunk_manager_refresh_bm*
- build/install/bin/migration_chunk_cloner_source_legacy_bm*
- build/install/bin/sharding_write_router_bm*
# These benchmarks included in the benchmarks_cst.yml test suite are disabled under SERVER-64949.
# - build/install/bin/cst_bm*
# These benchmarks are being run as part of the benchmarks_bsoncolumn.yml test suite.
- build/install/bin/bsoncolumn_bm*
- build/install/bin/simple8b_bm*

View File

@ -1,810 +0,0 @@
# This test suite runs the tests in core with the internalQueryEnableCSTParser set to true to
# exercise the new parsing path for queries.
test_kind: js_test
selector:
roots:
- jstests/core/**/*.js
- jstests/fle2/**/*.js
- src/mongo/db/modules/*/jstests/fle2/**/*.js
- jstests/core_standalone/**/*.js
exclude_files:
# Transactions are not supported on MongoDB standalone nodes, so we do not run these tests in the
# 'core' suite. Instead we run them against a 1-node replica set in the 'core_txns' suite.
- jstests/core/txns/**/*.js
# Queryable encryption is not supported on standalone
- jstests/core/queryable_encryption/**/*.js
# These tests produce different error codes depending on which parser implementation.
- jstests/core/**/sort_with_meta_operator.js
# TODO SERVER-50239 Enable $jsonSchema tests against the CST.
- jstests/core/json_schema/**/*.js
# TODO SERVER-50851 Implement $sampleRate in CST.
- jstests/core/**/sample_rate.js
# TODO SERVER-48847 Enable tests with comparison operators.
- jstests/core/**/background_index_multikey.js
- jstests/core/**/wildcard_index_collation.js
- jstests/core/**/wildcard_index_hint.js
- jstests/core/**/wildcard_index_return_key.js
- jstests/core/**/wildcard_index_projection.js
- jstests/core/**/update_array_offset_positional.js
- jstests/core/**/update_arraymatch6.js
- jstests/core/**/update_arraymatch8.js
- jstests/core/**/update_min_max_examples.js
- jstests/core/**/update_modifier_pop.js
- jstests/core/**/count9.js
- jstests/core/**/find_and_modify_server6588.js
- jstests/core/**/find_and_modify_server6909.js
- jstests/core/**/find_and_modify3.js
- jstests/core/**/index6.js
- jstests/core/**/mr_scope.js
- jstests/core/**/profile_hide_index.js
- jstests/core/**/profile_query_hash.js
- jstests/core/**/agg_hint.js
- jstests/core/**/all2.js
- jstests/core/**/all3.js
- jstests/core/**/and.js
- jstests/core/**/and.js
- jstests/core/**/and_or_index_sort.js
- jstests/core/**/and_or_nested.js
- jstests/core/**/andor.js
- jstests/core/**/array1.js
- jstests/core/**/array4.js
- jstests/core/**/array_comparison_correctness.js
- jstests/core/**/array_index_and_nonIndex_consistent.js
- jstests/core/**/array_match1.js
- jstests/core/**/array_match3.js
- jstests/core/**/array_match4.js
- jstests/core/**/arrayfind1.js
- jstests/core/**/arrayfind10.js
- jstests/core/**/arrayfind2.js
- jstests/core/**/arrayfind3.js
- jstests/core/**/arrayfind4.js
- jstests/core/**/arrayfind6.js
- jstests/core/**/arrayfind7.js
- jstests/core/**/arrayfind8.js
- jstests/core/**/arrayfind9.js
- jstests/core/**/arrayfinda.js
- jstests/core/**/arrayfindb.js
- jstests/core/**/background_unique_indexes.js
- jstests/core/**/batch_size.js
- jstests/core/**/batch_write_command_update.js
- jstests/core/**/bench_test1.js
- jstests/core/**/bench_test2.js
- jstests/core/**/bindata_eq.js
- jstests/core/**/bindata_indexonly.js
- jstests/core/write/delete/batched_multi_deletes_a.js
- jstests/core/write/delete/batched_multi_deletes_id.js
- jstests/core/**/capped_update.js
- jstests/core/**/collation.js
- jstests/core/**/collation_find_and_modify.js
- jstests/core/**/collation_plan_cache.js
- jstests/core/**/collation_update.js
- jstests/core/**/command_json_schema_field.js
- jstests/core/**/command_let_variables.js
- jstests/core/**/computed_projections.js
- jstests/core/**/connection_string_validation.js
- jstests/core/**/count11.js
- jstests/core/**/count4.js
- jstests/core/**/count5.js
- jstests/core/**/count7.js
- jstests/core/**/countb.js
- jstests/core/**/countc.js
- jstests/core/**/covered_index_compound_1.js
- jstests/core/**/covered_index_negative_1.js
- jstests/core/**/covered_index_simple_1.js
- jstests/core/**/covered_index_simple_2.js
- jstests/core/**/covered_index_simple_3.js
- jstests/core/**/covered_index_simple_id.js
- jstests/core/**/covered_index_sort_1.js
- jstests/core/**/covered_index_sort_no_fetch_optimization.js
- jstests/core/**/covered_multikey.js
- jstests/core/**/coveredIndex1.js
- jstests/core/**/crud_api.js
- jstests/core/**/currentop_cursors.js
- jstests/core/**/cursor3.js
- jstests/core/**/cursor4.js
- jstests/core/**/cursor5.js
- jstests/core/**/cursor6.js
- jstests/core/**/cursor7.js
- jstests/core/**/date2.js
- jstests/core/**/date3.js
- jstests/core/**/delx.js
- jstests/core/**/distinct_semantics.js
- jstests/core/**/distinct3.js
- jstests/core/**/distinct_index1.js
- jstests/core/**/distinct_multikey.js
- jstests/core/**/distinct_multikey_dotted_path.js
- jstests/core/**/distinct_with_hashed_index.js
- jstests/core/**/doc_validation.js
- jstests/core/**/doc_validation_invalid_validators.js
- jstests/core/**/doc_validation_options.js
- jstests/core/**/dotted_path_in_null.js
- jstests/core/**/elemmatch_or_pushdown.js
- jstests/core/**/elemmatch_projection.js
- jstests/core/**/positional_projection.js
- jstests/core/**/exists.js
- jstests/core/**/existsa.js
- jstests/core/**/explain1.js
- jstests/core/**/explain4.js
- jstests/core/**/explain5.js
- jstests/core/**/explain6.js
- jstests/core/**/explain_execution_error.js
- jstests/core/**/explain_find.js
- jstests/core/**/explain_find_and_modify.js
- jstests/core/**/explain_large_bounds.js
- jstests/core/**/explain_multi_plan.js
- jstests/core/**/explain_server_params.js
- jstests/core/**/explain_shell_helpers.js
- jstests/core/**/explain_sort_type.js
- jstests/core/**/explain_winning_plan.js
- jstests/core/**/merge_sort_collation.js
- jstests/core/**/explode_for_sort_fetch.js
- jstests/core/**/expr.js
- jstests/core/**/expr_index_use.js
- jstests/core/**/expr_or_pushdown.js
- jstests/core/**/expr_valid_positions.js
- jstests/core/**/field_name_validation.js
- jstests/core/**/filemd5.js
- jstests/core/**/find6.js
- jstests/core/**/find7.js
- jstests/core/**/find8.js
- jstests/core/**/find_and_modify.js
- jstests/core/**/find_and_modify2.js
- jstests/core/**/find_and_modify4.js
- jstests/core/**/find_and_modify_concurrent_update.js
- jstests/core/**/find_and_modify_empty_coll.js
- jstests/core/**/find_and_modify_metrics.js
- jstests/core/**/find_and_modify_server6226.js
- jstests/core/**/find_and_modify_server6865.js
- jstests/core/**/find_covered_projection.js
- jstests/core/**/find_dedup.js
- jstests/core/**/find_project_sort.js
- jstests/core/**/finda.js
- jstests/core/**/fts_mix.js
- jstests/core/**/geo2.js
- jstests/core/**/geo3.js
- jstests/core/**/geo6.js
- jstests/core/**/geo9.js
- jstests/core/**/geo_2d_explain.js
- jstests/core/**/geo_2d_trailing_fields.js
- jstests/core/**/geo_2d_with_geojson_point.js
- jstests/core/**/geo_array2.js
- jstests/core/**/geo_big_polygon3.js
- jstests/core/**/geo_borders.js
- jstests/core/**/geo_center_sphere2.js
- jstests/core/**/geo_circle2.js
- jstests/core/**/geo_distinct.js
- jstests/core/**/geo_exactfetch.js
- jstests/core/**/geo_max.js
- jstests/core/**/geo_mindistance.js
- jstests/core/**/geo_mindistance_boundaries.js
- jstests/core/**/geo_multikey0.js
- jstests/core/**/geo_multinest1.js
- jstests/core/**/geo_near_random1.js
- jstests/core/**/geo_near_random2.js
- jstests/core/**/geo_nearwithin.js
- jstests/core/**/geo_oob_sphere.js
- jstests/core/**/geo_operator_crs.js
- jstests/core/**/geo_or.js
- jstests/core/**/geo_queryoptimizer.js
- jstests/core/**/geo_regex0.js
- jstests/core/**/geo_regex0.js
- jstests/core/**/geo_s2explain.js
- jstests/core/**/geo_s2index.js
- jstests/core/**/geo_s2near.js
- jstests/core/**/geo_s2near_equator_opposite.js
- jstests/core/**/geo_s2nearComplex.js
- jstests/core/**/geo_s2nearcorrect.js
- jstests/core/**/geo_s2nongeoarray.js
- jstests/core/**/geo_s2nonstring.js
- jstests/core/**/geo_s2nopoints.js
- jstests/core/**/geo_small_large.js
- jstests/core/**/geo_sort1.js
- jstests/core/**/geo_uniqueDocs2.js
- jstests/core/**/geo_update.js
- jstests/core/**/geo_update1.js
- jstests/core/**/geo_update2.js
- jstests/core/**/geo_update_btree2.js
- jstests/core/**/geo_update_dedup.js
- jstests/core/**/geo_validate.js
- jstests/core/**/geo_withinquery.js
- jstests/core/**/geoa.js
- jstests/core/**/geoc.js
- jstests/core/**/geof.js
- jstests/core/**/getmore_invalidated_documents.js
- jstests/core/**/grow_hash_table.js
- jstests/core/**/hashed_index_covered_queries.js
- jstests/core/**/hashed_index_queries.js
- jstests/core/**/hashed_index_queries_with_logical_operators.js
- jstests/core/**/hashed_index_sort.js
- jstests/core/**/hashed_index_with_arrays.js
- jstests/core/**/hashed_partial_and_sparse_index.js
- jstests/core/**/hint1.js
- jstests/core/**/id1.js
- jstests/core/**/idhack.js
- jstests/core/**/in.js
- jstests/core/**/in2.js
- jstests/core/**/in3.js
- jstests/core/**/in4.js
- jstests/core/**/in5.js
- jstests/core/**/in6.js
- jstests/core/**/in7.js
- jstests/core/**/in8.js
- jstests/core/**/in_with_mixed_values.js
- jstests/core/**/inc-SERVER-7446.js
- jstests/core/**/inc1.js
- jstests/core/**/inc2.js
- jstests/core/**/inc3.js
- jstests/core/**/index1.js
- jstests/core/**/index13.js
- jstests/core/**/index2.js
- jstests/core/**/index4.js
- jstests/core/**/index_arr2.js
- jstests/core/**/index_bigkeys.js
- jstests/core/**/index_bounds_code.js
- jstests/core/**/index_bounds_maxkey.js
- jstests/core/**/index_bounds_minkey.js
- jstests/core/**/index_bounds_number_edge_cases.js
- jstests/core/**/index_bounds_object.js
- jstests/core/**/index_bounds_pipe.js
- jstests/core/**/index_bounds_timestamp.js
- jstests/core/**/index_check3.js
- jstests/core/**/index_check5.js
- jstests/core/**/index_check6.js
- jstests/core/**/index_check7.js
- jstests/core/**/index_decimal.js
- jstests/core/**/index_filter_commands.js
- jstests/core/**/index_filter_on_hidden_index.js
- jstests/core/**/index_multiple_compatibility.js
- jstests/core/**/index_partial_2dsphere.js
- jstests/core/**/index_partial_create_drop.js
- jstests/core/**/index_partial_read_ops.js
- jstests/core/**/index_partial_validate.js
- jstests/core/**/index_signature.js
- jstests/core/**/index_sort_within_multiple_point_ranges.js
- jstests/core/**/index_stats.js
- jstests/core/**/indexc.js
- jstests/core/**/indexg.js
- jstests/core/**/indexj.js
- jstests/core/**/indexl.js
- jstests/core/**/indexm.js
- jstests/core/**/indexn.js
- jstests/core/**/indexr.js
- jstests/core/**/indexs.js
- jstests/core/**/indexs.js
- jstests/core/**/indexu.js
- jstests/core/**/json1.js
- jstests/core/json_schema/bsontype.js
- jstests/core/json_schema/misc_validation.js
- jstests/core/**/list_collections1.js
- jstests/core/**/list_collections_filter.js
- jstests/core/**/list_databases.js
- jstests/core/**/list_databases.js
- jstests/core/**/list_namespaces_invalidation.js
- jstests/core/**/list_sessions.js
- jstests/core/**/min_max_key.js
- jstests/core/**/mod.js
- jstests/core/**/mod_with_where.js
- jstests/core/**/mod_overflow.js
- jstests/core/**/mr_correctness.js
- jstests/core/**/mr_merge.js
- jstests/core/**/mr_multikey_deduping.js
- jstests/core/**/mr_reduce.js
- jstests/core/**/mr_sort.js
- jstests/core/**/multi.js
- jstests/core/**/multikey_geonear.js
- jstests/core/**/nan.js
- jstests/core/**/ne1.js
- jstests/core/**/ne2.js
- jstests/core/**/ne3.js
- jstests/core/**/ne_array.js
- jstests/core/**/nin.js
- jstests/core/**/nin2.js
- jstests/core/**/not1.js
- jstests/core/**/not2.js
- jstests/core/**/not3.js
- jstests/core/**/null_query_semantics.js
- jstests/core/**/null_query_semantics.js
- jstests/core/**/objectfind.js
- jstests/core/**/opcounters_write_cmd.js
- jstests/core/**/operation_latency_histogram.js
- jstests/core/**/optimized_match_explain.js
- jstests/core/**/or1.js
- jstests/core/**/or3.js
- jstests/core/**/or5.js
- jstests/core/**/or7.js
- jstests/core/**/or7.js
- jstests/core/**/or8.js
- jstests/core/**/or9.js
- jstests/core/**/or_inexact.js
- jstests/core/**/or_inexact.js
- jstests/core/**/or_to_in.js
- jstests/core/**/ora.js
- jstests/core/**/orb.js
- jstests/core/**/orc.js
- jstests/core/**/ore.js
- jstests/core/**/orp.js
- jstests/core/**/plan_cache_clear.js
- jstests/core/**/plan_cache_shell_helpers.js
- jstests/core/**/profile1.js
- jstests/core/**/profile2.js
- jstests/core/**/profile3.js
- jstests/core/**/profile_agg.js
- jstests/core/**/profile_count.js
- jstests/core/**/profile_delete.js
- jstests/core/**/profile_distinct.js
- jstests/core/**/profile_find.js
- jstests/core/**/profile_findandmodify.js
- jstests/core/**/profile_getmore.js
- jstests/core/**/profile_list_indexes.js
- jstests/core/**/profile_mapreduce.js
- jstests/core/**/profile_sampling.js
- jstests/core/**/profile_update.js
- jstests/core/**/proj_key1.js
- jstests/core/**/projection_meta_index_key.js
- jstests/core/**/pull2.js
- jstests/core/**/pull_remove1.js
- jstests/core/**/pullall2.js
- jstests/core/**/queryoptimizera.js
- jstests/core/**/ref2.js
- jstests/core/**/regex3.js
- jstests/core/**/regex4.js
- jstests/core/**/regex4.js
- jstests/core/**/regex5.js
- jstests/core/**/regex5.js
- jstests/core/**/regex6.js
- jstests/core/**/regex6.js
- jstests/core/**/regex7.js
- jstests/core/**/regex_limit.js
- jstests/core/**/regex_not_id.js
- jstests/core/**/regexa.js
- jstests/core/**/remove2.js
- jstests/core/**/remove3.js
- jstests/core/**/remove6.js
- jstests/core/**/remove7.js
- jstests/core/**/removea.js
- jstests/core/**/removeb.js
- jstests/core/**/removec.js
- jstests/core/**/rename_operator_missing_source.js
- jstests/core/**/role_management_helpers.js
- jstests/core/**/rollback_index_drop.js
- jstests/core/**/server1470.js
- jstests/core/**/server5346.js
- jstests/core/**/server50762.js
- jstests/core/**/set7.js
- jstests/core/**/single_field_hashed_index.js
- jstests/core/**/sort1.js
- jstests/core/**/sort8.js
- jstests/core/**/sort9.js
- jstests/core/**/sort_array.js
- jstests/core/**/sortc.js
- jstests/core/**/sortd.js
- jstests/core/**/sorth.js
- jstests/core/**/sortj.js
- jstests/core/**/sortk.js
- jstests/core/**/sort_with_update_between_getmores.js
- jstests/core/**/sparse_index_supports_ne_null.js
- jstests/core/**/stages_and_hash.js
- jstests/core/**/stages_collection_scan.js
- jstests/core/**/top.js
- jstests/core/txns/abort_prepared_transaction.js
- jstests/core/txns/aggregation_in_transaction.js
- jstests/core/txns/commit_and_abort_large_prepared_transactions.js
- jstests/core/txns/commit_and_abort_large_unprepared_transactions.js
- jstests/core/txns/commit_prepared_transaction.js
- jstests/core/txns/find_and_modify_in_transaction.js
- jstests/core/txns/many_txns.js
- jstests/core/txns/multi_statement_transaction.js
- jstests/core/txns/multi_statement_transaction_using_api.js
- jstests/core/txns/multi_statement_transaction_write_error.js
- jstests/core/txns/prepare_conflict.js
- jstests/core/txns/prepared_transactions_do_not_block_non_conflicting_ddl.js
- jstests/core/txns/statement_ids_accepted.js
- jstests/core/txns/timestamped_reads_wait_for_prepare_oplog_visibility.js
- jstests/core/txns/transactions_profiling.js
- jstests/core/txns/transactions_profiling_with_drops.js
- jstests/core/txns/transactions_write_conflicts.js
- jstests/core/txns/transactions_write_conflicts_unique_indexes.js
- jstests/core/**/update2.js
- jstests/core/**/update3.js
- jstests/core/**/update5.js
- jstests/core/**/update6.js
- jstests/core/**/update7.js
- jstests/core/**/update8.js
- jstests/core/**/update9.js
- jstests/core/**/update_affects_indexes.js
- jstests/core/**/update_arrayFilters.js
- jstests/core/**/update_arraymatch1.js
- jstests/core/**/update_arraymatch2.js
- jstests/core/**/update_arraymatch3.js
- jstests/core/**/update_arraymatch7.js
- jstests/core/**/update_invalid1.js
- jstests/core/**/update_metrics.js
- jstests/core/**/update_setOnInsert.js
- jstests/core/**/update_with_pipeline.js
- jstests/core/**/updatea.js
- jstests/core/**/updateb.js
- jstests/core/**/updated.js
- jstests/core/**/updateg.js
- jstests/core/**/updateh.js
- jstests/core/**/updatel.js
- jstests/core/**/updatel.js
- jstests/core/**/updatem.js
- jstests/core/**/upsert_and.js
- jstests/core/**/upsert_fields.js
- jstests/core/**/upsert_shell.js
- jstests/core/**/useindexonobjgtlt.js
- jstests/core/**/user_management_helpers.js
- jstests/core/**/verify_update_mods.js
- jstests/core/views/views_aggregation.js
- jstests/core/views/invalid_system_views.js
- jstests/core/views/views_all_commands.js
- jstests/core/views/views_basic.js
- jstests/core/views/views_change.js
- jstests/core/views/views_collation.js
- jstests/core/views/views_count.js
- jstests/core/views/views_distinct.js
- jstests/core/views/views_validation.js
- jstests/core/**/where_system_js.js
- jstests/core/**/wildcard_and_text_indexes.js
- jstests/core/**/wildcard_index_basic_index_bounds.js
- jstests/core/**/wildcard_index_cached_plans.js
- jstests/core/**/wildcard_index_count.js
- jstests/core/**/wildcard_index_covered_queries.js
- jstests/core/**/wildcard_index_distinct_scan.js
- jstests/core/**/wildcard_index_empty_arrays.js
- jstests/core/**/wildcard_index_equality_to_empty_obj.js
- jstests/core/**/wildcard_index_multikey.js
- jstests/core/**/wildcard_index_nonblocking_sort.js
- jstests/core/**/wildcard_index_partial_index.js
- jstests/core/**/wildcard_index_validindex.js
- jstests/core/**/write_commands_reject_unknown_fields.js
- jstests/core/**/write_result.js
# TODO SERVER-48853 Enable tests with array operators.
- jstests/core/**/all.js
- jstests/core/**/all2.js
- jstests/core/**/all3.js
- jstests/core/**/all4.js
- jstests/core/**/all4.js
- jstests/core/**/all5.js
- jstests/core/**/all5.js
- jstests/core/**/always_true_false.js
- jstests/core/**/arrayfind1.js
- jstests/core/**/arrayfind2.js
- jstests/core/**/arrayfind2.js
- jstests/core/**/arrayfind3.js
- jstests/core/**/arrayfind5.js
- jstests/core/**/arrayfind6.js
- jstests/core/**/arrayfind7.js
- jstests/core/**/arrayfind8.js
- jstests/core/**/arrayfind9.js
- jstests/core/**/arrayfinda.js
- jstests/core/**/arrayfindb.js
- jstests/core/**/dbref2.js
- jstests/core/**/dbref3.js
- jstests/core/**/elemmatch_object.js
- jstests/core/**/elemmatch_or_pushdown.js
- jstests/core/**/elemmatch_value.js
- jstests/core/**/elemmatch_projection.js
- jstests/core/**/positional_projection.js
- jstests/core/**/existsa.js
- jstests/core/**/expr.js
- jstests/core/**/expr_valid_positions.js
- jstests/core/**/find_and_modify_server6865.js
- jstests/core/**/find_size.js
- jstests/core/**/fts_trailing_fields.js
- jstests/core/**/geo_2d_trailing_fields.js
- jstests/core/**/idhack.js
- jstests/core/**/in7.js
- jstests/core/**/index13.js
- jstests/core/**/index_check2.js
- jstests/core/**/indexl.js
- jstests/core/json_schema/misc_validation.js
- jstests/core/**/ne_array.js
- jstests/core/**/nin.js
- jstests/core/**/not2.js
- jstests/core/**/not2.js
- jstests/core/**/not2.js
- jstests/core/**/null_query_semantics.js
- jstests/core/**/or5.js
- jstests/core/**/or_inexact.js
- jstests/core/**/positional_projection_multiple_array_fields.js
- jstests/core/**/regex.js
- jstests/core/**/regex5.js
- jstests/core/**/sparse_index_supports_ne_null.js
- jstests/core/**/update_arraymatch5.js
- jstests/core/**/upsert_fields.js
- jstests/core/**/upsert_fields.js
- jstests/core/views/views_find.js
- jstests/core/**/where1.js
- jstests/core/**/wildcard_index_covered_queries.js
- jstests/core/**/wildcard_index_multikey.js
- jstests/core/**/wildcard_index_nonblocking_sort.js
# TODO SERVER-48851 Enable tests with evaluation operators.
- jstests/core/**/and.js
- jstests/core/**/and3.js
- jstests/core/**/arrayfind8.js
- jstests/core/**/collation.js
- jstests/core/**/command_let_variables.js
- jstests/core/**/constructors.js
- jstests/core/**/count.js
- jstests/core/**/count10.js
- jstests/core/**/count_plan_summary.js
- jstests/core/**/counta.js
- jstests/core/**/countb.js
- jstests/core/**/countc.js
- jstests/core/**/cursora.js
- jstests/core/**/depth_limit.js
- jstests/core/**/doc_validation.js
- jstests/core/**/doc_validation_invalid_validators.js
- jstests/core/**/elemmatch_projection.js
- jstests/core/**/positional_projection.js
- jstests/core/**/explain_sort_type.js
- jstests/core/**/expr.js
- jstests/core/**/expr_index_use.js
- jstests/core/**/expr_or_pushdown.js
- jstests/core/**/expr_valid_positions.js
- jstests/core/**/find6.js
- jstests/core/**/find_and_modify_concurrent_update.js
- jstests/core/**/find_and_modify_where.js
- jstests/core/**/fts1.js
- jstests/core/**/fts2.js
- jstests/core/**/fts3.js
- jstests/core/**/fts4.js
- jstests/core/**/fts5.js
- jstests/core/**/fts6.js
- jstests/core/**/fts_array.js
- jstests/core/**/fts_blog.js
- jstests/core/**/fts_blogwild.js
- jstests/core/**/fts_casesensitive.js
- jstests/core/**/fts_diacritic_and_caseinsensitive.js
- jstests/core/**/fts_diacritic_and_casesensitive.js
- jstests/core/**/fts_diacriticsensitive.js
- jstests/core/**/fts_dotted_prefix_fields.js
- jstests/core/**/fts_explain.js
- jstests/core/**/fts_find_and_modify.js
- jstests/core/**/fts_index.js
- jstests/core/**/fts_index2.js
- jstests/core/**/fts_index3.js
- jstests/core/**/fts_index_version1.js
- jstests/core/**/fts_index_version2.js
- jstests/core/**/fts_index_wildcard_and_weight.js
- jstests/core/**/fts_mix.js
- jstests/core/**/fts_partition1.js
- jstests/core/**/fts_phrase.js
- jstests/core/**/fts_proj.js
- jstests/core/**/fts_projection.js
- jstests/core/**/fts_querylang.js
- jstests/core/**/fts_score_sort.js
- jstests/core/**/fts_spanish.js
- jstests/core/**/fts_trailing_fields.js
- jstests/core/**/function_string_representations.js
- jstests/core/**/getlog2.js
- jstests/core/**/getmore_invalidated_documents.js
- jstests/core/**/hidden_index.js
- jstests/core/**/index_filter_commands.js
- jstests/core/**/index_partial_create_drop.js
- jstests/core/**/js1.js
- jstests/core/**/js2.js
- jstests/core/**/js3.js
- jstests/core/**/js4.js
- jstests/core/**/js5.js
- jstests/core/**/js8.js
- jstests/core/**/json1.js
- jstests/core/json_schema/misc_validation.js
- jstests/core/**/list_collections_filter.js
- jstests/core/**/list_databases.js
- jstests/core/**/list_namespaces_invalidation.js
- jstests/core/**/mod.js
- jstests/core/**/mod_with_where.js
- jstests/core/**/mod_overflow.js
- jstests/core/**/not2.js
- jstests/core/**/optimized_match_explain.js
- jstests/core/**/or_inexact.js
- jstests/core/**/ora.js
- jstests/core/**/plan_cache_clear.js
- jstests/core/**/plan_cache_list_shapes.js
- jstests/core/**/profile_delete.js
- jstests/core/**/regex.js
- jstests/core/**/regex2.js
- jstests/core/**/regex6.js
- jstests/core/**/regex8.js
- jstests/core/**/regex9.js
- jstests/core/**/regex_embed1.js
- jstests/core/**/regex_error.js
- jstests/core/**/regex_limit.js
- jstests/core/**/regex_options.js
- jstests/core/**/regex_unicode.js
- jstests/core/**/regex_verbs.js
- jstests/core/**/regexb.js
- jstests/core/**/regexc.js
- jstests/core/**/server7756.js
- jstests/core/**/sortk.js
- jstests/core/**/system_js_access.js
- jstests/core/**/text_covered_matching.js
- jstests/core/txns/transactions_profiling_with_drops.js
- jstests/core/**/type1.js
- jstests/core/**/update_arrayFilters.js
- jstests/core/**/upsert_shell.js
- jstests/core/**/where1.js
- jstests/core/**/where2.js
- jstests/core/**/where3.js
- jstests/core/**/where5.js
- jstests/core/**/where_system_js.js
- jstests/core/**/where_tolerates_js_exception.js
- jstests/core/**/wildcard_and_text_indexes.js
# TODO SERVER-48852: Implement geo operators.
- jstests/core/**/collation.js
- jstests/core/**/doc_validation_invalid_validators.js
- jstests/core/**/expr.js
- jstests/core/**/geo1.js
- jstests/core/**/geo10.js
- jstests/core/**/geo2.js
- jstests/core/**/geo3.js
- jstests/core/**/geo6.js
- jstests/core/**/geo7.js
- jstests/core/**/geo9.js
- jstests/core/**/geo_2d_explain.js
- jstests/core/**/geo_2d_trailing_fields.js
- jstests/core/**/geo_2d_with_geojson_point.js
- jstests/core/**/geo_allowedcomparisons.js
- jstests/core/**/geo_array0.js
- jstests/core/**/geo_array2.js
- jstests/core/**/geo_big_polygon.js
- jstests/core/**/geo_big_polygon2.js
- jstests/core/**/geo_big_polygon3.js
- jstests/core/**/geo_borders.js
- jstests/core/**/geo_box1.js
- jstests/core/**/geo_box1_noindex.js
- jstests/core/**/geo_box2.js
- jstests/core/**/geo_box3.js
- jstests/core/**/geo_center_sphere1.js
- jstests/core/**/geo_center_sphere2.js
- jstests/core/**/geo_circle1.js
- jstests/core/**/geo_circle1_noindex.js
- jstests/core/**/geo_circle2.js
- jstests/core/**/geo_circle2a.js
- jstests/core/**/geo_circle3.js
- jstests/core/**/geo_circle4.js
- jstests/core/**/geo_circle5.js
- jstests/core/**/geo_distinct.js
- jstests/core/**/geo_exactfetch.js
- jstests/core/**/geo_fiddly_box.js
- jstests/core/**/geo_fiddly_box2.js
- jstests/core/**/geo_max.js
- jstests/core/**/geo_mindistance.js
- jstests/core/**/geo_mindistance_boundaries.js
- jstests/core/**/geo_multinest0.js
- jstests/core/**/geo_multinest1.js
- jstests/core/**/geo_near_point_query.js
- jstests/core/**/geo_near_random1.js
- jstests/core/**/geo_near_random2.js
- jstests/core/**/geo_near_tailable.js
- jstests/core/**/geo_nearwithin.js
- jstests/core/**/geo_oob_sphere.js
- jstests/core/**/geo_operator_crs.js
- jstests/core/**/geo_or.js
- jstests/core/**/geo_poly_edge.js
- jstests/core/**/geo_poly_line.js
- jstests/core/**/geo_polygon1.js
- jstests/core/**/geo_polygon1_noindex.js
- jstests/core/**/geo_polygon2.js
- jstests/core/**/geo_polygon3.js
- jstests/core/**/geo_queryoptimizer.js
- jstests/core/**/geo_regex0.js
- jstests/core/**/geo_s2cursorlimitskip.js
- jstests/core/**/geo_s2dedupnear.js
- jstests/core/**/geo_s2descindex.js
- jstests/core/**/geo_s2disjoint_holes.js
- jstests/core/**/geo_s2dupe_points.js
- jstests/core/**/geo_s2edgecases.js
- jstests/core/**/geo_s2exact.js
- jstests/core/**/geo_s2explain.js
- jstests/core/**/geo_s2holesameasshell.js
- jstests/core/**/geo_s2index.js
- jstests/core/**/geo_s2indexoldformat.js
- jstests/core/**/geo_s2intersection.js
- jstests/core/**/geo_s2largewithin.js
- jstests/core/**/geo_s2meridian.js
- jstests/core/**/geo_s2multi.js
- jstests/core/**/geo_s2near.js
- jstests/core/**/geo_s2near_equator_opposite.js
- jstests/core/**/geo_s2nearComplex.js
- jstests/core/**/geo_s2nearcorrect.js
- jstests/core/**/geo_s2nearwithin.js
- jstests/core/**/geo_s2nongeoarray.js
- jstests/core/**/geo_s2nonstring.js
- jstests/core/**/geo_s2nopoints.js
- jstests/core/**/geo_s2oddshapes.js
- jstests/core/**/geo_s2ordering.js
- jstests/core/**/geo_s2overlappingpolys.js
- jstests/core/**/geo_s2polywithholes.js
- jstests/core/**/geo_s2twofields.js
- jstests/core/**/geo_s2within.js
- jstests/core/**/geo_s2within_line_polygon_sphere.js
- jstests/core/**/geo_small_large.js
- jstests/core/**/geo_sort1.js
- jstests/core/**/geo_uniqueDocs.js
- jstests/core/**/geo_uniqueDocs2.js
- jstests/core/**/geo_update.js
- jstests/core/**/geo_update1.js
- jstests/core/**/geo_update2.js
- jstests/core/**/geo_update_btree.js
- jstests/core/**/geo_update_btree2.js
- jstests/core/**/geo_update_dedup.js
- jstests/core/**/geo_validate.js
- jstests/core/**/geo_withinquery.js
- jstests/core/**/geoa.js
- jstests/core/**/geob.js
- jstests/core/**/geoc.js
- jstests/core/**/geod.js
- jstests/core/**/geoe.js
- jstests/core/**/geof.js
- jstests/core/**/geonear_cmd_input_validation.js
- jstests/core/**/geonear_key.js
- jstests/core/**/getmore_invalidated_documents.js
- jstests/core/**/hidden_index.js
- jstests/core/**/index_partial_2dsphere.js
- jstests/core/json_schema/misc_validation.js
- jstests/core/**/list_collections_filter.js
- jstests/core/**/list_databases.js
- jstests/core/**/multikey_geonear.js
- jstests/core/**/operation_latency_histogram.js
- jstests/core/**/or5.js
- jstests/core/**/or_inexact.js
- jstests/core/**/ora.js
- jstests/core/**/update_arrayFilters.js
# TODO SERVER-48854 Implement bitwise ops.
- jstests/core/**/bittest.js
# TODO SERVER-51224 Support dotted-path fieldnames in find filters
- jstests/core/**/hashed_index_collation.js
- jstests/core/**/wildcard_index_dedup.js
- jstests/core/**/array_match2.js
- jstests/core/**/dbref4.js
- jstests/core/**/exists5.js
- jstests/core/**/comment_field.js
- jstests/core/**/wildcard_index_type.js
- jstests/core/**/type_array.js
- jstests/core/**/exists9.js
- jstests/core/**/exists8.js
# TODO SERVER-54042 Time-series collection queries
- jstests/core/timeseries/timeseries_bucket_limit_count.js
- jstests/core/timeseries/timeseries_min_max.js
- jstests/core/timeseries/timeseries_simple.js
- jstests/core/timeseries/timeseries_sparse.js
executor:
archive:
hooks:
- ValidateCollections
config:
shell_options:
eval: await import("jstests/libs/override_methods/detect_spawning_own_mongod.js");
hooks:
- class: ValidateCollections
shell_options:
global_vars:
TestData:
skipValidationOnNamespaceNotFound: false
- class: CleanEveryN
n: 20
fixture:
class: MongoDFixture
mongod_options:
set_parameters:
enableTestCommands: 1
internalQueryEnableCSTParser: 1
# TODO SERVER-48847: The TTL Monitor uses a $gt expression.
ttlMonitorEnabled: 0

View File

@ -1252,29 +1252,6 @@ tasks:
exec_timeout_secs: 18000 # 5 hour timeout.
resmoke_jobs_max: 1
# Disabled under SERVER-64949.
# - <<: *benchmark_template
# name: benchmarks_cst
# tags: ["assigned_to_jira_team_server_query_optimization", "experimental, "benchmarks"]
# commands:
# - func: "do benchmark setup"
# - func: "run benchmark tests"
# vars:
# suite: benchmarks_cst
# resmoke_jobs_max: 1
- <<: *task_template
name: cst_jscore_passthrough
tags:
[
"assigned_to_jira_team_server_query_optimization",
"experimental",
"jscore",
]
commands:
- func: "do setup"
- func: "run tests"
## Standalone generational fuzzer for checking optimized and unoptimized change stream pipelines ##
- <<: *jstestfuzz_template
name: change_stream_optimization_fuzzer_gen

View File

@ -33,7 +33,6 @@ env.SConscript(
"catalog",
"commands",
"concurrency",
"cst",
"exec",
"fts",
"ftdc",

View File

@ -11,9 +11,6 @@ filters:
- "change_stream_state_command.cpp":
approvers:
- 10gen/query-execution
- "cst_command.cpp":
approvers:
- 10gen/query-optimization
- "index_filter_commands.*":
approvers:
- 10gen/query-optimization

View File

@ -1,100 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/base/init.h"
#include "mongo/db/auth/authorization_session.h"
#include "mongo/db/commands.h"
#include "mongo/db/commands/test_commands_enabled.h"
#include "mongo/db/cst/bson_lexer.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/cst_pipeline_translation.h"
#include "mongo/db/cst/parser_gen.hpp"
namespace mongo {
class CstCommand : public BasicCommand {
public:
CstCommand() : BasicCommand("cst") {}
AllowedOnSecondary secondaryAllowed(ServiceContext*) const override {
return AllowedOnSecondary::kAlways;
}
bool supportsWriteConcern(const BSONObj& cmdObj) const override {
return false;
}
// Test commands should never be enabled in production, but we try to require auth on new
// test commands anyway, just in case someone enables them by mistake.
Status checkAuthForOperation(OperationContext* opCtx,
const DatabaseName& dbname,
const BSONObj&) const override {
AuthorizationSession* authSession = AuthorizationSession::get(opCtx->getClient());
// This auth check is more restrictive than necessary, to make it simpler.
// The CST command constructs a Pipeline, which might hold execution resources.
// We could do fine-grained permission checking similar to the find or aggregate commands,
// but that seems more complicated than necessary since this is only a test command.
if (!authSession->isAuthorizedForAnyActionOnAnyResourceInDB(dbname)) {
return Status(ErrorCodes::Unauthorized, "Unauthorized");
}
return Status::OK();
}
std::string help() const override {
return "test command for CST";
}
bool run(OperationContext* opCtx,
const DatabaseName& dbName,
const BSONObj& cmdObj,
BSONObjBuilder& result) override {
CNode pipelineCst;
{
BSONLexer lexer(cmdObj["pipeline"].Obj(), ParserGen::token::START_PIPELINE);
ParserGen parser(lexer, &pipelineCst);
int err = parser.parse();
// We use exceptions instead of return codes to report parse errors, so this
// should never happen.
invariant(!err);
}
result.append("cst", BSONArray(pipelineCst.toBson()));
auto nss = NamespaceString{dbName, ""};
auto expCtx = make_intrusive<ExpressionContext>(opCtx, nullptr /*collator*/, nss);
auto pipeline = cst_pipeline_translation::translatePipeline(pipelineCst, expCtx);
result.append("ds", pipeline->serializeToBson());
return true;
}
};
MONGO_REGISTER_COMMAND(CstCommand).testOnly().forShard();
} // namespace mongo

View File

@ -1,10 +0,0 @@
load("//bazel:mongo_src_rules.bzl", "mongo_cc_library")
package(default_visibility = ["//visibility:public"])
exports_files(
glob([
"*.h",
"*.cpp",
]),
)

View File

@ -1,5 +0,0 @@
version: 1.0.0
filters:
- "*":
approvers:
- 10gen/query-optimization

View File

@ -1,78 +0,0 @@
# -*- mode: python -*-
Import("env")
env = env.Clone()
env.Library(
target="cst",
source=[
"bson_lexer.cpp",
"c_node.cpp",
"c_node_disambiguation.cpp",
"c_node_validation.cpp",
"compound_key.cpp",
"cst_match_translation.cpp",
"cst_pipeline_translation.cpp",
"cst_sort_translation.cpp",
"parser_gen.cpp",
],
LIBDEPS=[
"$BUILD_DIR/mongo/base",
"$BUILD_DIR/mongo/db/pipeline/pipeline",
"$BUILD_DIR/mongo/db/pipeline/variable_validation",
"$BUILD_DIR/mongo/db/query/datetime/date_time_support",
],
)
env.CppUnitTest(
target="cst_test",
source=[
"bson_lexer_test.cpp",
"cst_error_test.cpp",
"cst_expression_test.cpp",
"cst_find_project_test.cpp",
"cst_match_test.cpp",
"cst_match_translation_test.cpp",
"cst_sort_translation_test.cpp",
"cst_test.cpp",
],
LIBDEPS=[
# $text depends on FTSAccessMethod.
"$BUILD_DIR/mongo/db/index/index_access_method",
"$BUILD_DIR/mongo/db/matcher/expressions_mongod_only",
"$BUILD_DIR/mongo/db/query/query_test_service_context",
"$BUILD_DIR/mongo/db/service_context_non_d",
"cst",
],
)
env.CppUnitTest(
target="cst_pipeline_translation_test",
source=[
"cst_literals_test.cpp",
"cst_pipeline_translation_test.cpp",
"cst_set_operator_translation_test.cpp",
],
LIBDEPS=[
"$BUILD_DIR/mongo/db/query/query_test_service_context",
"$BUILD_DIR/mongo/db/service_context_non_d",
"cst",
],
)
# Disabled under SERVER-64949.
#
# env.Benchmark(
# target='cst_bm',
# source=[
# 'cst_bm.cpp',
# ],
# LIBDEPS=[
# '$BUILD_DIR/mongo/db/pipeline/pipeline',
# '$BUILD_DIR/mongo/db/query/query_test_service_context',
# '$BUILD_DIR/mongo/unittest/unittest',
# '$BUILD_DIR/mongo/util/processinfo',
# 'cst',
# ],
# )

View File

@ -1,526 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <absl/container/node_hash_map.h>
#include <boost/algorithm/string/split.hpp>
#include <boost/core/addressof.hpp>
#include <boost/function/function_base.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/move/utility_core.hpp>
#include <boost/optional/optional.hpp>
#include <boost/type_index/type_index_facade.hpp>
// IWYU pragma: no_include "ext/alloc_traits.h"
#include <cstddef>
#include <list>
#include <memory>
#include <string>
#include "mongo/base/string_data.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/bson/bsontypes_util.h"
#include "mongo/db/cst/bson_lexer.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/parser_gen.hpp"
#include "mongo/platform/decimal128.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/string_map.h"
namespace mongo {
using namespace std::string_literals;
namespace {
// Mapping of reserved key fieldnames to BSON token. Any key which is not included in this map is
// assumed to be a user field name.
const StringMap<ParserGen::token_type> reservedKeyFieldnameLookup = {
{"_id", ParserGen::token::ID},
// Stages and their arguments.
{"$_internalInhibitOptimization", ParserGen::token::STAGE_INHIBIT_OPTIMIZATION},
{"$limit", ParserGen::token::STAGE_LIMIT},
{"$project", ParserGen::token::STAGE_PROJECT},
{"$match", ParserGen::token::STAGE_MATCH},
{"$sample", ParserGen::token::STAGE_SAMPLE},
{"size", ParserGen::token::ARG_SIZE},
{"$skip", ParserGen::token::STAGE_SKIP},
{"$unionWith", ParserGen::token::STAGE_UNION_WITH},
{"coll", ParserGen::token::ARG_COLL},
{"pipeline", ParserGen::token::ARG_PIPELINE},
// Pathless match operators
{"$expr", ParserGen::token::EXPR},
{"$text", ParserGen::token::TEXT},
{"$where", ParserGen::token::WHERE},
// Expressions
{"$abs", ParserGen::token::ABS},
{"$acos", ParserGen::token::ACOS},
{"$acosh", ParserGen::token::ACOSH},
{"$add", ParserGen::token::ADD},
{"$and", ParserGen::token::AND},
{"$asin", ParserGen::token::ASIN},
{"$asinh", ParserGen::token::ASINH},
{"$atan", ParserGen::token::ATAN},
{"$atan2", ParserGen::token::ATAN2},
{"$atan2", ParserGen::token::ATAN2},
{"$atanh", ParserGen::token::ATANH},
{"$ceil", ParserGen::token::CEIL},
{"$cmp", ParserGen::token::CMP},
{"$concat", ParserGen::token::CONCAT},
{"$const", ParserGen::token::CONST_EXPR},
{"$convert", ParserGen::token::CONVERT},
{"$cos", ParserGen::token::COS},
{"$cosh", ParserGen::token::COSH},
{"$dateFromString", ParserGen::token::DATE_FROM_STRING},
{"$dateToString", ParserGen::token::DATE_TO_STRING},
{"$degreesToRadians", ParserGen::token::DEGREES_TO_RADIANS},
{"$divide", ParserGen::token::DIVIDE},
{"$elemMatch", ParserGen::token::ELEM_MATCH},
{"$eq", ParserGen::token::EQ},
{"$exp", ParserGen::token::EXPONENT},
{"$floor", ParserGen::token::FLOOR},
{"$gt", ParserGen::token::GT},
{"$gte", ParserGen::token::GTE},
{"$indexOfBytes", ParserGen::token::INDEX_OF_BYTES},
{"$indexOfCP", ParserGen::token::INDEX_OF_CP},
{"$literal", ParserGen::token::LITERAL},
{"$ln", ParserGen::token::LN},
{"$log", ParserGen::token::LOG},
{"$log10", ParserGen::token::LOGTEN},
{"$lt", ParserGen::token::LT},
{"$lte", ParserGen::token::LTE},
{"$ltrim", ParserGen::token::LTRIM},
{"$meta", ParserGen::token::META},
{"$mod", ParserGen::token::MOD},
{"$multiply", ParserGen::token::MULTIPLY},
{"$ne", ParserGen::token::NE},
{"$nor", ParserGen::token::NOR},
{"$not", ParserGen::token::NOT},
{"$or", ParserGen::token::OR},
{"$pow", ParserGen::token::POW},
{"$round", ParserGen::token::ROUND},
{"$slice", ParserGen::token::SLICE},
{"$sqrt", ParserGen::token::SQRT},
{"$subtract", ParserGen::token::SUBTRACT},
{"$trunc", ParserGen::token::TRUNC},
{"$concat", ParserGen::token::CONCAT},
{"$dateFromString", ParserGen::token::DATE_FROM_STRING},
{"$dateToString", ParserGen::token::DATE_TO_STRING},
{"$indexOfBytes", ParserGen::token::INDEX_OF_BYTES},
{"$indexOfCP", ParserGen::token::INDEX_OF_CP},
{"$ltrim", ParserGen::token::LTRIM},
{"$meta", ParserGen::token::META},
{"$radiansToDegrees", ParserGen::token::RADIANS_TO_DEGREES},
{"$regexFind", ParserGen::token::REGEX_FIND},
{"$regexFindAll", ParserGen::token::REGEX_FIND_ALL},
{"$regexMatch", ParserGen::token::REGEX_MATCH},
{"$replaceAll", ParserGen::token::REPLACE_ALL},
{"$replaceOne", ParserGen::token::REPLACE_ONE},
{"$round", ParserGen::token::ROUND},
{"$rtrim", ParserGen::token::RTRIM},
{"$sin", ParserGen::token::SIN},
{"$sinh", ParserGen::token::SINH},
{"$split", ParserGen::token::SPLIT},
{"$sqrt", ParserGen::token::SQRT},
{"$strcasecmp", ParserGen::token::STR_CASE_CMP},
{"$strLenBytes", ParserGen::token::STR_LEN_BYTES},
{"$strLenCP", ParserGen::token::STR_LEN_CP},
{"$substr", ParserGen::token::SUBSTR},
{"$substrBytes", ParserGen::token::SUBSTR_BYTES},
{"$substrCP", ParserGen::token::SUBSTR_CP},
{"$subtract", ParserGen::token::SUBTRACT},
{"$tan", ParserGen::token::TAN},
{"$tanh", ParserGen::token::TANH},
{"$toBool", ParserGen::token::TO_BOOL},
{"$toDate", ParserGen::token::TO_DATE},
{"$toDecimal", ParserGen::token::TO_DECIMAL},
{"$toDouble", ParserGen::token::TO_DOUBLE},
{"$toInt", ParserGen::token::TO_INT},
{"$toLong", ParserGen::token::TO_LONG},
{"$toLower", ParserGen::token::TO_LOWER},
{"$toObjectId", ParserGen::token::TO_OBJECT_ID},
{"$toString", ParserGen::token::TO_STRING},
{"$toUpper", ParserGen::token::TO_UPPER},
{"$trim", ParserGen::token::TRIM},
{"$trunc", ParserGen::token::TRUNC},
{"$type", ParserGen::token::TYPE},
{"chars", ParserGen::token::ARG_CHARS},
{"date", ParserGen::token::ARG_DATE},
{"$comment", ParserGen::token::COMMENT},
{"$exists", ParserGen::token::EXISTS},
{"dateString", ParserGen::token::ARG_DATE_STRING},
{"find", ParserGen::token::ARG_FIND},
{"format", ParserGen::token::ARG_FORMAT},
{"input", ParserGen::token::ARG_INPUT},
{"onError", ParserGen::token::ARG_ON_ERROR},
{"onNull", ParserGen::token::ARG_ON_NULL},
{"options", ParserGen::token::ARG_OPTIONS},
{"find", ParserGen::token::ARG_FIND},
{"regex", ParserGen::token::ARG_REGEX},
{"replacement", ParserGen::token::ARG_REPLACEMENT},
{"$allElementsTrue", ParserGen::token::ALL_ELEMENTS_TRUE},
{"$anyElementTrue", ParserGen::token::ANY_ELEMENT_TRUE},
{"$setDifference", ParserGen::token::SET_DIFFERENCE},
{"$setEquals", ParserGen::token::SET_EQUALS},
{"$setIntersection", ParserGen::token::SET_INTERSECTION},
{"$setIsSubset", ParserGen::token::SET_IS_SUBSET},
{"$setUnion", ParserGen::token::SET_UNION},
{"timezone", ParserGen::token::ARG_TIMEZONE},
{"to", ParserGen::token::ARG_TO},
{"minute", ParserGen::token::ARG_MINUTE},
{"second", ParserGen::token::ARG_SECOND},
{"millisecond", ParserGen::token::ARG_MILLISECOND},
{"day", ParserGen::token::ARG_DAY},
{"isoDayOfWeek", ParserGen::token::ARG_ISO_DAY_OF_WEEK},
{"isoWeek", ParserGen::token::ARG_ISO_WEEK},
{"isoWeekYear", ParserGen::token::ARG_ISO_WEEK_YEAR},
{"iso8601", ParserGen::token::ARG_ISO_8601},
{"month", ParserGen::token::ARG_MONTH},
{"year", ParserGen::token::ARG_YEAR},
{"hour", ParserGen::token::ARG_HOUR},
{"$dateFromParts", ParserGen::token::DATE_FROM_PARTS},
{"$dateToParts", ParserGen::token::DATE_TO_PARTS},
{"$dayOfMonth", ParserGen::token::DAY_OF_MONTH},
{"$dayOfWeek", ParserGen::token::DAY_OF_WEEK},
{"$dayOfYear", ParserGen::token::DAY_OF_YEAR},
{"$hour", ParserGen::token::HOUR},
{"$isoDayOfWeek", ParserGen::token::ISO_DAY_OF_WEEK},
{"$isoWeek", ParserGen::token::ISO_WEEK},
{"$isoWeekYear", ParserGen::token::ISO_WEEK_YEAR},
{"$millisecond", ParserGen::token::MILLISECOND},
{"$minute", ParserGen::token::MINUTE},
{"$month", ParserGen::token::MONTH},
{"$second", ParserGen::token::SECOND},
{"$week", ParserGen::token::WEEK},
{"$year", ParserGen::token::YEAR},
{"$search", ParserGen::token::ARG_SEARCH},
{"$language", ParserGen::token::ARG_LANGUAGE},
{"$caseSensitive", ParserGen::token::ARG_CASE_SENSITIVE},
{"$diacriticSensitive", ParserGen::token::ARG_DIACRITIC_SENSITIVE},
{"$mod", ParserGen::token::MOD},
{"$arrayElemAt", ParserGen::token::ARRAY_ELEM_AT},
{"$arrayToObject", ParserGen::token::ARRAY_TO_OBJECT},
{"$concatArrays", ParserGen::token::CONCAT_ARRAYS},
{"$filter", ParserGen::token::FILTER},
{"$first", ParserGen::token::FIRST},
{"$in", ParserGen::token::IN_},
{"$indexOfArray", ParserGen::token::INDEX_OF_ARRAY},
{"$isArray", ParserGen::token::IS_ARRAY},
{"as", ParserGen::token::ARG_AS},
{"cond", ParserGen::token::ARG_COND}};
// Mapping of reserved key values to BSON token. Any key which is not included in this map is
// assumed to be a user value.
const StringMap<ParserGen::token_type> reservedKeyValueLookup = {
{"geoNearDistance", ParserGen::token::GEO_NEAR_DISTANCE},
{"geoNearPoint", ParserGen::token::GEO_NEAR_POINT},
{"indexKey", ParserGen::token::INDEX_KEY},
{"randVal", ParserGen::token::RAND_VAL},
{"recordId", ParserGen::token::RECORD_ID},
{"searchHighlights", ParserGen::token::SEARCH_HIGHLIGHTS},
{"searchScore", ParserGen::token::SEARCH_SCORE},
{"sortKey", ParserGen::token::SORT_KEY},
{"textScore", ParserGen::token::TEXT_SCORE},
};
bool isCompound(ParserGen::symbol_type token) {
return token.type_get() == static_cast<int>(ParserGen::token::START_OBJECT) ||
token.type_get() == static_cast<int>(ParserGen::token::START_ARRAY);
}
} // namespace
void BSONLexer::sortObjTokens() {
// A TokenElement is similar to a BSONElement, with the payload being a vector of Bison symbols
// if the type is compound (object or array).
using TokenElement = std::pair<ParserGen::symbol_type, std::vector<ParserGen::symbol_type>>;
struct TokenElementCompare {
bool operator()(const TokenElement& elem1, const TokenElement& elem2) const {
return elem1.first.type_get() < elem2.first.type_get();
}
};
auto currentPosition = _position;
// Ensure that we've just entered an object - i.e. that the previous token was a START_OBJECT.
// Otherwise, this function is a no-op.
if (currentPosition < 1 ||
_tokens[currentPosition - 1].type_get() !=
static_cast<int>(ParserGen::token::START_OBJECT)) {
return;
}
std::list<TokenElement> sortedTokenPairs;
// We've just entered an object (i.e. the previous token was a start object). We will sort
// tokens until the matching END_OBJECT is found.
while (_tokens[currentPosition].type_get() != static_cast<int>(ParserGen::token::END_OBJECT)) {
invariant(size_t(currentPosition) < _tokens.size());
auto keyToken = _tokens[currentPosition++];
std::vector<ParserGen::symbol_type> rhsTokens;
rhsTokens.push_back(_tokens[currentPosition]);
if (isCompound(_tokens[currentPosition])) {
auto braceCount = 1;
currentPosition++;
// Only sort the top level tokens. If we encounter a compound type, then jump to its
// matching bracket or brace.
while (braceCount > 0) {
if (isCompound(_tokens[currentPosition]))
braceCount++;
if (_tokens[currentPosition].type_get() ==
static_cast<int>(ParserGen::token::END_OBJECT) ||
_tokens[currentPosition].type_get() ==
static_cast<int>(ParserGen::token::END_ARRAY))
braceCount--;
rhsTokens.push_back(_tokens[currentPosition++]);
}
} else {
// Scalar, already added above.
currentPosition++;
}
sortedTokenPairs.push_back(std::make_pair(keyToken, rhsTokens));
}
sortedTokenPairs.sort(TokenElementCompare());
// _position is at the token immediately following the initial START_OBJECT, and currentPosition
// is at the matching END_OBJECT. We need to flatten the sorted list of KV pairs to get the
// correct order of tokens.
auto replacePosition = _position;
for (auto&& [key, rhsTokens] : sortedTokenPairs) {
_tokens[replacePosition].clear();
_tokens[replacePosition++].move(key);
for (auto&& token : rhsTokens) {
_tokens[replacePosition].clear();
_tokens[replacePosition++].move(token);
}
}
}
void BSONLexer::tokenize(BSONElement elem, bool includeFieldName) {
boost::optional<ScopedLocationTracker> context;
// Skipped when we are tokenizing arrays.
if (includeFieldName) {
if (auto it = reservedKeyFieldnameLookup.find(elem.fieldNameStringData());
it != reservedKeyFieldnameLookup.end()) {
// Place the token expected by the parser if this is a reserved key fieldname.
pushToken(elem.fieldNameStringData(), it->second);
context.emplace(this, elem.fieldNameStringData());
} else if (elem.fieldNameStringData().find('.') != std::string::npos) {
auto components = std::vector<std::string>{};
const auto fieldName = elem.fieldNameStringData();
boost::split(components, fieldName, [](auto c) { return c == '.'; });
pushToken(elem.fieldNameStringData(),
ParserGen::token::DOTTED_FIELDNAME,
std::move(components));
} else if (elem.fieldNameStringData()[0] == '$') {
pushToken(elem.fieldNameStringData(),
ParserGen::token::DOLLAR_PREF_FIELDNAME,
elem.fieldName());
} else {
pushToken(elem.fieldNameStringData(), ParserGen::token::FIELDNAME, elem.fieldName());
}
}
switch (elem.type()) {
case BSONType::Array: {
pushToken("start array", ParserGen::token::START_ARRAY);
auto index = 0U;
for (auto&& nestedElem : elem.embeddedObject()) {
ScopedLocationTracker arrayCtx{this, index++};
// For arrays, do not tokenize the field names.
tokenize(nestedElem, false);
}
pushToken("end array", ParserGen::token::END_ARRAY);
break;
}
case BSONType::Object:
pushToken("start object", ParserGen::token::START_OBJECT);
for (auto&& nestedElem : elem.embeddedObject()) {
tokenize(nestedElem, true);
}
pushToken("end object", ParserGen::token::END_OBJECT);
break;
case NumberDouble:
if (elem.numberDouble() == 0.0)
pushToken(elem, ParserGen::token::DOUBLE_ZERO);
else if (elem.numberDouble() == 1.0)
pushToken(elem, ParserGen::token::DOUBLE_ONE);
else if (elem.numberDouble() == -1.0)
pushToken(elem, ParserGen::token::DOUBLE_NEGATIVE_ONE);
else
pushToken(elem, ParserGen::token::DOUBLE_OTHER, elem.numberDouble());
break;
case BSONType::String:
if (auto it = reservedKeyValueLookup.find(elem.valueStringData());
it != reservedKeyValueLookup.end()) {
// Place the token expected by the parser if this is a reserved key value.
pushToken(elem.valueStringData(), it->second);
} else {
// If we don't care about the keyword, then it's treated as a generic value.
if (elem.valueStringData().starts_with('$')) {
if (elem.valueStringData().starts_with("$$")) {
pushToken(elem.valueStringData(),
ParserGen::token::DOLLAR_DOLLAR_STRING,
elem.String());
} else {
pushToken(
elem.valueStringData(), ParserGen::token::DOLLAR_STRING, elem.String());
}
} else {
pushToken(elem.valueStringData(), ParserGen::token::STRING, elem.String());
}
}
break;
case BSONType::BinData: {
int len;
auto data = elem.binData(len);
pushToken(elem, ParserGen::token::BINARY, BSONBinData{data, len, elem.binDataType()});
break;
}
case BSONType::Undefined:
pushToken(elem, ParserGen::token::UNDEFINED, UserUndefined{});
break;
case BSONType::jstOID:
pushToken(elem, ParserGen::token::OBJECT_ID, elem.OID());
break;
case Bool:
pushToken(elem,
elem.boolean() ? ParserGen::token::BOOL_TRUE : ParserGen::token::BOOL_FALSE);
break;
case BSONType::Date:
pushToken(elem, ParserGen::token::DATE_LITERAL, elem.date());
break;
case BSONType::jstNULL:
pushToken(elem, ParserGen::token::JSNULL, UserNull{});
break;
case BSONType::RegEx:
pushToken(elem, ParserGen::token::REGEX, BSONRegEx{elem.regex(), elem.regexFlags()});
break;
case BSONType::DBRef:
pushToken(
elem, ParserGen::token::DB_POINTER, BSONDBRef{elem.dbrefNS(), elem.dbrefOID()});
break;
case BSONType::Code:
pushToken(elem, ParserGen::token::JAVASCRIPT, BSONCode{elem.valueStringData()});
break;
case BSONType::Symbol:
pushToken(elem, ParserGen::token::SYMBOL, BSONSymbol{elem.valueStringData()});
break;
case BSONType::CodeWScope: {
auto code = StringData{elem.codeWScopeCode(),
static_cast<size_t>(elem.codeWScopeCodeLen()) - 1ull};
pushToken(elem,
ParserGen::token::JAVASCRIPT_W_SCOPE,
BSONCodeWScope{code, elem.codeWScopeObject()});
break;
}
case NumberInt:
if (elem.numberInt() == 0)
pushToken(elem, ParserGen::token::INT_ZERO);
else if (elem.numberInt() == 1)
pushToken(elem, ParserGen::token::INT_ONE);
else if (elem.numberInt() == -1)
pushToken(elem, ParserGen::token::INT_NEGATIVE_ONE);
else
pushToken(elem, ParserGen::token::INT_OTHER, elem.numberInt());
break;
case BSONType::bsonTimestamp:
pushToken(elem, ParserGen::token::TIMESTAMP, elem.timestamp());
break;
case NumberLong:
if (elem.numberLong() == 0ll)
pushToken(elem, ParserGen::token::LONG_ZERO);
else if (elem.numberLong() == 1ll)
pushToken(elem, ParserGen::token::LONG_ONE);
else if (elem.numberLong() == -1ll)
pushToken(elem, ParserGen::token::LONG_NEGATIVE_ONE);
else
pushToken(elem, ParserGen::token::LONG_OTHER, elem.numberLong());
break;
case NumberDecimal:
if (elem.numberDecimal() == Decimal128::kNormalizedZero)
pushToken(elem, ParserGen::token::DECIMAL_ZERO);
else if (elem.numberDecimal() == Decimal128(1)) {
pushToken(elem, ParserGen::token::DECIMAL_ONE);
} else if (elem.numberDecimal() == Decimal128(-1)) {
pushToken(elem, ParserGen::token::DECIMAL_NEGATIVE_ONE);
} else
pushToken(elem, ParserGen::token::DECIMAL_OTHER, elem.numberDecimal());
break;
case BSONType::MinKey:
pushToken(elem, ParserGen::token::MIN_KEY, UserMinKey{});
break;
case BSONType::MaxKey:
pushToken(elem, ParserGen::token::MAX_KEY, UserMaxKey{});
break;
default:
MONGO_UNREACHABLE;
}
}
BSONLexer::BSONLexer(BSONObj obj, ParserGen::token_type startingToken) {
// Add a prefix to the location depending on the starting token.
ScopedLocationTracker inputPrefix{
this,
startingToken == ParserGen::token::START_PIPELINE
? "pipeline"
: (startingToken == ParserGen::token::START_SORT
? "sort"
: (startingToken == ParserGen::token::START_PROJECT ? "project" : "filter"))};
pushToken("start", startingToken);
// If 'obj' is representing a pipeline, each element is a stage with the fieldname being the
// array index. No need to tokenize the fieldname for that case.
if (startingToken == ParserGen::token::START_PIPELINE) {
pushToken("start array", ParserGen::token::START_ARRAY);
auto index = 0U;
for (auto&& elem : obj) {
ScopedLocationTracker stageCtx{this, index++};
tokenize(elem, false);
}
pushToken("end array", ParserGen::token::END_ARRAY);
} else {
pushToken("start object", ParserGen::token::START_OBJECT);
for (auto&& elem : obj) {
tokenize(elem, true);
}
pushToken("end object", ParserGen::token::END_OBJECT);
}
// Final token must indicate EOF.
pushToken("EOF", ParserGen::token::END_OF_FILE);
// Reset the position to use in yylex().
_position = 0;
};
ParserGen::symbol_type yylex(mongo::BSONLexer& lexer) {
return lexer.getNext();
}
} // namespace mongo

View File

@ -1,114 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/db/cst/bson_location.h"
#include "mongo/db/cst/parser_gen.hpp"
namespace mongo {
class BSONLexer {
public:
BSONLexer(BSONObj obj, ParserGen::token_type startingToken);
/**
* Retrieves the next token in the stream.
*/
ParserGen::symbol_type getNext() {
return _tokens[_position++];
}
/**
* Sorts the object that the lexer just entered (i.e., a START_OBJECT token was just emitted,
* and currentPosition is now one past the start of the object), based on the enum for each of
* the field name tokens.
*/
void sortObjTokens();
/**
* Convenience for retrieving the token at the given offset.
*/
auto& operator[](int offset) {
return _tokens[offset];
}
/**
* Scoped struct which pushes a location prefix for subsequently generated tokens. Pops the
* prefix off the stack upon destruction.
*/
struct ScopedLocationTracker {
ScopedLocationTracker(BSONLexer* lexer, BSONLocation::LocationPrefix prefix)
: _lexer(lexer) {
_lexer->_locationPrefixes.emplace_back(prefix);
}
~ScopedLocationTracker() {
_lexer->_locationPrefixes.pop_back();
}
BSONLexer* _lexer{nullptr};
};
private:
// Tokenizes the given BSONElement, traversing its children if necessary. If the field name
// should not be considered, set 'includeFieldName' to false.
void tokenize(BSONElement elem, bool includeFieldName);
template <class LocationType, class... Args>
void pushToken(LocationType name, Args&&... args) {
auto token = ParserGen::symbol_type(std::forward<Args>(args)...,
BSONLocation{std::move(name), _locationPrefixes});
_tokens.emplace_back(std::move(token));
_position++;
}
// Track the position of the input, both during construction of the list of tokens as well as
// during parse.
unsigned int _position = 0; // note: counter_type is only available on 3.5+
// A set of prefix strings that describe the current location in the lexer. As we walk the input
// BSON, this will change depending on the context that we're parsing.
std::vector<BSONLocation::LocationPrefix> _locationPrefixes;
std::vector<ParserGen::symbol_type> _tokens;
};
// This is the entry point for retrieving the next token from the lexer, invoked from Bison's
// yyparse().
ParserGen::symbol_type yylex(mongo::BSONLexer& lexer);
} // namespace mongo

View File

@ -1,264 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/base/string_data.h"
#include "mongo/bson/json.h"
#include "mongo/db/cst/bson_lexer.h"
#include "mongo/db/cst/parser_gen.hpp"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/framework.h"
namespace mongo {
namespace {
TEST(BSONLexerTest, TokenizesOpaqueUserObjects) {
auto input = fromjson("{pipeline: [{a: 2, b: '1', c: \"$path\", d: \"$$NOW\"}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_EQ(ParserGen::token::START_PIPELINE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::INT_OTHER, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STRING, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::DOLLAR_STRING, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::DOLLAR_DOLLAR_STRING, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OF_FILE, lexer.getNext().type_get());
}
TEST(BSONLexerTest, TokenizesReservedKeywords) {
auto input = fromjson("{pipeline: [{$_internalInhibitOptimization: {}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_EQ(ParserGen::token::START_PIPELINE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STAGE_INHIBIT_OPTIMIZATION, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
}
TEST(BSONLexerTest, TokenizesReservedKeywordsAtAnyDepth) {
auto input = fromjson("{pipeline: [{a: {$_internalInhibitOptimization: {}}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_EQ(ParserGen::token::START_PIPELINE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STAGE_INHIBIT_OPTIMIZATION, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
}
TEST(BSONLexerTest, MidRuleActionToSortNestedObject) {
auto input = fromjson("{pipeline: [{pipeline: 2.0, coll: 'test'}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
// Iterate until the first object.
ASSERT_EQ(ParserGen::token::START_PIPELINE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
// Kick the lexer to sort the object, which should move element 'coll' in front of 'pipeline'.
// Not that this only works because these are reserved keywords recognized by the lexer,
// arbitrary string field names with *not* get sorted.
lexer.sortObjTokens();
ASSERT_EQ(ParserGen::token::ARG_COLL, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STRING, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::ARG_PIPELINE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::DOUBLE_OTHER, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
}
TEST(BSONLexerTest, MidRuleActionToSortDoesNotSortNestedObjects) {
auto input = fromjson(
"{pipeline: [{$unionWith: {pipeline: [{$unionWith: 'inner', a: 3.0}], coll: 'outer'}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
// Iterate until we reach the $unionWith object.
ASSERT_EQ(ParserGen::token::START_PIPELINE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STAGE_UNION_WITH, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
lexer.sortObjTokens();
ASSERT_EQ(ParserGen::token::ARG_COLL, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STRING, lexer.getNext().type_get()); // coll: 'outer'
ASSERT_EQ(ParserGen::token::ARG_PIPELINE,
lexer.getNext().type_get()); // inner pipeline
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
// The nested pipeline does *not* get sorted, meaning '$unionWith' stays before 'a'.
ASSERT_EQ(ParserGen::token::STAGE_UNION_WITH, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STRING, lexer.getNext().type_get()); // $unionWith: 'inner'
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::DOUBLE_OTHER, lexer.getNext().type_get()); // a: 1.0
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
}
TEST(BSONLexerTest, MultipleNestedObjectsAreReorderedCorrectly) {
auto input = fromjson(
"{pipeline: [{$unionWith: {pipeline: [{$unionWith: 'inner', a: 3.0}], coll: [{$unionWith: "
"'innerB', a: 2.0}]}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
// Iterate until we reach the $unionWith object.
ASSERT_EQ(ParserGen::token::START_PIPELINE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STAGE_UNION_WITH, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
lexer.sortObjTokens();
ASSERT_EQ(ParserGen::token::ARG_COLL, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
// The nested pipeline does *not* get sorted, meaning '$unionWith' stays before 'a'.
ASSERT_EQ(ParserGen::token::STAGE_UNION_WITH, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STRING, lexer.getNext().type_get()); // innerb
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get()); // a
ASSERT_EQ(ParserGen::token::DOUBLE_OTHER, lexer.getNext().type_get()); // a: 2.0
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
// Coll nested object ends here.
ASSERT_EQ(ParserGen::token::ARG_PIPELINE,
lexer.getNext().type_get()); // inner pipeline
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
// The nested pipeline does *not* get sorted, meaning '$unionWith' stays before 'a'.
ASSERT_EQ(ParserGen::token::STAGE_UNION_WITH, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STRING, lexer.getNext().type_get()); // $unionWith: 'inner'
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get()); // a
ASSERT_EQ(ParserGen::token::DOUBLE_OTHER, lexer.getNext().type_get()); // a: 1.0
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
}
TEST(BSONLexerTest, MultiLevelBSONDoesntSortChildren) {
auto input = fromjson(
"{pipeline: [{$unionWith: {pipeline: [{$unionWith: {'nested': 3.0, 'apple': 3.0}, a: 3.0}],"
" coll: 'outer'}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
// Iterate until we reach the $unionWith object.
ASSERT_EQ(ParserGen::token::START_PIPELINE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STAGE_UNION_WITH, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
lexer.sortObjTokens();
ASSERT_EQ(ParserGen::token::ARG_COLL, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STRING, lexer.getNext().type_get()); // coll: 'outer'
ASSERT_EQ(ParserGen::token::ARG_PIPELINE,
lexer.getNext().type_get()); // inner pipeline
// First nested object
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STAGE_UNION_WITH, lexer.getNext().type_get());
// Second nested object
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get()); // nested: 1.0
ASSERT_EQ(ParserGen::token::DOUBLE_OTHER, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get()); // apple: 1.0
ASSERT_EQ(ParserGen::token::DOUBLE_OTHER, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
// End second nested object
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::DOUBLE_OTHER, lexer.getNext().type_get()); // a: 1.0
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
// End first nested object
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
}
TEST(BSONLexerTest, EmptyMatchExpressionsAreLexedCorrectly) {
BSONLexer lexer(fromjson("{}"), ParserGen::token::START_MATCH);
ASSERT_EQ(ParserGen::token::START_MATCH, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
}
TEST(BSONLexerTest, TokenizesObjWithPathCorrectly) {
auto input = fromjson(
"{pipeline: [{$project: { m: { $dateToString: { date: '$date', "
"format: '%Y-%m-%d' } } } } ] }");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_EQ(ParserGen::token::START_PIPELINE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_ARRAY, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STAGE_PROJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::DATE_TO_STRING, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::ARG_DATE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::DOLLAR_STRING, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::ARG_FORMAT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::STRING, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_ARRAY, lexer.getNext().type_get());
}
TEST(BSONLexerTest, SortSpecTokensGeneratedCorrectly) {
auto input = fromjson("{sort: {val: 1, test: -1.0, rand: {$meta: 'textScore'}}}");
BSONLexer lexer(input["sort"].embeddedObject(), ParserGen::token::START_SORT);
ASSERT_EQ(ParserGen::token::START_SORT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::INT_ONE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::DOUBLE_NEGATIVE_ONE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::FIELDNAME, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::START_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::META, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::TEXT_SCORE, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
ASSERT_EQ(ParserGen::token::END_OBJECT, lexer.getNext().type_get());
}
} // namespace
} // namespace mongo

View File

@ -1,109 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <sstream>
#include <string>
#include <variant>
#include <vector>
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/util/overloaded_visitor.h"
namespace mongo {
/**
* Represents the location of a specific token in a BSON object.
*/
class BSONLocation {
public:
using LocationPrefix = std::variant<unsigned int, StringData>;
// A location may be either the payload of a BSONElement, or a string representing a fieldname
// or metadata token (e.g. '{' for the start of an object). Array indices are not represented as
// a BSONLocation, per se, but instead are part of the list of prefix descriptors.
using LocationType = std::variant<BSONElement, StringData>;
BSONLocation() = default;
/**
* Builds a location of a token in the input BSON. The 'prefix' argument is a list of elements
* that describe the path to 'location'. There must be at least one element in 'prefix',
* detailing the parser entry point.
*/
BSONLocation(LocationType location, std::vector<LocationPrefix> prefix)
: _location(std::move(location)), _prefix(std::move(prefix)) {}
/**
* Prints this location along with the prefix strings that describe the path to the element. The
* resulting string is verbose and useful in debugging or syntax errors.
*/
std::string toString() const {
std::ostringstream stream;
visit(OverloadedVisitor{
[&](const BSONElement& elem) { stream << "'" << elem.toString(false) << "'"; },
[&](StringData elem) {
stream << "'" << elem << "'";
}},
_location);
// The assumption is that there is always at least one prefix that represents the entry
// point to the parser (e.g. the 'pipeline' argument for an aggregation command).
invariant(_prefix.size() > 0);
for (auto it = _prefix.rbegin(); it != _prefix.rend() - 1; ++it) {
visit(OverloadedVisitor{[&](const unsigned int& index) {
stream << " within array at index " << index;
},
[&](StringData pref) {
stream << " within '" << pref << "'";
}},
*it);
}
// The final prefix (or first element in the vector) is the input description.
visit(OverloadedVisitor{[&](const unsigned int& index) { MONGO_UNREACHABLE; },
[&](StringData pref) {
stream << " of input " << pref;
}},
_prefix[0]);
return stream.str();
}
friend std::ostream& operator<<(std::ostream& stream, const BSONLocation& location) {
return stream << location.toString();
}
friend StringBuilder& operator<<(StringBuilder& stream, const BSONLocation& location) {
return stream << location.toString();
}
private:
LocationType _location;
std::vector<LocationPrefix> _prefix;
};
} // namespace mongo

View File

@ -1,333 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/cst/c_node.h"
#include <algorithm>
#include <limits>
#include <memory>
#include <numeric>
#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/db/query/datetime/date_time_support.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/hex.h"
#include "mongo/util/overloaded_visitor.h" // IWYU pragma: keep
namespace mongo {
using namespace std::string_literals;
namespace {
auto tabs(int num) {
std::string out;
for (; num > 0; num--)
out += "\t";
return out;
}
auto printFieldname(const CNode::Fieldname& fieldname) {
return visit(
OverloadedVisitor{
[](const KeyFieldname& key) { return "<KeyFieldname "s + toStringData(key) + ">"; },
[](const UserFieldname& user) { return "<UserFieldname "s + user + ">"; },
[](const FieldnamePath& path) {
return visit(OverloadedVisitor{[&](const ProjectionPath& projPath) {
return "<ProjectionPath "s +
path::vectorToString(projPath) + ">";
},
[&](const PositionalProjectionPath& posProjPath) {
return "<PositionalProjectionPath "s +
path::vectorToString(posProjPath) + ">";
},
[&](const SortPath& sortPath) {
return "<SortPath "s +
path::vectorToString(sortPath) + ">";
}},
path);
}},
fieldname);
}
auto printNonZeroKey(const NonZeroKey& nonZeroKey) {
return visit(OverloadedVisitor{
[](const int& keyInt) { return "int "s + std::to_string(keyInt); },
[](const long long& keyLong) { return "long "s + std::to_string(keyLong); },
[](const double& keyDouble) { return "double "s + std::to_string(keyDouble); },
[](const Decimal128& keyDecimal) {
return "decimal "s + keyDecimal.toString();
}},
nonZeroKey);
}
template <typename T>
auto printValue(const T& payload) {
return visit(
OverloadedVisitor{
[](const CNode::ArrayChildren&) { return "<Array>"s; },
[](const CNode::ObjectChildren&) { return "<Object>"s; },
[](const CompoundInclusionKey&) { return "<CompoundInclusionKey>"s; },
[](const CompoundExclusionKey&) { return "<CompoundExclusionKey>"s; },
[](const CompoundInconsistentKey&) { return "<CompoundInconsistentKey>"s; },
[](const KeyValue& value) { return "<KeyValue "s + toStringData(value) + ">"; },
[](const NonZeroKey& nonZeroKey) {
return "<NonZeroKey of type "s + printNonZeroKey(nonZeroKey) + ">";
},
[](const ValuePath& valuePath) {
return visit(OverloadedVisitor{[&](const AggregationPath& aggPath) {
return "<AggregationPath "s +
path::vectorToString(aggPath) + ">";
},
[&](const AggregationVariablePath& aggVarPath) {
return "<AggregationVariablePath "s +
path::vectorToString(aggVarPath) + ">";
}},
valuePath);
},
[](const UserDouble& userDouble) {
return "<UserDouble "s + std::to_string(userDouble) + ">";
},
[](const UserString& userString) { return "<UserString "s + userString + ">"; },
[](const UserBinary& userBinary) {
return "<UserBinary "s + typeName(userBinary.type) + ", " +
hexblob::encode(userBinary.data, std::max(userBinary.length, 0)) + ">";
},
[](const UserUndefined& userUndefined) { return "<UserUndefined>"s; },
[](const UserObjectId& userObjectId) {
return "<UserObjectId "s + userObjectId.toString() + ">";
},
[](const UserBoolean& userBoolean) {
return "<UserBoolean "s + (userBoolean ? "true" : "false") + ">";
},
[](const UserDate& userDate) {
return "<UserDate "s +
[&] {
if (auto string =
TimeZoneDatabase::utcZone().formatDate(kIsoFormatStringZ, userDate);
string.isOK())
return string.getValue();
else
return "illegal date"s;
}() +
">";
},
[](const UserNull& userNull) { return "<UserNull>"s; },
[](const UserRegex& userRegex) {
return "<UserRegex "s + "/" + userRegex.pattern + "/" + userRegex.flags + ">";
},
[](const UserDBPointer& userDBPointer) {
return "<UserDBPointer "s + userDBPointer.ns + ", " + userDBPointer.oid.toString() +
">";
},
[](const UserJavascript& userJavascript) {
return "<UserJavascript "s + userJavascript.code + ">";
},
[](const UserSymbol& userSymbol) { return "<UserSymbol "s + userSymbol.symbol + ">"; },
[](const UserJavascriptWithScope& userJavascriptWithScope) {
return "<UserJavascriptWithScope "s + userJavascriptWithScope.code + ", " +
userJavascriptWithScope.scope.toString() + ">";
},
[](const UserInt& userInt) { return "<UserInt "s + std::to_string(userInt) + ">"; },
[](const UserTimestamp& userTimestamp) {
return "<UserTimestamp "s + userTimestamp.toString() + ">";
},
[](const UserLong& userLong) { return "<UserLong "s + std::to_string(userLong) + ">"; },
[](const UserDecimal& userDecimal) {
return "<UserDecimal "s + userDecimal.toString() + ">";
},
[](const UserMinKey& userMinKey) { return "<UserMinKey>"s; },
[](const UserMaxKey& userMaxKey) {
return "<UserMaxKey>"s;
}},
payload);
}
} // namespace
std::string CNode::toStringHelper(int numTabs) const {
return visit(
OverloadedVisitor{
[numTabs](const ArrayChildren& children) {
return std::accumulate(children.cbegin(),
children.cend(),
tabs(numTabs) + "[\n",
[numTabs](auto&& string, auto&& child) {
return string + child.toStringHelper(numTabs + 1) + "\n";
}) +
tabs(numTabs) + "]";
},
[numTabs](const ObjectChildren& children) {
return std::accumulate(children.cbegin(),
children.cend(),
tabs(numTabs) + "{\n",
[numTabs](auto&& string, auto&& childpair) {
return string + tabs(numTabs) +
printFieldname(childpair.first) + " :\n" +
childpair.second.toStringHelper(numTabs + 1) + "\n";
}) +
tabs(numTabs) + "}";
},
[numTabs](const CompoundInclusionKey& compoundKey) {
return tabs(numTabs) + "<CompoundInclusionKey>\n" +
compoundKey.obj->toStringHelper(numTabs + 1);
},
[numTabs](const CompoundExclusionKey& compoundKey) {
return tabs(numTabs) + "<CompoundExclusionKey>\n" +
compoundKey.obj->toStringHelper(numTabs + 1);
},
[numTabs](const CompoundInconsistentKey& compoundKey) {
return tabs(numTabs) + "<CompoundInconsistentKey>\n" +
compoundKey.obj->toStringHelper(numTabs + 1);
},
[this, numTabs](auto&&) {
return tabs(numTabs) + printValue(payload);
}},
payload);
}
std::pair<BSONObj, bool> CNode::toBsonWithArrayIndicator() const {
auto addChild = [](auto&& bson, auto&& fieldname, auto&& child) {
// This is a non-compound field. pull the BSONElement out of it's BSONObj shell and add it.
if (auto [childBson, isArray] = child.toBsonWithArrayIndicator();
!childBson.isEmpty() && childBson.firstElementFieldNameStringData().empty())
return bson.addField(
childBson
.replaceFieldNames(BSON(std::forward<decltype(fieldname)>(fieldname) << ""))
.firstElement());
// This field is an array. Reconstruct with BSONArray and add it.
else if (isArray)
return bson.addField(
BSON(std::forward<decltype(fieldname)>(fieldname) << BSONArray{childBson})
.firstElement());
// This field is an object. Add it directly.
else
return bson.addField(
BSON(std::forward<decltype(fieldname)>(fieldname) << childBson).firstElement());
};
return visit(
OverloadedVisitor{
// Build an array which will lose its identity and appear as a BSONObj
[&](const ArrayChildren& children) {
return std::pair{
std::accumulate(children.cbegin(),
children.cend(),
BSONObj{},
[&, fieldCount = 0u](auto&& bson, auto&& child) mutable {
return addChild(std::forward<decltype(bson)>(bson),
std::to_string(fieldCount++),
std::forward<decltype(child)>(child));
}),
true};
},
// Build an object in a BSONObj.
[&](const ObjectChildren& children) {
return std::pair{std::accumulate(children.cbegin(),
children.cend(),
BSONObj{},
[&](auto&& bson, auto&& childPair) {
return addChild(
std::forward<decltype(bson)>(bson),
printFieldname(childPair.first),
childPair.second);
}),
false};
},
// Build a compound inclusion key wrapper in a BSONObj.
[&](const CompoundInclusionKey& compoundKey) {
return std::pair{addChild(BSONObj{}, "<CompoundInclusionKey>"s, *compoundKey.obj),
false};
},
// Build a compound exclusion key wrapper in a BSONObj.
[&](const CompoundExclusionKey& compoundKey) {
return std::pair{addChild(BSONObj{}, "<CompoundExclusionKey>"s, *compoundKey.obj),
false};
},
// Build a compound exclusion key wrapper in a BSONObj.
[&](const CompoundInconsistentKey& compoundKey) {
return std::pair{
addChild(BSONObj{}, "<CompoundInconsistentKey>"s, *compoundKey.obj), false};
},
// Build a non-compound field in a BSONObj shell.
[this](auto&&) {
return std::pair{BSON("" << printValue(payload)), false};
}},
payload);
}
bool CNode::isNumber() const {
return visit(OverloadedVisitor{
[](const UserLong&) { return true; },
[](const UserDouble&) { return true; },
[](const UserDecimal&) { return true; },
[](const UserInt&) { return true; },
[](auto&&) { return false; },
},
payload);
}
int CNode::numberInt() const {
// BSONElement has no safeNumberInt, so use CNode::numberLong which uses
// BSONElement::safeNumberLong. We don't want to use BSONElement::numberInt because it has
// undefined behavior for certain inputs (for one example, NaN).
// safeNumberLong returns the LLONG_MIN/LLONG_MAX when the original value is too big/small
// for a long long, so imitate that behavior here for int.
long long val = numberLong();
constexpr int max = std::numeric_limits<int>::max();
constexpr int min = std::numeric_limits<int>::min();
if (val > static_cast<long long>(max))
return max;
if (val < static_cast<long long>(min))
return min;
return static_cast<long long>(val);
}
long long CNode::numberLong() const {
return visit(
OverloadedVisitor{[](const UserDouble& userDouble) {
return (BSON("" << userDouble).firstElement()).safeNumberLong();
},
[](const UserInt& userInt) {
return (BSON("" << userInt).firstElement()).safeNumberLong();
},
[](const UserLong& userLong) { return userLong; },
[](const UserDecimal& userDecimal) {
return (BSON("" << userDecimal).firstElement()).safeNumberLong();
},
[](auto&&) -> UserLong {
MONGO_UNREACHABLE
}},
payload);
}
} // namespace mongo

View File

@ -1,265 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <boost/move/utility_core.hpp>
#include <boost/optional.hpp>
#include <boost/optional/optional.hpp>
#include <iosfwd>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsontypes_util.h"
#include "mongo/bson/oid.h"
#include "mongo/bson/timestamp.h"
#include "mongo/bson/util/builder_fwd.h"
#include "mongo/db/cst/compound_key.h"
#include "mongo/db/cst/key_fieldname.h"
#include "mongo/db/cst/key_value.h"
#include "mongo/db/cst/path.h"
#include "mongo/platform/basic.h"
#include "mongo/platform/decimal128.h"
#include "mongo/util/assert_util_core.h"
#include "mongo/util/time_support.h"
namespace mongo {
using UserFieldname = std::string;
// These all indicate simple inclusion projection and are used as leaves in CompoundInclusion.
using NonZeroKey = std::variant<int, long long, double, Decimal128>;
// These are the non-compound types from bsonspec.org.
using UserDouble = double;
using UserString = std::string;
using UserBinary = BSONBinData;
struct UserUndefined {};
using UserObjectId = OID;
using UserBoolean = bool;
using UserDate = Date_t;
struct UserNull {};
using UserRegex = BSONRegEx;
using UserDBPointer = BSONDBRef;
using UserJavascript = BSONCode;
using UserSymbol = BSONSymbol;
using UserJavascriptWithScope = BSONCodeWScope;
using UserInt = int;
using UserTimestamp = Timestamp;
using UserLong = long long;
using UserDecimal = Decimal128;
struct UserMinKey {};
struct UserMaxKey {};
enum class ProjectionType : char { inclusion, exclusion, inconsistent };
struct CNode {
static auto noopLeaf() {
return CNode{ObjectChildren{}};
}
/**
* Produce a string formatted with tabs and endlines that describes the CST underneath this
* CNode.
*/
auto toString() const {
return toStringHelper(0) + "\n";
}
friend std::ostream& operator<<(std::ostream& stream, const CNode& cst) {
return stream << cst.toString();
}
friend StringBuilder& operator<<(StringBuilder& stream, const CNode& cst) {
return stream << cst.toString();
}
/**
* Produce BSON representing this CST. This is for debugging and testing with structured output,
* not for decompiling to the input query. The produced BSON will consist of arrays, objects,
* and descriptive strings only. This version also returns bool that indicates if the returned
* BSON is a BSONArray.
*/
std::pair<BSONObj, bool> toBsonWithArrayIndicator() const;
/**
* Produce BSON representing this CST. This is for debugging and testing with structured output,
* not for decompiling to the input query. The produced BSON will consist of arrays, objects,
* and descriptive strings only.
*/
BSONObj toBson() const {
return toBsonWithArrayIndicator().first;
}
/*
* Produce the children of this CNode representing an array. Throws a fatal exception if this
* CNode does not represent an array. Const version.
*/
auto& arrayChildren() const {
return get<ArrayChildren>(payload);
}
/*
* Produce the children of this CNode representing an array. Throws a fatal exception if this
* CNode does not represent an array. Non-const version.
*/
auto& arrayChildren() {
return get<ArrayChildren>(payload);
}
/*
* Produce the children of this CNode representing an object. Throws a fatal exception if this
* CNode does not represent an object. Const version.
*/
auto& objectChildren() const {
return get<ObjectChildren>(payload);
}
/*
* Produce the children of this CNode representing an object. Throws a fatal exception if this
* CNode does not represent an object. Non-const version.
*/
auto& objectChildren() {
return get<ObjectChildren>(payload);
}
/*
* Produce the KeyFieldname of the first element of this CNode representing an object. Throws a
* fatal exception if this CNode does not represent an object, if it is an empty object or if
* the first element does not have a KeyFieldname. Const version.
*/
auto& firstKeyFieldname() const {
dassert(objectChildren().size() > 0);
return get<KeyFieldname>(objectChildren().begin()->first);
}
/*
* Produce the KeyFieldname of the first element of this CNode representing an object. Throws a
* fatal exception if this CNode does not represent an object, if it is an empty object or if
* the first element does not have a KeyFieldname. Non-const version.
*/
auto& firstKeyFieldname() {
dassert(objectChildren().size() > 0);
return get<KeyFieldname>(objectChildren().begin()->first);
}
/*
* Returns whether the payload indicates inclusion/exclusion or inconsistency through a key.
* Note that this returns none for ObjectChildren payloads even if they indicate a computed
* projection which can be treated as inclusion in projection type determination contexts.
*/
auto projectionType() const {
if (holds_alternative<NonZeroKey>(payload) ||
holds_alternative<CompoundInclusionKey>(payload) ||
(holds_alternative<KeyValue>(payload) && get<KeyValue>(payload) == KeyValue::trueKey))
return boost::optional<ProjectionType>{ProjectionType::inclusion};
else if (holds_alternative<CompoundExclusionKey>(payload) ||
(holds_alternative<KeyValue>(payload) && [&] {
switch (get<KeyValue>(payload)) {
case KeyValue::intZeroKey:
case KeyValue::longZeroKey:
case KeyValue::doubleZeroKey:
case KeyValue::decimalZeroKey:
case KeyValue::falseKey:
return true;
default:
return false;
}
}()))
return boost::optional<ProjectionType>{ProjectionType::exclusion};
else if (holds_alternative<CompoundInconsistentKey>(payload))
return boost::optional<ProjectionType>{ProjectionType::inconsistent};
else
return boost::optional<ProjectionType>{};
}
/**
* Returns true if the value is of numeric type.
*/
bool isNumber() const;
/**
* Coerces the value to an int.
*
* Invalid to call if isNumber() is false.
*/
int numberInt() const;
/**
* Coerces the value to an int.
*
* Invalid to call if isNumber() is false.
*/
long long numberLong() const;
private:
std::string toStringHelper(int numTabs) const;
public:
using Fieldname = std::variant<KeyFieldname, UserFieldname, FieldnamePath>;
using ArrayChildren = std::vector<CNode>;
using ObjectChildren = std::vector<std::pair<Fieldname, CNode>>;
using Payload = std::variant<ArrayChildren,
ObjectChildren,
CompoundInclusionKey,
CompoundExclusionKey,
CompoundInconsistentKey,
KeyValue,
NonZeroKey,
ValuePath,
UserDouble,
UserString,
UserBinary,
UserUndefined,
UserObjectId,
UserBoolean,
UserDate,
UserNull,
UserRegex,
UserDBPointer,
UserJavascript,
UserSymbol,
UserJavascriptWithScope,
UserInt,
UserTimestamp,
UserLong,
UserDecimal,
UserMinKey,
UserMaxKey>;
Payload payload;
CNode() = default;
CNode(Payload p) : payload(std::move(p)){};
/*
* Returns whether this fieldname is the key fieldname representing the _id syntax.
*/
static auto fieldnameIsId(const CNode::Fieldname& name) {
return holds_alternative<KeyFieldname>(name) && get<KeyFieldname>(name) == KeyFieldname::id;
}
};
} // namespace mongo

View File

@ -1,92 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <memory>
#include <numeric>
#include <variant>
#include <boost/move/utility_core.hpp>
#include <boost/optional/optional.hpp>
#include "mongo/db/cst/c_node_disambiguation.h"
#include "mongo/db/cst/compound_key.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/overloaded_visitor.h" // IWYU pragma: keep
namespace mongo::c_node_disambiguation {
namespace {
ProjectionType disambiguateCNode(const CNode& cst) {
return visit(OverloadedVisitor{
[](const CNode::ObjectChildren& children) {
return *std::accumulate(
children.begin(),
children.end(),
boost::optional<ProjectionType>{},
[](auto&& currentProjType, auto&& child) {
const auto seenProjType =
holds_alternative<FieldnamePath>(child.first)
// This is part of the compound key and must be explored.
? disambiguateCNode(child.second)
// This is an arbitrary expression to produce a computed field.
: ProjectionType::inclusion;
if (!currentProjType)
return seenProjType;
else if (*currentProjType != seenProjType)
return ProjectionType::inconsistent;
else
return *currentProjType;
});
},
[&](auto&&) {
if (auto type = cst.projectionType())
// This is a key which indicates the projection type.
return *type;
else
// This is a value which will produce a computed field.
return ProjectionType::inclusion;
}},
cst.payload);
}
} // namespace
CNode disambiguateCompoundProjection(CNode project) {
switch (disambiguateCNode(project)) {
case ProjectionType::inclusion:
return CNode{CompoundInclusionKey{std::make_unique<CNode>(std::move(project))}};
case ProjectionType::exclusion:
return CNode{CompoundExclusionKey{std::make_unique<CNode>(std::move(project))}};
case ProjectionType::inconsistent:
return CNode{CompoundInconsistentKey{std::make_unique<CNode>(std::move(project))}};
}
MONGO_UNREACHABLE;
}
} // namespace mongo::c_node_disambiguation

View File

@ -1,63 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <iterator>
#include <string>
#include <utility>
#include <vector>
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/c_node_validation.h"
#include "mongo/db/cst/path.h"
#include "mongo/platform/basic.h"
/**
* Functions which perform additional disambiguation beyond what a context free grammar can handle.
* These take the form of CNode -> CNode tranformations with the output providing the correct form.
*/
namespace mongo::c_node_disambiguation {
/**
* Replace the syntax for compound projection with a tree explicitly representing it.
*/
CNode disambiguateCompoundProjection(CNode project);
inline FieldnamePath disambiguateProjectionPathType(std::vector<std::string> components,
c_node_validation::IsPositional positional) {
if (positional == c_node_validation::IsPositional::yes)
// Omit the trailing '$' since it's just input syntax.
return PositionalProjectionPath{{std::make_move_iterator(components.begin()),
std::make_move_iterator(std::prev(components.end()))}};
else
return ProjectionPath{std::move(components)};
}
} // namespace mongo::c_node_disambiguation

View File

@ -1,408 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <boost/move/utility_core.hpp>
#include <boost/optional/optional.hpp>
// IWYU pragma: no_include "ext/alloc_traits.h"
#include <algorithm>
#include <iterator>
#include <memory>
#include <set>
#include <type_traits>
#include <utility>
#include <variant>
#include "mongo/base/error_codes.h"
#include "mongo/base/status.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bson_depth.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/c_node_validation.h"
#include "mongo/db/cst/compound_key.h"
#include "mongo/db/cst/key_fieldname.h"
#include "mongo/db/cst/path.h"
#include "mongo/db/matcher/matcher_type_set.h"
#include "mongo/db/pipeline/field_path.h"
#include "mongo/db/pipeline/variable_validation.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/overloaded_visitor.h" // IWYU pragma: keep
#include "mongo/util/str.h"
namespace mongo::c_node_validation {
using namespace std::string_literals;
namespace {
template <typename Iter, typename EndFun>
StatusWith<IsInclusion> processAdditionalFieldsInclusionAssumed(const Iter& iter,
const EndFun& isEnd);
template <typename Iter, typename EndFun>
StatusWith<IsInclusion> processAdditionalFieldsExclusionAssumed(const Iter& iter,
const EndFun& isEnd);
auto isInclusionField(const CNode& project) {
if (auto type = project.projectionType())
switch (*type) {
case ProjectionType::inclusion:
// This is an inclusion Key.
return true;
case ProjectionType::exclusion:
// This is an exclusion Key.
return false;
default:
MONGO_UNREACHABLE;
}
else
// This is an arbitrary expression to produce a computed field (this counts as inclusion).
return true;
}
template <typename Iter, typename EndFun>
StatusWith<IsInclusion> processAdditionalFieldsInclusionConfirmed(const Iter& iter,
const EndFun& isEnd) {
if (!isEnd(iter)) {
if (CNode::fieldnameIsId(iter->first)) {
return processAdditionalFieldsInclusionConfirmed(std::next(iter), isEnd);
} else {
if (isInclusionField(iter->second))
return processAdditionalFieldsInclusionConfirmed(std::next(iter), isEnd);
else
return Status{ErrorCodes::FailedToParse,
"project containing inclusion and/or computed fields must "
"contain no exclusion fields"};
}
} else {
return IsInclusion::yes;
}
}
template <typename Iter, typename EndFun>
StatusWith<IsInclusion> processAdditionalFieldsExclusionConfirmed(const Iter& iter,
const EndFun& isEnd) {
if (!isEnd(iter)) {
if (CNode::fieldnameIsId(iter->first)) {
return processAdditionalFieldsExclusionConfirmed(std::next(iter), isEnd);
} else {
if (isInclusionField(iter->second))
return Status{ErrorCodes::FailedToParse,
"project containing exclusion fields must contain no "
"inclusion and/or computed fields"};
else
return processAdditionalFieldsExclusionConfirmed(std::next(iter), isEnd);
}
} else {
return IsInclusion::no;
}
}
template <typename Iter, typename EndFun>
StatusWith<IsInclusion> processAdditionalFieldsWhenAssuming(const Iter& iter, const EndFun& isEnd) {
if (CNode::fieldnameIsId(iter->first)) {
if (isInclusionField(iter->second))
return processAdditionalFieldsInclusionAssumed(std::next(iter), isEnd);
else
return processAdditionalFieldsExclusionAssumed(std::next(iter), isEnd);
} else {
if (isInclusionField(iter->second))
return processAdditionalFieldsInclusionConfirmed(std::next(iter), isEnd);
else
return processAdditionalFieldsExclusionConfirmed(std::next(iter), isEnd);
}
}
template <typename Iter, typename EndFun>
StatusWith<IsInclusion> processAdditionalFieldsInclusionAssumed(const Iter& iter,
const EndFun& isEnd) {
if (!isEnd(iter))
return processAdditionalFieldsWhenAssuming(iter, isEnd);
else
return IsInclusion::yes;
}
template <typename Iter, typename EndFun>
StatusWith<IsInclusion> processAdditionalFieldsExclusionAssumed(const Iter& iter,
const EndFun& isEnd) {
if (!isEnd(iter))
return processAdditionalFieldsWhenAssuming(iter, isEnd);
else
return IsInclusion::no;
}
Status validatePathComponent(const std::string& component) {
if (component.empty())
return Status{ErrorCodes::FailedToParse, "field path is empty"};
if (std::string::npos != component.find('\0'))
return Status{ErrorCodes::FailedToParse, "field path contains null byte"};
return Status::OK();
}
auto validateNotPrefix(const std::vector<StringData>& potentialPrefixOne,
const std::vector<StringData>& potentialPrefixTwo) {
// If all components examined are identical up to a point where one path is exhausted,
// one path is a prefix of the other (or they're equal but this equality is already checked
// by the set emplace operation).
for (auto n = decltype(potentialPrefixOne.size()){0ull};
n < std::min(potentialPrefixOne.size(), potentialPrefixTwo.size());
++n)
if (potentialPrefixOne[n] != potentialPrefixTwo[n])
return Status::OK();
return Status{ErrorCodes::FailedToParse,
"paths appearing in project conflict because one is a prefix of the other: "s +
path::vectorToString(potentialPrefixOne) + " & " +
path::vectorToString(potentialPrefixTwo)};
}
/**
* Validate a path by checking to make sure it was never seen by using set uniqueness. In addition
* to checking that it is not a prefix of another path and no path is a prefix of it. This function
* modifies seenPaths in order to keep track.
*/
auto validateNotRedundantOrPrefixConflicting(const std::vector<StringData>& currentPath,
std::set<std::vector<StringData>>* const seenPaths) {
// The set 'seenPaths' is lexicographically ordered and we check only the next and previous
// elements for the prefix relationship. If a path is a prefix of another path, that path
// must appear next in order based on the invariant that the set has no prefix relationships
// before the most recent 'emplace()'. If another path is the prefix of the emplaced path,
// it must appear directly previous in order since any sibling that could otherwise appear
// previous would be also prefixed by the path that prefixes the emplaced path and violate
// the invariant. Thus it sufficies to check only these two positions in the set after
// emplacing to guarantee there are no prefix relationships in the entire set.
if (auto&& [iter, notDuplicate] = seenPaths->emplace(currentPath); notDuplicate) {
if (iter != seenPaths->begin())
if (auto status = validateNotPrefix(currentPath, *std::prev(iter)); !status.isOK())
return status;
if (std::next(iter) != seenPaths->end())
if (auto status = validateNotPrefix(currentPath, *std::next(iter)); !status.isOK())
return status;
return Status::OK();
} else {
return Status{ErrorCodes::FailedToParse,
"path appears more than once in project: "s +
path::vectorToString(currentPath)};
}
}
Status addPathsFromTreeToSet(const CNode::ObjectChildren& children,
const std::vector<StringData>& previousPath,
std::set<std::vector<StringData>>* const seenPaths) {
for (auto&& child : children) {
// Add all path components which make up the fieldname of the current child to
// currentPath. FieldnamePath may introduce more than one if it originated from syntax
// like '{"a.b": 1}'.
auto currentPath = previousPath;
if (auto&& fieldname = get_if<FieldnamePath>(&child.first))
for (auto&& component : visit(
[](auto&& fn) -> auto&& { return fn.components; }, *fieldname))
currentPath.emplace_back(component);
// Or add a translaiton of _id if we have a key for that.
else
currentPath.emplace_back("_id"_sd);
// Ensure that the tree is constructed correctly. Confirm anything that's not a
// FieldnamePath is actually _id.
dassert(holds_alternative<FieldnamePath>(child.first) ||
(holds_alternative<KeyFieldname>(child.first) &&
get<KeyFieldname>(child.first) == KeyFieldname::id));
if (auto status = visit(
OverloadedVisitor{
[&](const CompoundInclusionKey& compoundKey) {
// In this context we have a compound inclusion key to descend into.
return addPathsFromTreeToSet(
std::as_const(compoundKey.obj->objectChildren()),
currentPath,
seenPaths);
},
[&](const CompoundExclusionKey& compoundKey) {
// In this context we have a compound exclusion key to descend into.
return addPathsFromTreeToSet(
std::as_const(compoundKey.obj->objectChildren()),
currentPath,
seenPaths);
},
[&](const CNode::ObjectChildren& objectChildren) {
if (holds_alternative<FieldnamePath>(objectChildren[0].first))
// In this context we have a project path object to recurse over.
return addPathsFromTreeToSet(objectChildren, currentPath, seenPaths);
else
// We have a leaf from the point of view of computing paths.
return validateNotRedundantOrPrefixConflicting(currentPath, seenPaths);
},
[&](auto&&) {
// We have a leaf from the point of view of computing paths.
return validateNotRedundantOrPrefixConflicting(currentPath, seenPaths);
}},
child.second.payload);
!status.isOK())
// If a redundant path is found, return early and report this.
return status;
}
return Status::OK();
}
template <typename T>
Status validateNumericType(T num) {
auto valueAsInt = BSON("" << num).firstElement().parseIntegerElementToInt();
if (!valueAsInt.isOK() || valueAsInt.getValue() == 0 ||
!isValidBSONType(valueAsInt.getValue())) {
if constexpr (std::is_same_v<std::decay_t<T>, UserDecimal>) {
return Status{ErrorCodes::FailedToParse,
str::stream() << "invalid numerical type code: " << num.toString()
<< " provided as argument"};
} else {
return Status{ErrorCodes::FailedToParse,
str::stream()
<< "invalid numerical type code: " << num << " provided as argument"};
}
}
return Status::OK();
}
Status validateSingleType(const CNode& element) {
return visit(
OverloadedVisitor{
[&](const UserDouble& dbl) { return validateNumericType(dbl); },
[&](const UserInt& num) { return validateNumericType(num); },
[&](const UserLong& lng) { return validateNumericType(lng); },
[&](const UserDecimal& dc) { return validateNumericType(dc); },
[&](const UserString& st) {
if (st == MatcherTypeSet::kMatchesAllNumbersAlias) {
return Status::OK();
}
auto optValue = findBSONTypeAlias(st);
if (!optValue) {
// The string "missing" can be returned from the $type agg expression, but is
// not valid for use in the $type match expression predicate. Return a special
// error message for this case.
if (st == StringData{typeName(BSONType::EOO)}) {
return Status{
ErrorCodes::FailedToParse,
"unknown type name alias 'missing' (to query for "
"non-existence of a field, use {$exists:false}) provided as argument"};
}
return Status{ErrorCodes::FailedToParse,
str::stream() << "unknown type name alias: '" << st
<< "' provided as argument"};
}
return Status::OK();
},
[&](auto&&) -> Status {
MONGO_UNREACHABLE;
}},
element.payload);
}
} // namespace
StatusWith<IsInclusion> validateProjectionAsInclusionOrExclusion(const CNode& projects) {
return processAdditionalFieldsInclusionAssumed(
projects.objectChildren().cbegin(),
[&](auto&& iter) { return iter == projects.objectChildren().cend(); });
}
Status validateNoConflictingPathsInProjectFields(const CNode& projects) {
// A collection of all paths previously seen. Purposefully ordered. Vector orders
// lexicographically.
auto seenPaths = std::set<std::vector<StringData>>{};
return addPathsFromTreeToSet(projects.objectChildren(), std::vector<StringData>{}, &seenPaths);
}
Status validateAggregationPath(const std::vector<std::string>& components) {
if (components.size() > BSONDepth::getMaxAllowableDepth())
return Status{ErrorCodes::FailedToParse,
"aggregation field path has too many dot-seperated parts"};
if (components[0][0] == '$')
return Status{ErrorCodes::FailedToParse,
"aggregation field path begins with dollar character"};
for (auto n = 0ull; n < components.size(); ++n)
if (auto status = validatePathComponent(components[n]); !status.isOK())
return status.withReason("component " + std::to_string(n) + " of aggregation "s +
status.reason());
return Status::OK();
}
Status validateVariableNameAndPathSuffix(const std::vector<std::string>& nameAndPathComponents) {
try {
variableValidation::validateNameForUserRead(nameAndPathComponents[0]);
} catch (AssertionException& ae) {
return Status{ae.code(), ae.reason()};
}
if (nameAndPathComponents.size() > BSONDepth::getMaxAllowableDepth())
return Status{ErrorCodes::FailedToParse,
"aggregation variable field path has too many dot-seperated parts"};
// Skip the variable prefix since it's already been checked.
for (auto n = 1ull; n < nameAndPathComponents.size(); ++n)
if (auto status = validatePathComponent(nameAndPathComponents[n]); !status.isOK())
return status.withReason("component " + std::to_string(n) +
" of aggregation variable "s + status.reason());
return Status::OK();
}
StatusWith<IsPositional> validateProjectionPathAsNormalOrPositional(
const std::vector<std::string>& components) {
if (components.size() > BSONDepth::getMaxAllowableDepth())
return Status{ErrorCodes::FailedToParse,
"projection field path has too many dot-seperated parts"};
auto isPositional =
components[components.size() - 1] == "$" ? IsPositional::yes : IsPositional::no;
if (isPositional == IsPositional::no && components[0][0] == '$')
return Status{ErrorCodes::FailedToParse,
"projection field path begins with dollar character"};
for (auto n = 0ull; n < components.size() - (isPositional == IsPositional::yes ? 1 : 0); ++n)
if (auto status = validatePathComponent(components[n]); !status.isOK())
return status.withReason("component " + std::to_string(n) + " of projection "s +
status.reason());
return isPositional;
}
Status validateSortPath(const std::vector<std::string>& pathComponents) {
try {
for (auto&& component : pathComponents) {
FieldPath::uassertValidFieldName(component);
}
} catch (AssertionException& ae) {
return Status{ae.code(), ae.reason()};
}
return Status::OK();
}
Status validateTypeOperatorArgument(const CNode& types) {
// If the CNode is an array, we need to validate all of the types within it.
if (auto&& children = get_if<CNode::ArrayChildren>(&types.payload)) {
for (auto&& child : (*children)) {
if (auto status = validateSingleType(child); !status.isOK()) {
return status;
}
}
return Status::OK();
}
return validateSingleType(types);
}
} // namespace mongo::c_node_validation

View File

@ -1,105 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <string>
#include <vector>
#include "mongo/base/status.h"
#include "mongo/base/status_with.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/matcher/matcher_type_set.h"
#include "mongo/platform/basic.h"
/**
* Functions which perform additional validation beyond what a context free grammar can handle.
* These return error messages which can be used to cause errors from inside the Bison parser.
*/
namespace mongo::c_node_validation {
enum class IsInclusion : bool { no, yes };
StatusWith<IsInclusion> validateProjectionAsInclusionOrExclusion(const CNode& projects);
Status validateNoConflictingPathsInProjectFields(const CNode& projects);
/**
* Performs the following checks:
* * Forbids empty path components.
* * Path length is limited to the max allowable BSON depth.
* * Forbids dollar characters.
* * Forbids null bytes.
*/
Status validateAggregationPath(const std::vector<std::string>& pathComponents);
/**
* Performs the following checks on the variable prefix:
* * Forbides emptiness.
* * Requires the first character to be a lowercase character or non-ascii.
* * Requires all subsequent characters to be an alphanumeric, underscores or non-ascii.
* Performs the following checks on the path components if any:
* * Forbids empty path components.
* * Path length is limited to the max allowable BSON depth.
* * Forbids dollar characters.
* * Forbids null bytes.
*/
Status validateVariableNameAndPathSuffix(const std::vector<std::string>& nameAndPathComponents);
enum class IsPositional : bool { no, yes };
/**
* Determines if the projection is positional and performs the following checks:
* * Forbids empty path components.
* * Path length is limited to the max allowable BSON depth.
* * Forbids dollar characters.
* * Forbids null bytes.
* 'pathComponents' is expected to contain at least one element.
*/
StatusWith<IsPositional> validateProjectionPathAsNormalOrPositional(
const std::vector<std::string>& pathComponents);
/**
* Verifies that the given path is valid in a sort spec. Performs the following checks:
* * Forbids empty path components.
* * Forbids dollar characters.
* * Forbids null bytes.
*/
Status validateSortPath(const std::vector<std::string>& pathComponents);
/**
* Given a CNode holding the argument given to a parsed $type operator, this function returns
* Status::OK if the argument is either a valid number representing a BSON type, a valid string
* representing a BSON type, or an array whose members are all valid number-or-string BSON type
* specifiers. If the argument does not meet these conditions, than it is not a valid argument to
* $type, and this function returns an error status along with an error message detailing why the
* argument is invalid.
*/
Status validateTypeOperatorArgument(const CNode& argument);
} // namespace mongo::c_node_validation

View File

@ -1,44 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/cst/compound_key.h"
#include "mongo/db/cst/c_node.h"
namespace mongo {
CompoundInclusionKey::CompoundInclusionKey(CNode cNode)
: obj{std::make_unique<CNode>(std::move(cNode))} {}
CompoundExclusionKey::CompoundExclusionKey(CNode cNode)
: obj{std::make_unique<CNode>(std::move(cNode))} {}
CompoundInconsistentKey::CompoundInconsistentKey(CNode cNode)
: obj{std::make_unique<CNode>(std::move(cNode))} {}
} // namespace mongo

View File

@ -1,89 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <memory>
#include <utility>
#include "mongo/platform/basic.h"
namespace mongo {
struct CNode;
// This indicates compound inclusion projection. Leaves for these subtrees are NonZeroKeys and
// ZeroKeys.
struct CompoundInclusionKey final {
CompoundInclusionKey() = default;
explicit CompoundInclusionKey(CNode cNode);
CompoundInclusionKey(std::unique_ptr<CNode> obj) : obj{std::move(obj)} {}
CompoundInclusionKey(CompoundInclusionKey&&) noexcept = default;
CompoundInclusionKey(const CompoundInclusionKey& other)
: obj(std::make_unique<CNode>(*other.obj)) {}
CompoundInclusionKey& operator=(CompoundInclusionKey&&) = default;
CompoundInclusionKey& operator=(const CompoundInclusionKey& other) {
obj = std::make_unique<CNode>(*other.obj);
return *this;
}
std::unique_ptr<CNode> obj;
};
// This indicates compound exclusion projection. Leaves for these subtrees are NonZeroKeys and
// ZeroKeys.
struct CompoundExclusionKey final {
CompoundExclusionKey() = default;
explicit CompoundExclusionKey(CNode cNode);
CompoundExclusionKey(std::unique_ptr<CNode> obj) : obj{std::move(obj)} {}
CompoundExclusionKey(CompoundExclusionKey&&) noexcept = default;
CompoundExclusionKey(const CompoundExclusionKey& other)
: obj(std::make_unique<CNode>(*other.obj)) {}
CompoundExclusionKey& operator=(CompoundExclusionKey&&) = default;
CompoundExclusionKey& operator=(const CompoundExclusionKey& other) {
obj = std::make_unique<CNode>(*other.obj);
return *this;
}
std::unique_ptr<CNode> obj;
};
// This indicates inconsitent compound exclusion projection. This type of projection is disallowed
// and will produce an error in $project.
struct CompoundInconsistentKey final {
CompoundInconsistentKey() = default;
explicit CompoundInconsistentKey(CNode cNode);
CompoundInconsistentKey(std::unique_ptr<CNode> obj) : obj{std::move(obj)} {}
CompoundInconsistentKey(CompoundInconsistentKey&&) noexcept = default;
CompoundInconsistentKey(const CompoundInconsistentKey& other)
: obj(std::make_unique<CNode>(*other.obj)) {}
CompoundInconsistentKey& operator=(CompoundInconsistentKey&&) = default;
CompoundInconsistentKey& operator=(const CompoundInconsistentKey& other) {
obj = std::make_unique<CNode>(*other.obj);
return *this;
}
std::unique_ptr<CNode> obj;
};
} // namespace mongo

View File

@ -1,125 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/platform/basic.h"
#include <benchmark/benchmark.h>
#include <boost/intrusive_ptr.hpp>
#include "mongo/unittest/unittest.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/processinfo.h"
#include "mongo/util/time_support.h"
#include "mongo/db/cst/bson_lexer.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/cst_match_translation.h"
#include "mongo/db/cst/parser_gen.hpp"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/query/query_test_service_context.h"
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/matcher/extensions_callback_noop.h"
namespace mongo {
namespace {
std::string getField(int index) {
const StringData kViableChars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"_sd;
invariant(size_t(index) < kViableChars.size());
return std::to_string(kViableChars[index]);
}
/**
* Builds a filter BSON with 'nFields' simple equality predicates.
*/
BSONObj buildSimpleMatch(int nFields) {
std::vector<std::string> genFields;
BSONObjBuilder filter;
filter.append("_id", 1);
for (auto i = 0; i < nFields; i++) {
genFields.emplace_back(getField(i));
filter.append(genFields.back(), i);
}
return filter.obj();
}
} // namespace
void BM_BisonMatchSimple(benchmark::State& state) {
auto filter = buildSimpleMatch(state.range(0));
QueryTestServiceContext testServiceContext;
auto opCtx = testServiceContext.makeOperationContext();
auto nss = NamespaceString("test.bm");
boost::intrusive_ptr<ExpressionContextForTest> expCtx =
new ExpressionContextForTest(opCtx.get(), nss);
ExtensionsCallbackNoop extensions;
// This is where recording starts.
for (auto keepRunning : state) {
CNode cst;
BSONLexer lexer{filter, ParserGen::token::START_MATCH};
ParserGen(lexer, &cst).parse();
benchmark::DoNotOptimize(
cst_match_translation::translateMatchExpression(cst, expCtx, extensions));
benchmark::ClobberMemory();
}
}
// The baseline benchmark is included here for comparison purposes, there's no interaction between
// this and the CST benchmark.
void BM_BaselineMatchSimple(benchmark::State& state) {
auto filter = buildSimpleMatch(state.range(0));
QueryTestServiceContext testServiceContext;
auto opCtx = testServiceContext.makeOperationContext();
auto nss = NamespaceString("test.bm");
boost::intrusive_ptr<ExpressionContextForTest> expCtx =
new ExpressionContextForTest(opCtx.get(), nss);
// This is where recording starts.
for (auto keepRunning : state) {
benchmark::DoNotOptimize(
MatchExpressionParser::parse(filter,
expCtx,
ExtensionsCallbackNoop(),
MatchExpressionParser::kAllowAllSpecialFeatures));
benchmark::ClobberMemory();
}
}
// The argument to the simple filter tests is the number of fields to match on.
BENCHMARK(BM_BaselineMatchSimple)->Arg(0)->Arg(10);
BENCHMARK(BM_BisonMatchSimple)->Arg(0)->Arg(10);
} // namespace mongo

View File

@ -1,234 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <string>
#include "mongo/base/error_codes.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/json.h"
#include "mongo/db/cst/bson_lexer.h"
#include "mongo/db/cst/parser_gen.hpp"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/framework.h"
#include "mongo/util/assert_util.h"
namespace mongo {
namespace {
TEST(CstErrorTest, EmptyStageSpec) {
auto input = fromjson("{pipeline: [{}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected end of object at element 'end object' "
"within array at index 0 of input pipeline");
}
TEST(CstErrorTest, UnknownStageName) {
// First stage.
{
auto input = fromjson("{pipeline: [{$unknownStage: {}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected $-prefixed fieldname at element "
"'$unknownStage' within array at index 0 of input pipeline");
}
// Subsequent stage.
{
auto input = fromjson("{pipeline: [{$limit: 1}, {$unknownStage: {}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected $-prefixed fieldname at element "
"'$unknownStage' within array at index 1 of input pipeline");
}
}
TEST(CstErrorTest, InvalidStageArgument) {
{
auto input = fromjson("{pipeline: [{$sample: 2}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected arbitrary integer, expecting object at element '2' within "
"'$sample' within array at index 0 of input pipeline");
}
{
auto input = fromjson("{pipeline: [{$project: {a: 1}}, {$limit: {}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected object at element 'start object' "
"within '$limit' within array at index 1 of input pipeline");
}
}
TEST(CstErrorTest, UnknownArgumentInStageSpec) {
{
auto input = fromjson("{pipeline: [{$sample: {huh: 1}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected fieldname, expecting size argument at element 'huh' within "
"'$sample' within array at index 0 of input pipeline");
}
{
auto input = fromjson("{pipeline: [{$project: {a: 1}}, {$limit: 1}, {$sample: {huh: 1}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected fieldname, expecting size argument at element 'huh' within "
"'$sample' within array at index 2 of input pipeline");
}
}
TEST(CstErrorTest, InvalidArgumentTypeWithinStageSpec) {
{
auto input = fromjson("{pipeline: [{$sample: {size: 'cmon'}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected string at element 'cmon' within 'size' within '$sample' "
"within array at index 0 of input pipeline");
}
{
auto input = fromjson("{pipeline: [{$project: {a: 1}}, {$sample: {size: true}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected true at element 'true' within 'size' "
"within '$sample' within array at index 1 of input pipeline");
}
}
TEST(CstErrorTest, MissingRequiredArgument) {
auto input = fromjson("{pipeline: [{$sample: {}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected end of object, expecting size argument at element 'end object' "
"within '$sample' within array at index 0 of input pipeline");
}
TEST(CstErrorTest, MissingRequiredArgumentOfMultiArgStage) {
auto input = fromjson("{pipeline: [{$unionWith: {pipeline: 0.0}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected pipeline argument, expecting coll argument at element 'pipeline' "
"within '$unionWith' within array at index 0 of input pipeline");
}
TEST(CstErrorTest, InvalidArgumentTypeForProjectionExpression) {
auto input = fromjson("{pipeline: [{$project: {a: {$eq: '$b'}}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected $-prefixed string, expecting array at "
"element '$b' within '$eq' within "
"'$project' within array at index 0 of input pipeline");
}
TEST(CstErrorTest, MixedProjectionTypes) {
auto input = fromjson("{pipeline: [{$project: {a: 1, b: 0}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"project containing inclusion and/or computed fields must contain no exclusion fields at "
"element '$project' within array at index 0 of input pipeline");
}
TEST(CstErrorTest, DeeplyNestedSyntaxError) {
auto input = fromjson("{pipeline: [{$project: {a: {$and: [1, {$or: [{$eq: '$b'}]}]}}}]}");
BSONLexer lexer(input["pipeline"].embeddedObject(), ParserGen::token::START_PIPELINE);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected $-prefixed string, expecting array at element '$b' within '$eq' "
"within "
"array at index 0 within '$or' within array at index 1 within '$and' within '$project' "
"within array at index 0 of input pipeline");
}
TEST(CstErrorTest, SortWithRandomIntFails) {
auto input = fromjson("{sort: {val: 5}}");
BSONLexer lexer(input["sort"].embeddedObject(), ParserGen::token::START_SORT);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected arbitrary integer at element '5' of input sort");
}
TEST(CstErrorTest, SortWithInvalidMetaFails) {
auto input = fromjson("{sort: {val: {$meta: \"str\"}}}");
BSONLexer lexer(input["sort"].embeddedObject(), ParserGen::token::START_SORT);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected string, expecting randVal or textScore "
"at element 'str' within '$meta' of input sort");
}
TEST(CstErrorTest, SortWithMetaSiblingKeyFails) {
auto input = fromjson("{sort: {val: {$meta: \"textScore\", someKey: 4}}}");
BSONLexer lexer(input["sort"].embeddedObject(), ParserGen::token::START_SORT);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected fieldname, expecting end of object at "
"element 'someKey' of input sort");
}
} // namespace
} // namespace mongo

File diff suppressed because it is too large Load Diff

View File

@ -1,314 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <string>
#include "mongo/base/error_codes.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/json.h"
#include "mongo/db/cst/bson_lexer.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/parser_gen.hpp"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/framework.h"
#include "mongo/util/assert_util.h"
namespace mongo {
namespace {
TEST(CstProjectTest, ParsesEmptyProjection) {
CNode output;
auto input = fromjson("{project: {}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(), "{ <KeyFieldname projectInclusion>: {} }");
}
TEST(CstProjectTest, ParsesBasicProjection) {
{
CNode output;
auto input = fromjson(
"{project: {a: 1.0, b: {c: NumberInt(1), d: NumberDecimal('1.0') }, _id: "
"NumberLong(1)}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(
output.toBson().toString(),
"{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: \"<NonZeroKey of type double "
"1.000000>\", <ProjectionPath b>: { "
"<CompoundInclusionKey>: { <ProjectionPath c>: \"<NonZeroKey of type int 1>\", "
"<ProjectionPath d>: \"<NonZeroKey "
"of type decimal 1.00000000000000>\" } }, <KeyFieldname id>: \"<NonZeroKey of type "
"long 1>\" } }");
}
{
CNode output;
auto input = fromjson(
"{project: {_id: 9.10, a: {$add: [4, 5, {$add: [6, 7, 8]}]}, b: {$atan2: "
"[1.0, {$add: [2, -3]}]}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(
output.toBson().toString(),
"{ <KeyFieldname projectInclusion>: { <KeyFieldname id>: \"<NonZeroKey of type double "
"9.100000>\", <ProjectionPath a>: { <KeyFieldname add>: [ \"<UserInt 4>\", \"<UserInt "
"5>\", { <KeyFieldname add>: [ \"<UserInt 6>\", \"<UserInt 7>\", \"<UserInt 8>\" ] } ] "
"}, <ProjectionPath b>: { <KeyFieldname atan2>: [ \"<UserDouble 1.000000>\", { "
"<KeyFieldname add>: [ \"<UserInt 2>\", \"<UserInt -3>\" ] } ] } } }");
}
{
CNode output;
auto input = fromjson("{project: {a: {$add: [6]}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(
output.toBson().toString(),
"{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: { <KeyFieldname add>: [ "
"\"<UserInt 6>\" ] } } }");
}
}
TEST(CstProjectTest, ParsesCompoundProjection) {
{
CNode output;
auto input = fromjson("{project: {a: 0.0, b: NumberInt(0), c: { d: { e: NumberLong(0)}}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname projectExclusion>: { <ProjectionPath a>: \"<KeyValue "
"doubleZeroKey>\", <ProjectionPath b>: \"<KeyValue intZeroKey>\", "
"<ProjectionPath c>: { <CompoundExclusionKey>: { <ProjectionPath d>: { "
"<ProjectionPath e>: \"<KeyValue longZeroKey>\" } } } } }");
}
{
CNode output;
auto input = fromjson("{project: {a: 0.0, b: NumberInt(0), \"c.d.e\": NumberLong(0)}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname projectExclusion>: { <ProjectionPath a>: \"<KeyValue "
"doubleZeroKey>\", <ProjectionPath b>: \"<KeyValue intZeroKey>\", "
"<ProjectionPath c.d.e>: \"<KeyValue longZeroKey>\" } }");
}
{
CNode output;
auto input = fromjson("{project: {a: 1.1, b: NumberInt(1), c: { \"d.e\": NumberLong(1)}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname projectInclusion>: { <ProjectionPath a>: \"<NonZeroKey of type "
"double 1.100000>\", <ProjectionPath b>: \"<NonZeroKey of type int 1>\", "
"<ProjectionPath c>: { <CompoundInclusionKey>: { <ProjectionPath d.e>: "
"\"<NonZeroKey of type long 1>\" } } } }");
}
}
TEST(CstProjectTest, ParsesPositonalProjection) {
{
CNode output;
auto input = fromjson("{project: {\"a.$\": 1}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname projectInclusion>: { <PositionalProjectionPath a>: "
"\"<NonZeroKey of type int 1>\" } }");
}
{
CNode output;
auto input = fromjson("{project: {\"a.b.c.$\": 1.0}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname projectInclusion>: { <PositionalProjectionPath a.b.c>: "
"\"<NonZeroKey of type double 1.000000>\" } }");
}
}
TEST(CstProjectTest, ParsesElemMatch) {
{
CNode output;
auto input = fromjson("{project: {\"a.b\": {$elemMatch: {c: 12}}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname projectInclusion>: { <ProjectionPath a.b>: { <KeyFieldname "
"elemMatch>: "
"{ <UserFieldname c>: \"<UserInt 12>\" } } } }");
}
{
CNode output;
auto input = fromjson(
"{project: {\"a.b\": {$elemMatch: {c: 22}}, \"c.d\": {$tan: {$add: [4, 9]}}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname projectInclusion>: { <ProjectionPath a.b>: { <KeyFieldname "
"elemMatch>: { <UserFieldname c>: \"<UserInt 22>\" } }, <ProjectionPath c.d>: { "
"<KeyFieldname tan>: { <KeyFieldname add>: [ \"<UserInt 4>\", \"<UserInt 9>\" ] "
"} } } }");
}
}
TEST(CstProjectTest, ParsesMeta) {
CNode output;
auto input = fromjson("{project: {\"a.b.c\": {$meta: \"textScore\"}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname projectInclusion>: { <ProjectionPath a.b.c>: { <KeyFieldname meta>: "
"\"<KeyValue textScore>\" } } }");
}
TEST(CstProjectTest, ParsesSlice) {
CNode output;
auto input = fromjson("{project: {\"a.b.c.d\": {$slice: 14}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname projectInclusion>: { <ProjectionPath a.b.c.d>: { <KeyFieldname "
"slice>: \"<UserInt 14>\" } } }");
}
TEST(CstGrammarTest, FailsToParseDottedPathBelowProjectOuterObjects) {
CNode output;
auto input = fromjson("{project: {a: [{b: 5}, {\"c.d\": 7}]}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
TEST(CstGrammarTest, FailsToParseRedundantPaths) {
{
CNode output;
auto input = fromjson("{project: {a: {b: 1}, \"a.b\": 1}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
{
CNode output;
auto input = fromjson("{project: {a: {b: {c: {$atan2: [1, 0]}}, \"b.c\": 1}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
}
TEST(CstGrammarTest, FailsToParsePrefixPaths) {
{
CNode output;
auto input = fromjson("{project: {a: 1, \"a.b\": 1}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
{
CNode output;
auto input = fromjson("{project: {a: {b: {c: {d: {$atan2: [1, 0]}}}, \"b.c\": 1}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
}
TEST(CstGrammarTest, FailsToParseMixedProject) {
{
CNode output;
auto input = fromjson("{project: {a: 1, b: 0.0}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
{
CNode output;
auto input = fromjson("{project: {a: 0, b: {$add: [5, 67]}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
}
TEST(CstGrammarTest, FailsToParseCompoundMixedProject) {
{
CNode output;
auto input = fromjson("{project: {a: {b: 1, c: 0.0}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
{
CNode output;
auto input = fromjson("{project: {a: {b: {c: {d: NumberLong(0)}, e: 45}}}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
}
TEST(CstGrammarTest, FailsToParseProjectWithDollarFieldNames) {
{
CNode output;
auto input = fromjson("{project: {$a: 1}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
{
CNode output;
auto input = fromjson("{project: {b: 1, $a: 1}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
{
CNode output;
auto input = fromjson("{project: {b: 1, $add: 1, c: 1}}");
BSONLexer lexer(input["project"].embeddedObject(), ParserGen::token::START_PROJECT);
auto parseTree = ParserGen(lexer, &output);
ASSERT_THROWS_CODE(parseTree.parse(), AssertionException, ErrorCodes::FailedToParse);
}
}
} // namespace
} // namespace mongo

View File

@ -1,367 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <list>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <boost/none.hpp>
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/bson/bsontypes_util.h"
#include "mongo/bson/oid.h"
#include "mongo/bson/timestamp.h"
#include "mongo/bson/unordered_fields_bsonobj_comparator.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/cst_pipeline_translation.h"
#include "mongo/db/cst/key_fieldname.h"
#include "mongo/db/cst/path.h"
#include "mongo/db/exec/document_value/document.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/document_source.h"
#include "mongo/db/pipeline/document_source_single_document_transformation.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/pipeline/pipeline.h"
#include "mongo/db/pipeline/transformer_interface.h"
#include "mongo/db/query/util/make_data_structure.h"
#include "mongo/platform/decimal128.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/framework.h"
#include "mongo/util/intrusive_counter.h"
#include "mongo/util/time_support.h"
namespace mongo {
namespace {
using namespace std::string_literals;
auto getExpCtx() {
auto nss = NamespaceString::createNamespaceString_forTest("db", "coll");
return boost::intrusive_ptr<ExpressionContextForTest>{new ExpressionContextForTest(nss)};
}
auto makePipelineContainingProjectStageWithLiteral(CNode&& literal) {
return CNode{CNode::ArrayChildren{CNode{CNode::ObjectChildren{
{KeyFieldname::projectInclusion,
CNode{CNode::ObjectChildren{
{ProjectionPath{makeVector<std::string>("a")},
CNode{CNode::ObjectChildren{{KeyFieldname::literal, std::move(literal)}}}}}}}}}}};
}
TEST(CstLiteralsTest, TranslatesDouble) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserDouble{5e-324}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << 5e-324)) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesString) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserString{"soup can"}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a"
<< BSON("$const"
<< "soup can")) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesBinary) {
auto cst =
makePipelineContainingProjectStageWithLiteral(CNode{UserBinary{"a\0b", 3, BinDataGeneral}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << BSONBinData("a\0b", 3, BinDataGeneral))) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesUndefined) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserUndefined{}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << BSONUndefined)) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesObjectId) {
auto cst = makePipelineContainingProjectStageWithLiteral(
CNode{UserObjectId{"01234567890123456789aaaa"}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << OID("01234567890123456789aaaa"))) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesBoolean) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserBoolean{false}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << false)) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesDate) {
auto cst = makePipelineContainingProjectStageWithLiteral(
CNode{UserDate{Date_t::fromMillisSinceEpoch(424242)}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << Date_t::fromMillisSinceEpoch(424242))) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesNull) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserNull{}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << BSONNULL)) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesRegex) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserRegex{".*", "i"}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << BSONRegEx(".*", "i"))) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesDBPointer) {
auto cst = makePipelineContainingProjectStageWithLiteral(
CNode{UserDBPointer{"db.c", OID("010203040506070809101112")}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a"
<< BSON("$const" << BSONDBRef("db.c", OID("010203040506070809101112")))) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesJavascript) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserJavascript{"5 === 5"}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << BSONCode("5 === 5"))) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesSymbol) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserSymbol{"foo"}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << BSONSymbol("foo"))) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesJavascriptWithScope) {
auto cst = makePipelineContainingProjectStageWithLiteral(
CNode{UserJavascriptWithScope{"6 === 6", BSONObj{}}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << BSONCodeWScope("6 === 6", BSONObj()))) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesInt) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserInt{777}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << 777)) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesTimestamp) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserTimestamp{4102444800, 1}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << Timestamp(4102444800, 1))) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesLong) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserLong{777777777777777777ll}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << 777777777777777777ll)) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesDecimal) {
auto cst = makePipelineContainingProjectStageWithLiteral(
CNode{UserDecimal{Decimal128::kLargestNegative}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << Decimal128::kLargestNegative)) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesMinKey) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserMinKey{}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << MINKEY)) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesMaxKey) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{UserMaxKey{}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << MAXKEY)) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesArray) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << BSONArray())) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesObject) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{CNode::ObjectChildren{}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a" << BSON("$const" << BSONObj())) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
TEST(CstLiteralsTest, TranslatesNestedLiteral) {
auto cst = makePipelineContainingProjectStageWithLiteral(CNode{CNode::ObjectChildren{
{UserFieldname{"a"}, CNode{UserMaxKey{}}},
{UserFieldname{"b"},
CNode{CNode::ObjectChildren{{UserFieldname{"1"}, CNode{UserDecimal{1.0}}},
{UserFieldname{"2"}, CNode{UserLong{2ll}}}}}},
{UserFieldname{"c"},
CNode{CNode::ArrayChildren{CNode{UserString{"foo"}}, CNode{UserSymbol{"bar"}}}}}}});
// DocumenSourceSingleDoucmentTransformation reorders fields so we need to be insensitive.
ASSERT(UnorderedFieldsBSONObjComparator{}.evaluate(
BSON("_id" << true << "a"
<< BSON("$const" << BSON("a" << MAXKEY << "b"
<< BSON("1" << Decimal128(1.0) << "2" << 2ll) << "c"
<< BSON_ARRAY("foo" << BSONSymbol("bar"))))) ==
dynamic_cast<DocumentSourceSingleDocumentTransformation&>(
**cst_pipeline_translation::translatePipeline(cst, getExpCtx())->getSources().begin())
.getTransformer()
.serializeTransformation(boost::none)
.toBson()));
}
} // namespace
} // namespace mongo

View File

@ -1,437 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <string>
#include "mongo/base/error_codes.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/json.h"
#include "mongo/db/cst/bson_lexer.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/parser_gen.hpp"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/framework.h"
#include "mongo/util/assert_util.h"
namespace mongo {
namespace {
TEST(CstMatchTest, ParsesEmptyPredicate) {
CNode output;
auto input = fromjson("{filter: {}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(), "{}");
}
TEST(CstMatchTest, ParsesEqualityPredicates) {
CNode output;
auto input = fromjson("{filter: {a: 5.0, b: NumberInt(10), _id: NumberLong(15)}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: \"<UserDouble 5.000000>\", <UserFieldname b>: \"<UserInt "
"10>\", <UserFieldname _id>: \"<UserLong 15>\" }");
}
TEST(CstMatchTest, ParsesLogicalOperatorsWithOneChild) {
{
CNode output;
auto input = fromjson("{filter: {$and: [{a: 1}]}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname andExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" } ] }");
}
{
CNode output;
auto input = fromjson("{filter: {$or: [{a: 1}]}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname orExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" } ] }");
}
{
CNode output;
auto input = fromjson("{filter: {$nor: [{a: 1}]}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname norExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" } ] }");
}
}
TEST(CstMatchTest, ParsesLogicalOperatorsWithMultipleChildren) {
{
CNode output;
auto input = fromjson("{filter: {$and: [{a: 1}, {b: 'bee'}]}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname andExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" }, { "
"<UserFieldname b>: \"<UserString bee>\" } ] }");
}
{
CNode output;
auto input = fromjson("{filter: {$or: [{a: 1}, {b: 'bee'}]}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname orExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" }, { "
"<UserFieldname b>: \"<UserString bee>\" } ] }");
}
{
CNode output;
auto input = fromjson("{filter: {$nor: [{a: 1}, {b: 'bee'}]}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname norExpr>: [ { <UserFieldname a>: \"<UserInt 1>\" }, { "
"<UserFieldname b>: \"<UserString bee>\" } ] }");
}
}
TEST(CstMatchTest, ParsesNotWithRegex) {
CNode output;
auto input = fromjson("{filter: {a: {$not: /^a/}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: { <KeyFieldname notExpr>: \"<UserRegex /^a/>\" } }");
}
TEST(CstMatchTest, ParsesNotWithChildExpression) {
CNode output;
auto input = fromjson("{filter: {a: {$not: {$not: /^a/}}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: { <KeyFieldname notExpr>: { <KeyFieldname notExpr>: "
"\"<UserRegex /^a/>\" } } }");
}
TEST(CstMatchTest, ParsesExistsWithSimpleValueChild) {
CNode output;
auto input = fromjson("{filter: {a: {$exists: true}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: { <KeyFieldname existsExpr>: \"<UserBoolean true>\" } }");
}
TEST(CstMatchTest, ParsesExistsWithCompoundValueChild) {
CNode output;
auto input = fromjson("{filter: {a: {$exists: [\"hello\", 5, true, \"goodbye\"]}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: { <KeyFieldname existsExpr>: [ \"<UserString hello>\", "
"\"<UserInt 5>\", "
"\"<UserBoolean true>\", \"<UserString goodbye>\" ] } }");
}
TEST(CstMatchTest, ParsesExistsWithObjectChild) {
CNode output;
auto input = fromjson("{filter: {a: {$exists: {a: false }}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: { <KeyFieldname existsExpr>: { <UserFieldname a>: "
"\"<UserBoolean false>\" } } }");
}
TEST(CstMatchTest, ParsesTypeSingleArgument) {
// Check that $type parses with a string argument - a BSON type alias
{
CNode output;
auto input = fromjson("{filter: {a: {$type: \"bool\" }}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: { <KeyFieldname type>: \"<UserString bool>\" } }");
}
// Check that $type parses a number (corresponding to a BSON type)
{
CNode output;
auto input = fromjson("{filter: {a: {$type: 1}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: { <KeyFieldname type>: \"<UserInt 1>\" } }");
}
}
TEST(CstMatchTest, ParsersTypeArrayArgument) {
CNode output;
auto input = fromjson("{filter: {a: {$type: [\"number\", 5, 127, \"objectId\"]}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: { <KeyFieldname type>: [ \"<UserString number>\", \"<UserInt "
"5>\", \"<UserInt 127>\", "
"\"<UserString objectId>\" ] } }");
}
TEST(CstMatchTest, ParsesCommentWithSimpleValueChild) {
CNode output;
auto input = fromjson("{filter: {a: 1, $comment: true}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: \"<UserInt 1>\", <KeyFieldname commentExpr>: \"<UserBoolean "
"true>\" }");
}
TEST(CstMatchTest, ParsesCommentWithCompoundValueChild) {
CNode output;
auto input = fromjson("{filter: {a: 1, $comment: [\"hi\", 5]}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: \"<UserInt 1>\", <KeyFieldname commentExpr>: [ \"<UserString "
"hi>\", \"<UserInt 5>\" ] }");
}
TEST(CstMatchTest, ParsesExpr) {
CNode output;
auto input = fromjson("{filter: {$expr: 123}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(), "{ <KeyFieldname expr>: \"<UserInt 123>\" }");
}
TEST(CstMatchTest, ParsesText) {
CNode output;
auto input = fromjson("{filter: {$text: {$search: \"abc\"}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname text>: { "
"<KeyFieldname caseSensitive>: \"<KeyValue absentKey>\", "
"<KeyFieldname diacriticSensitive>: \"<KeyValue absentKey>\", "
"<KeyFieldname language>: \"<KeyValue absentKey>\", "
"<KeyFieldname search>: \"<UserString abc>\" } }");
}
TEST(CstMatchTest, ParsesTextOptions) {
CNode output;
auto input = fromjson(
"{filter: {$text: {"
"$search: \"abc\", "
"$caseSensitive: true, "
"$diacriticSensitive: true, "
"$language: \"asdfzxcv\" } } }");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname text>: { "
"<KeyFieldname caseSensitive>: \"<UserBoolean true>\", "
"<KeyFieldname diacriticSensitive>: \"<UserBoolean true>\", "
"<KeyFieldname language>: \"<UserString asdfzxcv>\", "
"<KeyFieldname search>: \"<UserString abc>\" } }");
}
TEST(CstMatchTest, ParsesWhere) {
CNode output;
auto input = fromjson("{filter: {$where: \"return true;\"}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <KeyFieldname where>: \"<UserString return true;>\" }");
}
TEST(CstMatchTest, FailsToParseNotWithNonObject) {
CNode output;
auto input = fromjson("{filter: {a: {$not: 1}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected 1 (int), expecting object or regex at "
"element '1' within '$not' of input filter");
}
TEST(CstMatchTest, FailsToParseUnknownOperatorWithinNotExpression) {
CNode output;
auto input = fromjson("{filter: {a: {$not: {$and: [{a: 1}]}}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE(
ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
}
TEST(CstMatchTest, FailsToParseNotWithEmptyObject) {
CNode output;
auto input = fromjson("{filter: {a: {$not: {}}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE(
ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
}
TEST(CstMatchTest, FailsToParseDollarPrefixedPredicates) {
{
auto input = fromjson("{filter: {$atan2: [3, 5]}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected ATAN2 at element '$atan2' of input filter");
}
{
auto input = fromjson("{filter: {$prefixed: 5}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected $-prefixed fieldname at element '$prefixed' of input filter");
}
{
auto input = fromjson("{filter: {$$ROOT: 5}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected $-prefixed fieldname at element '$$ROOT' of input filter");
}
}
TEST(CstMatchTest, FailsToParseDollarPrefixedPredicatesWithinLogicalExpression) {
auto input = fromjson("{filter: {$and: [{$prefixed: 5}]}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE_AND_WHAT(
ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected $-prefixed fieldname at element '$prefixed' within array at "
"index 0 within '$and' of input filter");
}
TEST(CstMatchTest, FailsToParseNonArrayLogicalKeyword) {
auto input = fromjson("{filter: {$and: {a: 5}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected object, expecting array at element "
"'start object' within '$and' of input filter");
}
TEST(CstMatchTest, FailsToParseNonObjectWithinLogicalKeyword) {
auto input = fromjson("{filter: {$or: [5]}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected arbitrary integer, expecting object at "
"element '5' within array at index 0 within '$or' of input filter");
}
TEST(CstMatchTest, FailsToParseLogicalKeywordWithEmptyArray) {
auto input = fromjson("{filter: {$nor: []}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected end of array, expecting object at "
"element 'end array' within '$nor' of input filter");
}
TEST(CstMatchTest, FailsToParseTypeWithBadSpecifier) {
// Shouldn't parse if the number given isn't a valid BSON type specifier.
{
auto input = fromjson("{filter: {a: {$type: 0}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE(
ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
}
// Shouldn't parse if the string given isn't a valid BSON type alias.
{
auto input = fromjson("{filter: {$type: {a: \"notABsonType\"}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE(
ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
}
// Shouldn't parse if any argument isn't a valid BSON type alias.
{
auto input = fromjson("{filter: {a: {$type: [1, \"number\", \"notABsonType\"]}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE(
ParserGen(lexer, nullptr).parse(), AssertionException, ErrorCodes::FailedToParse);
}
}
TEST(CstMatchTest, ParsesMod) {
CNode output;
auto input = fromjson("{filter: {a: {$mod: [3, 2.0]}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
ASSERT_EQ(output.toBson().toString(),
"{ <UserFieldname a>: { <KeyFieldname matchMod>: ["
" \"<UserInt 3>\", \"<UserDouble 2.000000>\" ] } }");
}
TEST(CstMatchTest, FailsToParseModWithEmptyArray) {
auto input = fromjson("{filter: {a: {$mod: []}}}");
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
ASSERT_THROWS_CODE_AND_WHAT(ParserGen(lexer, nullptr).parse(),
AssertionException,
ErrorCodes::FailedToParse,
"syntax error, unexpected end of array at "
"element 'end array' within '$mod' of input filter");
}
} // namespace
} // namespace mongo

View File

@ -1 +0,0 @@

View File

@ -1,346 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <boost/move/utility_core.hpp>
#include <boost/optional/optional.hpp>
#include <boost/smart_ptr/intrusive_ptr.hpp>
// IWYU pragma: no_include "ext/alloc_traits.h"
#include <cstddef>
#include <set>
#include <string>
#include <type_traits>
#include <variant>
#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/bson/bsontypes_util.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/cst_match_translation.h"
#include "mongo/db/cst/cst_pipeline_translation.h"
#include "mongo/db/cst/key_fieldname.h"
#include "mongo/db/matcher/expression_expr.h"
#include "mongo/db/matcher/expression_leaf.h"
#include "mongo/db/matcher/expression_text_base.h"
#include "mongo/db/matcher/expression_tree.h"
#include "mongo/db/matcher/expression_type.h"
#include "mongo/db/matcher/matcher_type_set.h"
#include "mongo/platform/decimal128.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/overloaded_visitor.h" // IWYU pragma: keep
namespace mongo::cst_match_translation {
namespace {
std::unique_ptr<MatchExpression> translateMatchPredicate(
const CNode::Fieldname& fieldName,
const CNode& cst,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
const ExtensionsCallback& extensionsCallback);
std::unique_ptr<MatchExpression> translatePathExpression(const UserFieldname& fieldName,
const CNode::ObjectChildren& object);
/**
* Walk an array of nodes and produce a vector of MatchExpressions.
*/
template <class Type>
std::unique_ptr<Type> translateTreeExpr(const CNode::ArrayChildren& array,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
const ExtensionsCallback& extensionsCallback) {
auto expr = std::make_unique<Type>();
for (auto&& node : array) {
// Tree expressions require each element to be it's own match expression object.
expr->add(translateMatchExpression(node, expCtx, extensionsCallback));
}
return expr;
}
// Handles predicates of the form <fieldname>: { $not: <argument> }
std::unique_ptr<MatchExpression> translateNot(const UserFieldname& fieldName,
const CNode& argument) {
// $not can accept a regex or an object expression.
if (auto regex = get_if<UserRegex>(&argument.payload)) {
auto regexExpr = std::make_unique<RegexMatchExpression>(
StringData(fieldName), regex->pattern, regex->flags);
return std::make_unique<NotMatchExpression>(std::move(regexExpr));
}
auto root = std::make_unique<AndMatchExpression>();
root->add(translatePathExpression(fieldName, get<CNode::ObjectChildren>(argument.payload)));
return std::make_unique<NotMatchExpression>(std::move(root));
}
std::unique_ptr<MatchExpression> translateExists(const CNode::Fieldname& fieldName,
const CNode& argument) {
auto root = std::make_unique<ExistsMatchExpression>(StringData(get<UserFieldname>(fieldName)));
if (visit(OverloadedVisitor{
[&](const UserLong& userLong) { return userLong != 0; },
[&](const UserDouble& userDbl) { return userDbl != 0; },
[&](const UserDecimal& userDc) { return userDc.isNotEqual(Decimal128(0)); },
[&](const UserInt& userInt) { return userInt != 0; },
[&](const UserBoolean& b) { return b; },
[&](const UserNull&) { return false; },
[&](const UserUndefined&) { return false; },
[&](auto&&) {
return true;
}},
argument.payload)) {
return root;
}
return std::make_unique<NotMatchExpression>(root.release());
}
MatcherTypeSet getMatcherTypeSet(const CNode& argument) {
MatcherTypeSet ts;
auto add_individual_to_type_set = [&](const CNode& a) {
return visit(
OverloadedVisitor{
[&](const UserLong& userLong) {
auto valueAsInt =
BSON("" << userLong).firstElement().parseIntegerElementToInt();
ts.bsonTypes.insert(static_cast<BSONType>(valueAsInt.getValue()));
},
[&](const UserDouble& userDbl) {
auto valueAsInt = BSON("" << userDbl).firstElement().parseIntegerElementToInt();
ts.bsonTypes.insert(static_cast<BSONType>(valueAsInt.getValue()));
},
[&](const UserDecimal& userDc) {
auto valueAsInt = BSON("" << userDc).firstElement().parseIntegerElementToInt();
ts.bsonTypes.insert(static_cast<BSONType>(valueAsInt.getValue()));
},
[&](const UserInt& userInt) {
auto valueAsInt = BSON("" << userInt).firstElement().parseIntegerElementToInt();
ts.bsonTypes.insert(static_cast<BSONType>(valueAsInt.getValue()));
},
[&](const UserString& s) {
if (StringData{s} == MatcherTypeSet::kMatchesAllNumbersAlias) {
ts.allNumbers = true;
return;
}
auto optValue = findBSONTypeAlias(s);
invariant(optValue);
ts.bsonTypes.insert(*optValue);
},
[&](auto&&) {
MONGO_UNREACHABLE;
}},
a.payload);
};
if (auto children = get_if<CNode::ArrayChildren>(&argument.payload)) {
for (const auto& child : (*children)) {
add_individual_to_type_set(child);
}
} else {
add_individual_to_type_set(argument);
}
return ts;
}
// Handles predicates of the form <fieldname>: { ... }
// For example:
// { abc: {$not: 5} }
// { abc: {$eq: 0} }
// { abc: {$gt: 0, $lt: 2} }
// Examples of predicates not handled here:
// { abc: 5 }
// { $expr: ... }
// { $where: "return 1" }
// Note, this function does not require an ExpressionContext.
// The only MatchExpression that requires an ExpressionContext is $expr
// (if you include $where, which can desugar to $expr + $function).
std::unique_ptr<MatchExpression> translatePathExpression(const UserFieldname& fieldName,
const CNode::ObjectChildren& object) {
for (auto&& [op, argument] : object) {
switch (get<KeyFieldname>(op)) {
case KeyFieldname::notExpr:
return translateNot(fieldName, argument);
case KeyFieldname::existsExpr:
return translateExists(fieldName, argument);
case KeyFieldname::type:
return std::make_unique<TypeMatchExpression>(StringData(fieldName),
getMatcherTypeSet(argument));
case KeyFieldname::matchMod: {
const auto divisor = get<CNode::ArrayChildren>(argument.payload)[0].numberInt();
const auto remainder = get<CNode::ArrayChildren>(argument.payload)[1].numberInt();
return std::make_unique<ModMatchExpression>(
StringData(fieldName), divisor, remainder);
}
default:
MONGO_UNREACHABLE;
}
}
MONGO_UNREACHABLE;
}
// Take a variant and either get (by copying) the T it holds, or construct a default value using
// the callable. For example:
// getOr<int>(123, []() { return 0; }) == 123
// getOr<int>("x", []() { return 0; }) == 0
template <class T, class V, class F>
T getOr(const V& myVariant, F makeDefaultValue) {
if (auto* value = get_if<T>(&myVariant)) {
return *value;
} else {
return makeDefaultValue();
}
}
// Handles predicates of the form <fieldname>: <anything>
// For example:
// { abc: 5 }
// { abc: {$lt: 5} }
// Examples of predicates not handled here:
// { $where: "return 1" }
// { $and: ... }
std::unique_ptr<MatchExpression> translateMatchPredicate(
const CNode::Fieldname& fieldName,
const CNode& cst,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
const ExtensionsCallback& extensionsCallback) {
using namespace std::string_literals;
if (auto keyField = get_if<KeyFieldname>(&fieldName)) {
// Top level match expression.
switch (*keyField) {
case KeyFieldname::andExpr:
return translateTreeExpr<AndMatchExpression>(
cst.arrayChildren(), expCtx, extensionsCallback);
case KeyFieldname::orExpr:
return translateTreeExpr<OrMatchExpression>(
cst.arrayChildren(), expCtx, extensionsCallback);
case KeyFieldname::norExpr:
return translateTreeExpr<NorMatchExpression>(
cst.arrayChildren(), expCtx, extensionsCallback);
case KeyFieldname::commentExpr:
// comment expr is not added to the tree.
return nullptr;
case KeyFieldname::expr: {
// The ExprMatchExpression maintains (shared) ownership of expCtx,
// which the Expression from translateExpression depends on.
return std::make_unique<ExprMatchExpression>(
cst_pipeline_translation::translateExpression(
cst, expCtx.get(), expCtx->variablesParseState),
expCtx);
}
case KeyFieldname::text: {
const auto& args = cst.objectChildren();
dassert(verifyFieldnames(
{
KeyFieldname::caseSensitive,
KeyFieldname::diacriticSensitive,
KeyFieldname::language,
KeyFieldname::search,
},
args));
TextMatchExpressionBase::TextParams params;
params.caseSensitive = getOr<bool>(args[0].second.payload, []() {
return TextMatchExpressionBase::kCaseSensitiveDefault;
});
params.diacriticSensitive = getOr<bool>(args[1].second.payload, []() {
return TextMatchExpressionBase::kDiacriticSensitiveDefault;
});
params.language = getOr<std::string>(args[2].second.payload, []() { return ""s; });
params.query = get<std::string>(args[3].second.payload);
return extensionsCallback.createText(std::move(params));
}
case KeyFieldname::where: {
std::string code;
if (auto str = get_if<UserString>(&cst.payload)) {
code = *str;
} else if (auto js = get_if<UserJavascript>(&cst.payload)) {
code = std::string{js->code};
} else {
MONGO_UNREACHABLE;
}
return extensionsCallback.createWhere(expCtx, {std::move(code)});
}
default:
MONGO_UNREACHABLE;
}
} else {
// Expression is over a user fieldname.
return visit(
OverloadedVisitor{
[&](const CNode::ObjectChildren& userObject) -> std::unique_ptr<MatchExpression> {
return translatePathExpression(get<UserFieldname>(fieldName), userObject);
},
[&](const CNode::ArrayChildren& userObject) -> std::unique_ptr<MatchExpression> {
MONGO_UNREACHABLE;
},
// Other types are always treated as equality predicates.
[&](auto&& userValue) -> std::unique_ptr<MatchExpression> {
return std::make_unique<EqualityMatchExpression>(
StringData{get<UserFieldname>(fieldName)},
cst_pipeline_translation::translateLiteralLeaf(cst),
nullptr, /* TODO SERVER-49486: Add ErrorAnnotation for MatchExpressions */
expCtx->getCollator());
}},
cst.payload);
}
MONGO_UNREACHABLE;
}
} // namespace
std::unique_ptr<MatchExpression> translateMatchExpression(
const CNode& cst,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
const ExtensionsCallback& extensionsCallback) {
auto root = std::make_unique<AndMatchExpression>();
for (const auto& [fieldName, expr] : cst.objectChildren()) {
// A nullptr for 'translatedExpression' indicates that the particular operator should not
// be added to 'root'. The $comment operator currently follows this convention.
if (auto translatedExpression =
translateMatchPredicate(fieldName, expr, expCtx, extensionsCallback);
translatedExpression) {
root->add(std::move(translatedExpression));
}
}
return root;
}
bool verifyFieldnames(const std::vector<CNode::Fieldname>& expected,
const std::vector<std::pair<CNode::Fieldname, CNode>>& actual) {
if (expected.size() != actual.size())
return false;
for (size_t i = 0; i < expected.size(); ++i) {
if (expected[i] != actual[i].first)
return false;
}
return true;
}
} // namespace mongo::cst_match_translation

View File

@ -1,58 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <memory>
#include <utility>
#include <vector>
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/extensions_callback.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/platform/basic.h"
namespace mongo::cst_match_translation {
/**
* Walk an expression CNode and produce a MatchExpression.
*/
std::unique_ptr<MatchExpression> translateMatchExpression(
const CNode& cst,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
const ExtensionsCallback& extensionsCallback);
/**
* Check that the order of arguments is what we expect in an input expression.
*/
bool verifyFieldnames(const std::vector<CNode::Fieldname>& expected,
const std::vector<std::pair<CNode::Fieldname, CNode>>& actual);
} // namespace mongo::cst_match_translation

View File

@ -1,340 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <variant>
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonelement.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/bson/json.h"
#include "mongo/db/cst/bson_lexer.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/cst_match_translation.h"
#include "mongo/db/cst/parser_gen.hpp"
#include "mongo/db/matcher/expression_leaf.h"
#include "mongo/db/matcher/expression_tree.h"
#include "mongo/db/matcher/expression_type.h"
#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/matcher/matcher_type_set.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/bson_test_util.h"
#include "mongo/unittest/framework.h"
#include "mongo/util/intrusive_counter.h"
namespace mongo {
namespace {
auto getExpCtx() {
auto nss = NamespaceString::createNamespaceString_forTest("db", "coll");
return boost::intrusive_ptr<ExpressionContextForTest>{new ExpressionContextForTest(nss)};
}
auto translate(const CNode& cst) {
return cst_match_translation::translateMatchExpression(
cst, getExpCtx(), ExtensionsCallbackNoop{});
}
auto parseMatchToCst(BSONObj input) {
CNode output;
BSONLexer lexer(input["filter"].embeddedObject(), ParserGen::token::START_MATCH);
auto parseTree = ParserGen(lexer, &output);
ASSERT_EQ(0, parseTree.parse());
return output;
}
TEST(CstMatchTranslationTest, TranslatesEmpty) {
const auto cst = CNode{CNode::ObjectChildren{}};
auto match = translate(cst);
auto andExpr = dynamic_cast<AndMatchExpression*>(match.get());
ASSERT(andExpr);
ASSERT_EQ(0, andExpr->numChildren());
}
TEST(CstMatchTranslationTest, TranslatesSinglePredicate) {
const auto cst = CNode{CNode::ObjectChildren{{UserFieldname{"a"}, CNode{UserInt{1}}}}};
auto match = translate(cst);
ASSERT_BSONOBJ_EQ(match->serialize(), fromjson("{$and: [{a: {$eq: 1}}]}"));
}
TEST(CstMatchTranslationTest, TranslatesMultipleEqualityPredicates) {
const auto cst = CNode{CNode::ObjectChildren{
{UserFieldname{"a"}, CNode{UserInt{1}}},
{UserFieldname{"b"}, CNode{UserNull{}}},
}};
auto match = translate(cst);
ASSERT_BSONOBJ_EQ(match->serialize(), fromjson("{$and: [{a: {$eq: 1}}, {b: {$eq: null}}]}"));
}
TEST(CstMatchTranslationTest, TranslatesEqualityPredicatesWithId) {
const auto cst = CNode{CNode::ObjectChildren{
{UserFieldname{"_id"}, CNode{UserNull{}}},
}};
auto match = translate(cst);
auto andExpr = dynamic_cast<AndMatchExpression*>(match.get());
ASSERT(andExpr);
ASSERT_EQ(1, andExpr->numChildren());
ASSERT_BSONOBJ_EQ(match->serialize(), fromjson("{$and: [{_id: {$eq: null}}]}"));
}
TEST(CstMatchTranslationTest, TranslatesEmptyObject) {
const auto cst = CNode{CNode::ObjectChildren{}};
auto match = translate(cst);
auto andExpr = dynamic_cast<AndMatchExpression*>(match.get());
ASSERT(andExpr);
ASSERT_EQ(0, andExpr->numChildren());
}
TEST(CstMatchTranslationTest, TranslatesNotWithRegex) {
auto input = fromjson("{filter: {a: {$not: /b/}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
auto andExpr = dynamic_cast<AndMatchExpression*>(match.get());
ASSERT(andExpr);
ASSERT_EQ(1, andExpr->numChildren());
auto notExpr = dynamic_cast<NotMatchExpression*>(andExpr->getChild(0));
ASSERT(notExpr);
auto regex = dynamic_cast<RegexMatchExpression*>(notExpr->getChild(0));
ASSERT(regex);
ASSERT_EQ("a", regex->path());
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { a: { $not: { $regex: \"b\" } } } ] }");
}
TEST(CstMatchTranslationTest, TranslatesNotWithExpression) {
auto input = fromjson("{filter: {a: {$not: {$not: /b/}}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { $nor: [ { a: { $not: { $regex: \"b\" } } } ] } ] }");
}
TEST(CstMatchTranslationTest, TranslatesLogicalTreeExpressions) {
{
auto input = fromjson("{filter: {$and: [{b: {$not: /a/}}]}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { $and: [ { $and: [ { b: { $not: { $regex: \"a\" } } } ] } ] } ] }");
}
{
auto input = fromjson("{filter: {$or: [{b: 1}, {a: 2}]}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { $or: [ { $and: [ { b: { $eq: 1 } } ] }, { $and: [ { a: { $eq: 2 } } "
"] } ] } ] }");
}
{
auto input = fromjson("{filter: {$nor: [{b: {$not: /a/}}]}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { $nor: [ { $and: [ { b: { $not: { $regex: \"a\" } } } ] } ] } ] }");
}
}
TEST(CstMatchTranslationTest, TranslatesNestedLogicalTreeExpressions) {
{
auto input = fromjson("{filter: {$and: [{$or: [{b: {$not: /a/}}]}]}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { $and: [ { $and: [ { $or: [ { $and: [ { b: { $not: { $regex: \"a\" } "
"} } ] } ] } ] } ] } ] }");
}
{
auto input = fromjson("{filter: {$or: [{$and: [{b: {$not: /a/}}, {a: {$not: /b/}}]}]}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { $or: [ { $and: [ { $and: [ { $and: [ { b: { $not: { $regex: \"a\" } "
"} } ] }, { $and: [ { a: { $not: { $regex: \"b\" } } } ] } ] } ] } ] } ] }");
}
{
auto input = fromjson("{filter: {$and: [{$nor: [{b: {$not: /a/}}]}]}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { $and: [ { $and: [ { $nor: [ { $and: [ { b: { $not: { $regex: \"a\" "
"} } } ] } ] } ] } ] } ] }");
}
}
TEST(CstMatchTranslationTest, TranslatesExistsBool) {
{
auto input = fromjson("{filter: {a: {$exists: true}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { a: { $exists: true } } ] }");
}
{
auto input = fromjson("{filter: {a: {$exists: false}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { a: { $not: { $exists: true } } } ] }");
}
}
TEST(CstMatchTranslationTest, TranslatesExistsNumeric) {
{
auto input = fromjson("{filter: {a: {$exists: 15.0}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { a: { $exists: true } } ] }");
}
{
auto input = fromjson("{filter: {a: {$exists: 0}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { a: { $not: { $exists: true } } } ] }");
}
}
TEST(CstMatchTranslationTest, TranslatesExistsNullAndCompound) {
{
auto input = fromjson("{filter: {a: {$exists: null}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { a: { $not: { $exists: true } } } ] }");
}
{
auto input = fromjson("{filter: {a: {$exists: [\"arbitrary stuff\", null]}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { a: { $exists: true } } ] }");
}
{
auto input = fromjson("{filter: {a: {$exists: {doesnt: \"matter\"}}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { a: { $exists: true } } ] }");
}
}
TEST(CstMatchTranslationTest, TranslatesType) {
{
auto input = fromjson("{filter: {a: {$type: 1}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { a: { $type: [ 1 ] } } ] }");
}
{
auto input = fromjson("{filter: {a: {$type: \"number\"}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { a: { $type: [ \"number\" ] } } ] }");
// The compound "number" alias is not translated; instead the allNumbers flag of the typeset
// used by the MatchExpression is set.
auto andExpr = dynamic_cast<AndMatchExpression*>(match.get());
ASSERT(andExpr);
ASSERT_EQ(1, andExpr->numChildren());
auto type_match = dynamic_cast<TypeMatchExpression*>(andExpr->getChild(0));
ASSERT(type_match);
ASSERT(type_match->typeSet().allNumbers);
}
{
auto input = fromjson("{filter: {a: {$type: [ \"number\", \"string\", 11]}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ { a: { $type: [ \"number\", 2, 11 ] } } ] }");
// Direct type aliases (like "string" --> BSONType 2) are translated into their numeric
// type.
auto andExpr = dynamic_cast<AndMatchExpression*>(match.get());
ASSERT(andExpr);
ASSERT_EQ(1, andExpr->numChildren());
auto type_match = dynamic_cast<TypeMatchExpression*>(andExpr->getChild(0));
ASSERT(type_match->typeSet().allNumbers);
}
}
TEST(CstMatchTranslationTest, TranslatesComment) {
{
auto input = fromjson("{filter: {a: 1, $comment: \"hello, world\"}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { a: { $eq: 1 } } ] }");
}
{
auto input = fromjson("{filter: {$comment: \"hello, world\"}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
auto andExpr = dynamic_cast<AndMatchExpression*>(match.get());
ASSERT(andExpr);
ASSERT_EQ(0, andExpr->numChildren());
}
{
auto input = fromjson("{filter: {a: {$exists: true}, $comment: \"hello, world\"}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { a: { $exists: true } } ] }");
}
}
TEST(CstMatchTranslationTest, TranslatesExpr) {
auto input = fromjson("{filter: {$expr: 123}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { $expr: { $const: 123 } } ] }");
}
TEST(CstMatchTranslationTest, TranslatesText) {
auto input = fromjson("{filter: {$text: {$search: \"hi\"}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ "
"{ $text: { $search: \"hi\", $language: \"\", "
"$caseSensitive: false, $diacriticSensitive: false } } ] }");
}
TEST(CstMatchTranslationTest, TranslatesWhere) {
auto input = fromjson("{filter: {$where: \"return this.q\"}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(),
"{ $and: [ "
"{ $where: return this.q } ] }");
}
TEST(CstMatchTranslationTest, TranslatesMod) {
auto input = fromjson("{filter: {a: {$mod: [3, 2.0]}}}");
auto cst = parseMatchToCst(input);
auto match = translate(cst);
ASSERT_EQ(match->serialize().toString(), "{ $and: [ { a: { $mod: [ 3, 2 ] } } ] }");
}
} // namespace
} // namespace mongo

View File

@ -1,70 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include "mongo/platform/basic.h"
#include "mongo/db/cst/bson_lexer.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/cst_match_translation.h"
#include "mongo/db/cst/cst_sort_translation.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/extensions_callback.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/query/sort_pattern.h"
namespace mongo::cst {
/**
* Parses the given 'filter' to a MatchExpression. Throws an exception if the filter fails to parse.
*/
std::unique_ptr<MatchExpression> parseToMatchExpression(
BSONObj filter,
const boost::intrusive_ptr<ExpressionContext>& expCtx,
const ExtensionsCallback& extensionsCallback) {
BSONLexer lexer{filter, ParserGen::token::START_MATCH};
CNode cst;
ParserGen(lexer, &cst).parse();
return cst_match_translation::translateMatchExpression(cst, expCtx, extensionsCallback);
}
/**
* Parses the given 'sort' object into a SortPattern. Throws an exception if the sort object fails
* to parse.
*/
SortPattern parseToSortPattern(BSONObj sort,
const boost::intrusive_ptr<ExpressionContext>& expCtx) {
BSONLexer lexer{sort, ParserGen::token::START_SORT};
CNode cst;
ParserGen(lexer, &cst).parse();
return cst_sort_translation::translateSortSpec(cst, expCtx);
}
} // namespace mongo::cst

View File

@ -1,964 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <boost/move/utility_core.hpp>
#include <boost/none.hpp>
#include <boost/optional/optional.hpp>
#include <boost/smart_ptr/intrusive_ptr.hpp>
// IWYU pragma: no_include "ext/alloc_traits.h"
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <list>
#include <string>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonmisc.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/compound_key.h"
#include "mongo/db/cst/cst_match_translation.h"
#include "mongo/db/cst/cst_pipeline_translation.h"
#include "mongo/db/cst/key_fieldname.h"
#include "mongo/db/cst/key_value.h"
#include "mongo/db/cst/path.h"
#include "mongo/db/exec/document_value/document.h"
#include "mongo/db/exec/document_value/document_metadata_fields.h"
#include "mongo/db/exec/document_value/value.h"
#include "mongo/db/exec/exclusion_projection_executor.h"
#include "mongo/db/exec/inclusion_projection_executor.h"
#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/pipeline/document_source.h"
#include "mongo/db/pipeline/document_source_limit.h"
#include "mongo/db/pipeline/document_source_match.h"
#include "mongo/db/pipeline/document_source_sample.h"
#include "mongo/db/pipeline/document_source_single_document_transformation.h"
#include "mongo/db/pipeline/document_source_skip.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/expression_trigonometric.h"
#include "mongo/db/pipeline/field_path.h"
#include "mongo/db/pipeline/variable_validation.h"
#include "mongo/db/pipeline/variables.h"
#include "mongo/db/query/projection_policies.h"
#include "mongo/db/query/util/make_data_structure.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/intrusive_counter.h"
#include "mongo/util/overloaded_visitor.h" // IWYU pragma: keep
namespace mongo::cst_pipeline_translation {
namespace {
Value translateLiteralToValue(const CNode& cst);
/**
* Walk a literal array payload and produce a Value. This function is necessary because Aggregation
* Expression literals are required to be collapsed into Values inside ExpressionConst but
* uncollapsed otherwise.
*/
auto translateLiteralArrayToValue(const CNode::ArrayChildren& array) {
auto values = std::vector<Value>{};
static_cast<void>(
std::transform(array.begin(), array.end(), std::back_inserter(values), [&](auto&& elem) {
return translateLiteralToValue(elem);
}));
return Value{std::move(values)};
}
/**
* Walk a literal object payload and produce a Value. This function is neccesary because Aggregation
* Expression literals are required to be collapsed into Values inside ExpressionConst but
* uncollapsed otherwise.
*/
auto translateLiteralObjectToValue(const CNode::ObjectChildren& object) {
auto fields = std::vector<std::pair<StringData, Value>>{};
static_cast<void>(
std::transform(object.begin(), object.end(), std::back_inserter(fields), [&](auto&& field) {
return std::pair{StringData{get<UserFieldname>(field.first)},
translateLiteralToValue(field.second)};
}));
return Value{Document{std::move(fields)}};
}
/**
* Walk a purely literal CNode and produce a Value. This function is neccesary because Aggregation
* Expression literals are required to be collapsed into Values inside ExpressionConst but
* uncollapsed otherwise.
*/
Value translateLiteralToValue(const CNode& cst) {
return visit(OverloadedVisitor{[](const CNode::ArrayChildren& array) {
return translateLiteralArrayToValue(array);
},
[](const CNode::ObjectChildren& object) {
return translateLiteralObjectToValue(object);
},
[&](auto&& payload) {
return translateLiteralLeaf(cst);
}},
cst.payload);
}
/**
* Walk a literal array payload and produce an ExpressionArray.
*
* Caller must ensure the ExpressionContext outlives the result.
*/
auto translateLiteralArray(const CNode::ArrayChildren& array,
ExpressionContext* expCtx,
const VariablesParseState& vps) {
auto expressions = std::vector<boost::intrusive_ptr<Expression>>{};
static_cast<void>(std::transform(
array.begin(), array.end(), std::back_inserter(expressions), [&](auto&& elem) {
return translateExpression(elem, expCtx, vps);
}));
return ExpressionArray::create(expCtx, std::move(expressions));
}
/**
* Walk a literal object payload and produce an ExpressionObject.
*
* Caller must ensure the ExpressionContext outlives the result.
*/
auto translateLiteralObject(const CNode::ObjectChildren& object,
ExpressionContext* expCtx,
const VariablesParseState& vps) {
auto fields = std::vector<std::pair<std::string, boost::intrusive_ptr<Expression>>>{};
static_cast<void>(
std::transform(object.begin(), object.end(), std::back_inserter(fields), [&](auto&& field) {
return std::pair{std::string{get<UserFieldname>(field.first)},
translateExpression(field.second, expCtx, vps)};
}));
return ExpressionObject::create(expCtx, std::move(fields));
}
/**
* Walk an agg function/operator object payload and produce an ExpressionVector.
*
* Caller must ensure the ExpressionContext outlives the result.
*/
auto transformInputExpression(const CNode::ObjectChildren& object,
ExpressionContext* expCtx,
const VariablesParseState& vps) {
auto expressions = std::vector<boost::intrusive_ptr<Expression>>{};
visit(
OverloadedVisitor{
[&](const CNode::ArrayChildren& array) {
static_cast<void>(std::transform(
array.begin(), array.end(), std::back_inserter(expressions), [&](auto&& elem) {
return translateExpression(elem, expCtx, vps);
}));
},
[&](const CNode::ObjectChildren& object) {
static_cast<void>(std::transform(
object.begin(),
object.end(),
std::back_inserter(expressions),
[&](auto&& elem) { return translateExpression(elem.second, expCtx, vps); }));
},
// Everything else is a literal.
[&](auto&&) {
expressions.push_back(translateExpression(object[0].second, expCtx, vps));
}},
object[0].second.payload);
return expressions;
}
/**
* Check that the order of arguments is what we expect in an input expression.
*/
bool verifyFieldnames(const std::vector<CNode::Fieldname>& expected,
const std::vector<std::pair<CNode::Fieldname, CNode>>& actual) {
if (expected.size() != actual.size())
return false;
for (size_t i = 0; i < expected.size(); ++i) {
if (expected[i] != actual[i].first)
return false;
}
return true;
}
/**
* Walk an agg function/operator object payload and produce an ExpressionMeta.
*
* Caller must ensure the ExpressionContext outlives the result.
*/
auto translateMeta(const CNode::ObjectChildren& object, ExpressionContext* expCtx) {
switch (get<KeyValue>(object[0].second.payload)) {
case KeyValue::geoNearDistance:
return make_intrusive<ExpressionMeta>(expCtx, DocumentMetadataFields::kGeoNearDist);
case KeyValue::geoNearPoint:
return make_intrusive<ExpressionMeta>(expCtx, DocumentMetadataFields::kGeoNearPoint);
case KeyValue::indexKey:
return make_intrusive<ExpressionMeta>(expCtx, DocumentMetadataFields::kIndexKey);
case KeyValue::randVal:
return make_intrusive<ExpressionMeta>(expCtx, DocumentMetadataFields::kRandVal);
case KeyValue::recordId:
return make_intrusive<ExpressionMeta>(expCtx, DocumentMetadataFields::kRecordId);
case KeyValue::searchHighlights:
return make_intrusive<ExpressionMeta>(expCtx,
DocumentMetadataFields::kSearchHighlights);
case KeyValue::searchScore:
return make_intrusive<ExpressionMeta>(expCtx, DocumentMetadataFields::kSearchScore);
case KeyValue::sortKey:
return make_intrusive<ExpressionMeta>(expCtx, DocumentMetadataFields::kSortKey);
case KeyValue::textScore:
return make_intrusive<ExpressionMeta>(expCtx, DocumentMetadataFields::kTextScore);
case KeyValue::timeseriesBucketMaxTime:
return make_intrusive<ExpressionMeta>(expCtx,
DocumentMetadataFields::kTimeseriesBucketMaxTime);
case KeyValue::timeseriesBucketMinTime:
return make_intrusive<ExpressionMeta>(expCtx,
DocumentMetadataFields::kTimeseriesBucketMinTime);
default:
MONGO_UNREACHABLE;
}
}
auto translateFilter(const CNode::ObjectChildren& object,
ExpressionContext* expCtx,
const VariablesParseState& vps) {
using namespace std::string_literals;
// $filter's syntax guarantees that it's payload is ObjectChildren
auto&& children = get<CNode::ObjectChildren>(object[0].second.payload);
auto&& inputElem = children[0].second;
auto&& asElem = children[1].second;
auto&& condElem = children[2].second;
// The cond expression has a different VPS, where the variable the user gives in the "as"
// argument is defined.
auto vpsSub = VariablesParseState{vps};
auto varName = [&]() -> std::string {
if (auto x = get_if<UserString>(&asElem.payload)) {
return *x;
}
return "this"s;
}();
variableValidation::validateNameForUserWrite(varName);
auto varId = vpsSub.defineVariable(varName);
return make_intrusive<ExpressionFilter>(expCtx,
std::move(varName),
varId,
translateExpression(inputElem, expCtx, vps),
translateExpression(condElem, expCtx, vpsSub));
}
/**
* Walk an agg function/operator object payload and produce an Expression.
*
* Caller must ensure the ExpressionContext outlives the result.
*/
boost::intrusive_ptr<Expression> translateFunctionObject(const CNode::ObjectChildren& object,
ExpressionContext* expCtx,
const VariablesParseState& vps) {
// Constants require using Value instead of Expression to build the tree in agg.
if (get<KeyFieldname>(object[0].first) == KeyFieldname::constExpr ||
get<KeyFieldname>(object[0].first) == KeyFieldname::literal)
return make_intrusive<ExpressionConstant>(expCtx,
translateLiteralToValue(object[0].second));
// Meta is an exception since it has no Expression children but rather an enum member.
if (get<KeyFieldname>(object[0].first) == KeyFieldname::meta)
return translateMeta(object, expCtx);
// Filter is an exception because its Expression children need to be given particular variable
// states before they are translated.
if (get<KeyFieldname>(object[0].first) == KeyFieldname::filter)
return translateFilter(object, expCtx, vps);
auto expressions = transformInputExpression(object, expCtx, vps);
switch (get<KeyFieldname>(object[0].first)) {
case KeyFieldname::add:
return make_intrusive<ExpressionAdd>(expCtx, std::move(expressions));
case KeyFieldname::atan2:
return make_intrusive<ExpressionArcTangent2>(expCtx, std::move(expressions));
case KeyFieldname::andExpr:
return make_intrusive<ExpressionAnd>(expCtx, std::move(expressions));
case KeyFieldname::orExpr:
return make_intrusive<ExpressionOr>(expCtx, std::move(expressions));
case KeyFieldname::notExpr:
return make_intrusive<ExpressionNot>(expCtx, std::move(expressions));
case KeyFieldname::cmp:
return make_intrusive<ExpressionCompare>(
expCtx, ExpressionCompare::CMP, std::move(expressions));
case KeyFieldname::eq:
return make_intrusive<ExpressionCompare>(
expCtx, ExpressionCompare::EQ, std::move(expressions));
case KeyFieldname::gt:
return make_intrusive<ExpressionCompare>(
expCtx, ExpressionCompare::GT, std::move(expressions));
case KeyFieldname::gte:
return make_intrusive<ExpressionCompare>(
expCtx, ExpressionCompare::GTE, std::move(expressions));
case KeyFieldname::lt:
return make_intrusive<ExpressionCompare>(
expCtx, ExpressionCompare::LT, std::move(expressions));
case KeyFieldname::lte:
return make_intrusive<ExpressionCompare>(
expCtx, ExpressionCompare::LTE, std::move(expressions));
case KeyFieldname::ne:
return make_intrusive<ExpressionCompare>(
expCtx, ExpressionCompare::NE, std::move(expressions));
case KeyFieldname::convert:
dassert(verifyFieldnames({KeyFieldname::inputArg,
KeyFieldname::toArg,
KeyFieldname::formatArg,
KeyFieldname::onErrorArg,
KeyFieldname::onNullArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionConvert>(
expCtx,
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]),
std::move(expressions[3]),
std::move(expressions[4]),
ExpressionConvert::checkBinDataConvertAllowed());
case KeyFieldname::toBool:
return ExpressionConvert::create(expCtx, std::move(expressions[0]), BSONType::Bool);
case KeyFieldname::toDate:
return ExpressionConvert::create(expCtx, std::move(expressions[0]), BSONType::Date);
case KeyFieldname::toDecimal:
return ExpressionConvert::create(
expCtx, std::move(expressions[0]), BSONType::NumberDecimal);
case KeyFieldname::toDouble:
return ExpressionConvert::create(
expCtx, std::move(expressions[0]), BSONType::NumberDouble);
case KeyFieldname::toInt:
return ExpressionConvert::create(
expCtx, std::move(expressions[0]), BSONType::NumberInt);
case KeyFieldname::toLong:
return ExpressionConvert::create(
expCtx, std::move(expressions[0]), BSONType::NumberLong);
case KeyFieldname::toObjectId:
return ExpressionConvert::create(expCtx, std::move(expressions[0]), BSONType::jstOID);
case KeyFieldname::toString:
return ExpressionConvert::create(expCtx, std::move(expressions[0]), BSONType::String);
case KeyFieldname::concat:
return make_intrusive<ExpressionConcat>(expCtx, std::move(expressions));
case KeyFieldname::dateFromString:
dassert(verifyFieldnames({KeyFieldname::dateStringArg,
KeyFieldname::formatArg,
KeyFieldname::timezoneArg,
KeyFieldname::onErrorArg,
KeyFieldname::onNullArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionDateFromString>(expCtx,
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]),
std::move(expressions[3]),
std::move(expressions[4]));
case KeyFieldname::dateToString:
dassert(verifyFieldnames({KeyFieldname::dateArg,
KeyFieldname::formatArg,
KeyFieldname::timezoneArg,
KeyFieldname::onNullArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionDateToString>(expCtx,
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]),
std::move(expressions[3]));
case KeyFieldname::indexOfBytes:
return make_intrusive<ExpressionIndexOfBytes>(expCtx, std::move(expressions));
case KeyFieldname::indexOfCP:
return make_intrusive<ExpressionIndexOfCP>(expCtx, std::move(expressions));
case KeyFieldname::replaceOne:
dassert(verifyFieldnames(
{KeyFieldname::inputArg, KeyFieldname::findArg, KeyFieldname::replacementArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionReplaceOne>(expCtx,
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]));
case KeyFieldname::replaceAll:
dassert(verifyFieldnames(
{KeyFieldname::inputArg, KeyFieldname::findArg, KeyFieldname::replacementArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionReplaceAll>(expCtx,
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]));
case KeyFieldname::regexFind:
dassert(verifyFieldnames(
{KeyFieldname::inputArg, KeyFieldname::regexArg, KeyFieldname::optionsArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionRegexFind>(expCtx,
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]),
"$regexFind");
case KeyFieldname::regexFindAll:
dassert(verifyFieldnames(
{KeyFieldname::inputArg, KeyFieldname::regexArg, KeyFieldname::optionsArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionRegexFindAll>(expCtx,
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]),
"$regexFindAll");
case KeyFieldname::regexMatch:
dassert(verifyFieldnames(
{KeyFieldname::inputArg, KeyFieldname::regexArg, KeyFieldname::optionsArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionRegexMatch>(expCtx,
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]),
"$regexMatch");
case KeyFieldname::ltrim:
dassert(verifyFieldnames({KeyFieldname::inputArg, KeyFieldname::charsArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionTrim>(expCtx,
ExpressionTrim::TrimType::kLeft,
"$ltrim",
std::move(expressions[0]),
std::move(expressions[1]));
case KeyFieldname::rtrim:
dassert(verifyFieldnames({KeyFieldname::inputArg, KeyFieldname::charsArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionTrim>(expCtx,
ExpressionTrim::TrimType::kRight,
"$rtrim",
std::move(expressions[0]),
std::move(expressions[1]));
case KeyFieldname::trim:
dassert(verifyFieldnames({KeyFieldname::inputArg, KeyFieldname::charsArg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionTrim>(expCtx,
ExpressionTrim::TrimType::kBoth,
"$trim",
std::move(expressions[0]),
std::move(expressions[1]));
case KeyFieldname::slice:
return make_intrusive<ExpressionSlice>(expCtx, std::move(expressions));
case KeyFieldname::split:
return make_intrusive<ExpressionSplit>(expCtx, std::move(expressions));
case KeyFieldname::strcasecmp:
return make_intrusive<ExpressionStrcasecmp>(expCtx, std::move(expressions));
case KeyFieldname::strLenCP:
return make_intrusive<ExpressionStrLenCP>(expCtx, std::move(expressions));
case KeyFieldname::strLenBytes:
return make_intrusive<ExpressionStrLenBytes>(expCtx, std::move(expressions));
case KeyFieldname::substr:
case KeyFieldname::substrBytes:
return make_intrusive<ExpressionSubstrBytes>(expCtx, std::move(expressions));
case KeyFieldname::substrCP:
return make_intrusive<ExpressionSubstrCP>(expCtx, std::move(expressions));
case KeyFieldname::toLower:
return make_intrusive<ExpressionToLower>(expCtx, std::move(expressions));
case KeyFieldname::toUpper:
return make_intrusive<ExpressionToUpper>(expCtx, std::move(expressions));
case KeyFieldname::type:
return make_intrusive<ExpressionType>(expCtx, std::move(expressions));
case KeyFieldname::abs:
return make_intrusive<ExpressionAbs>(expCtx, std::move(expressions));
case KeyFieldname::ceil:
return make_intrusive<ExpressionCeil>(expCtx, std::move(expressions));
case KeyFieldname::divide:
return make_intrusive<ExpressionDivide>(expCtx, std::move(expressions));
case KeyFieldname::exponent:
return make_intrusive<ExpressionExp>(expCtx, std::move(expressions));
case KeyFieldname::floor:
return make_intrusive<ExpressionFloor>(expCtx, std::move(expressions));
case KeyFieldname::ln:
return make_intrusive<ExpressionLn>(expCtx, std::move(expressions));
case KeyFieldname::log:
return make_intrusive<ExpressionLog>(expCtx, std::move(expressions));
case KeyFieldname::logten:
return make_intrusive<ExpressionLog10>(expCtx, std::move(expressions));
case KeyFieldname::mod:
return make_intrusive<ExpressionMod>(expCtx, std::move(expressions));
case KeyFieldname::multiply:
return make_intrusive<ExpressionMultiply>(expCtx, std::move(expressions));
case KeyFieldname::pow:
return make_intrusive<ExpressionPow>(expCtx, std::move(expressions));
case KeyFieldname::round:
return make_intrusive<ExpressionRound>(expCtx, std::move(expressions));
case KeyFieldname::sqrt:
return make_intrusive<ExpressionSqrt>(expCtx, std::move(expressions));
case KeyFieldname::subtract:
return make_intrusive<ExpressionSubtract>(expCtx, std::move(expressions));
case KeyFieldname::trunc:
return make_intrusive<ExpressionTrunc>(expCtx, std::move(expressions));
case KeyFieldname::allElementsTrue:
return make_intrusive<ExpressionAllElementsTrue>(expCtx, std::move(expressions));
case KeyFieldname::anyElementTrue:
return make_intrusive<ExpressionAnyElementTrue>(expCtx, std::move(expressions));
case KeyFieldname::setDifference:
return make_intrusive<ExpressionSetDifference>(expCtx, std::move(expressions));
case KeyFieldname::setEquals:
return make_intrusive<ExpressionSetEquals>(expCtx, std::move(expressions));
case KeyFieldname::setIntersection:
return make_intrusive<ExpressionSetIntersection>(expCtx, std::move(expressions));
case KeyFieldname::setIsSubset:
return make_intrusive<ExpressionSetIsSubset>(expCtx, std::move(expressions));
case KeyFieldname::setUnion:
return make_intrusive<ExpressionSetUnion>(expCtx, std::move(expressions));
case KeyFieldname::sin:
return make_intrusive<ExpressionSine>(expCtx, std::move(expressions));
case KeyFieldname::cos:
return make_intrusive<ExpressionCosine>(expCtx, std::move(expressions));
case KeyFieldname::tan:
return make_intrusive<ExpressionTangent>(expCtx, std::move(expressions));
case KeyFieldname::sinh:
return make_intrusive<ExpressionHyperbolicSine>(expCtx, std::move(expressions));
case KeyFieldname::cosh:
return make_intrusive<ExpressionHyperbolicCosine>(expCtx, std::move(expressions));
case KeyFieldname::tanh:
return make_intrusive<ExpressionHyperbolicTangent>(expCtx, std::move(expressions));
case KeyFieldname::asin:
return make_intrusive<ExpressionArcSine>(expCtx, std::move(expressions));
case KeyFieldname::acos:
return make_intrusive<ExpressionArcCosine>(expCtx, std::move(expressions));
case KeyFieldname::atan:
return make_intrusive<ExpressionArcTangent>(expCtx, std::move(expressions));
case KeyFieldname::asinh:
return make_intrusive<ExpressionHyperbolicArcSine>(expCtx, std::move(expressions));
case KeyFieldname::acosh:
return make_intrusive<ExpressionHyperbolicArcCosine>(expCtx, std::move(expressions));
case KeyFieldname::atanh:
return make_intrusive<ExpressionHyperbolicArcTangent>(expCtx, std::move(expressions));
case KeyFieldname::degreesToRadians:
return make_intrusive<ExpressionDegreesToRadians>(expCtx, std::move(expressions));
case KeyFieldname::radiansToDegrees:
return make_intrusive<ExpressionRadiansToDegrees>(expCtx, std::move(expressions));
case KeyFieldname::dateToParts:
dassert(verifyFieldnames(
{KeyFieldname::dateArg, KeyFieldname::timezoneArg, KeyFieldname::iso8601Arg},
object[0].second.objectChildren()));
return make_intrusive<ExpressionDateToParts>(expCtx,
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]));
case KeyFieldname::dateFromParts:
if (get<KeyFieldname>(object[0].second.objectChildren().front().first) ==
KeyFieldname::yearArg) {
return make_intrusive<ExpressionDateFromParts>(expCtx,
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]),
std::move(expressions[3]),
std::move(expressions[4]),
std::move(expressions[5]),
std::move(expressions[6]),
nullptr,
nullptr,
nullptr,
std::move(expressions[7]));
} else {
return make_intrusive<ExpressionDateFromParts>(expCtx,
nullptr,
nullptr,
nullptr,
std::move(expressions[3]),
std::move(expressions[4]),
std::move(expressions[5]),
std::move(expressions[6]),
std::move(expressions[0]),
std::move(expressions[1]),
std::move(expressions[2]),
std::move(expressions[7]));
}
case KeyFieldname::dayOfMonth:
return make_intrusive<ExpressionDayOfMonth>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::dayOfWeek:
return make_intrusive<ExpressionDayOfWeek>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::dayOfYear:
return make_intrusive<ExpressionDayOfYear>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::hour:
return make_intrusive<ExpressionHour>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::isoDayOfWeek:
return make_intrusive<ExpressionIsoDayOfWeek>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::isoWeek:
return make_intrusive<ExpressionIsoWeek>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::isoWeekYear:
return make_intrusive<ExpressionIsoWeekYear>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::minute:
return make_intrusive<ExpressionMinute>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::millisecond:
return make_intrusive<ExpressionMillisecond>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::month:
return make_intrusive<ExpressionMonth>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::second:
return make_intrusive<ExpressionSecond>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::week:
return make_intrusive<ExpressionWeek>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::year:
return make_intrusive<ExpressionYear>(
expCtx,
std::move(expressions[0]),
(expressions.size() == 2) ? std::move(expressions[1]) : nullptr);
case KeyFieldname::arrayElemAt:
return make_intrusive<ExpressionArrayElemAt>(expCtx, std::move(expressions));
case KeyFieldname::arrayToObject:
return make_intrusive<ExpressionArrayToObject>(expCtx, std::move(expressions));
case KeyFieldname::concatArrays:
return make_intrusive<ExpressionConcatArrays>(expCtx, std::move(expressions));
case KeyFieldname::in:
return make_intrusive<ExpressionIn>(expCtx, std::move(expressions));
case KeyFieldname::indexOfArray:
return make_intrusive<ExpressionIndexOfArray>(expCtx, std::move(expressions));
case KeyFieldname::isArray:
return make_intrusive<ExpressionIsArray>(expCtx, std::move(expressions));
case KeyFieldname::first:
return make_intrusive<ExpressionFirst>(expCtx, std::move(expressions));
case KeyFieldname::tsSecond:
return make_intrusive<ExpressionTsSecond>(expCtx, std::move(expressions));
case KeyFieldname::tsIncrement:
return make_intrusive<ExpressionTsIncrement>(expCtx, std::move(expressions));
default:
MONGO_UNREACHABLE;
}
}
/**
* Walk a compound projection CNode payload (CompoundInclusionKey or CompoundExclusionKey) and
* produce a sequence of paths and optional expressions.
*
* Caller must ensure the ExpressionContext outlives the result.
*/
template <typename CompoundPayload>
auto translateCompoundProjection(const CompoundPayload& payload,
const std::vector<StringData>& path,
ExpressionContext* expCtx) {
auto resultPaths =
std::vector<std::pair<FieldPath, boost::optional<boost::intrusive_ptr<Expression>>>>{};
auto translateProjectionObject =
[&](auto&& recurse, auto&& children, auto&& previousPath) -> void {
for (auto&& child : children) {
auto&& components = get<ProjectionPath>(get<FieldnamePath>(child.first)).components;
auto currentPath = previousPath;
for (auto&& component : components)
currentPath.emplace_back(component);
// In this context we have a project path object to recurse over.
if (auto recursiveChildren = get_if<CNode::ObjectChildren>(&child.second.payload);
recursiveChildren &&
holds_alternative<FieldnamePath>((*recursiveChildren)[0].first))
recurse(recurse, *recursiveChildren, std::as_const(currentPath));
// Alternatively we have a key indicating inclusion/exclusion.
else if (child.second.projectionType())
resultPaths.emplace_back(path::vectorToString(currentPath), boost::none);
// Everything else is an agg expression to translate.
else
resultPaths.emplace_back(
path::vectorToString(currentPath),
translateExpression(child.second, expCtx, expCtx->variablesParseState));
}
};
translateProjectionObject(translateProjectionObject, payload.obj->objectChildren(), path);
return resultPaths;
}
/**
* Walk an inclusion project stage object CNode and produce a
* DocumentSourceSingleDocumentTransformation.
*/
auto translateProjectInclusion(const CNode& cst,
const boost::intrusive_ptr<ExpressionContext>& expCtx) {
// 'true' indicates that the fast path is enabled, it's harmless to leave it on for all cases.
auto executor = std::make_unique<projection_executor::InclusionProjectionExecutor>(
expCtx, ProjectionPolicies::aggregateProjectionPolicies(), true);
bool sawId = false;
for (auto&& [name, child] : cst.objectChildren()) {
sawId = sawId || CNode::fieldnameIsId(name);
// If we see a key fieldname, make sure it's _id.
const auto path = CNode::fieldnameIsId(name)
? makeVector<StringData>("_id"_sd)
: std::vector<StringData>{
get<ProjectionPath>(get<FieldnamePath>(name)).components.begin(),
get<ProjectionPath>(get<FieldnamePath>(name)).components.end()};
if (auto type = child.projectionType())
switch (*type) {
case ProjectionType::inclusion:
if (auto payload = get_if<CompoundInclusionKey>(&child.payload))
for (auto&& [compoundPath, expr] :
translateCompoundProjection(*payload, path, expCtx.get()))
if (expr)
executor->getRoot()->addExpressionForPath(std::move(compoundPath),
std::move(*expr));
else
executor->getRoot()->addProjectionForPath(std::move(compoundPath));
else
executor->getRoot()->addProjectionForPath(
FieldPath{path::vectorToString(path)});
break;
case ProjectionType::exclusion:
// InclusionProjectionExecutors must contain no exclusion besides _id so we do
// nothing here and translate the presence of an _id exclusion node by the
// absence of the implicit _id inclusion below.
invariant(CNode::fieldnameIsId(name));
break;
default:
MONGO_UNREACHABLE;
}
else
// This is a computed projection.
executor->getRoot()->addExpressionForPath(
FieldPath{path::vectorToString(path)},
translateExpression(child, expCtx.get(), expCtx->variablesParseState));
}
// If we didn't see _id we need to add it in manually for inclusion.
if (!sawId)
executor->getRoot()->addProjectionForPath(FieldPath{"_id"});
return make_intrusive<DocumentSourceSingleDocumentTransformation>(
expCtx, std::move(executor), "$project", false);
}
/**
* Walk an exclusion project stage object CNode and produce a
* DocumentSourceSingleDocumentTransformation.
*/
auto translateProjectExclusion(const CNode& cst,
const boost::intrusive_ptr<ExpressionContext>& expCtx) {
// 'true' indicates that the fast path is enabled, it's harmless to leave it on for all cases.
auto executor = std::make_unique<projection_executor::ExclusionProjectionExecutor>(
expCtx, ProjectionPolicies::aggregateProjectionPolicies(), true);
for (auto&& [name, child] : cst.objectChildren()) {
// If we see a key fieldname, make sure it's _id.
const auto path = CNode::fieldnameIsId(name)
? makeVector<StringData>("_id"_sd)
: std::vector<StringData>{
get<ProjectionPath>(get<FieldnamePath>(name)).components.begin(),
get<ProjectionPath>(get<FieldnamePath>(name)).components.end()};
if (auto type = child.projectionType())
switch (*type) {
case ProjectionType::inclusion:
// ExclusionProjectionExecutors must contain no inclusion besides _id so we do
// nothing here since including _id is the default.
break;
case ProjectionType::exclusion:
if (auto payload = get_if<CompoundExclusionKey>(&child.payload))
for (auto&& [compoundPath, unused] :
translateCompoundProjection(*payload, path, expCtx.get()))
executor->getRoot()->addProjectionForPath(std::move(compoundPath));
else
executor->getRoot()->addProjectionForPath(
FieldPath{path::vectorToString(path)});
break;
default:
MONGO_UNREACHABLE;
}
else
// This is a computed projection.
// Computed fields are disallowed in exclusion projection.
MONGO_UNREACHABLE;
}
return make_intrusive<DocumentSourceSingleDocumentTransformation>(
expCtx, std::move(executor), "$project", false);
}
/**
* Walk a skip stage object CNode and produce a DocumentSourceSkip.
*/
auto translateSkip(const CNode& cst, const boost::intrusive_ptr<ExpressionContext>& expCtx) {
UserLong nToSkip = cst.numberLong();
return DocumentSourceSkip::create(expCtx, nToSkip);
}
/**
* Unwrap a limit stage CNode and produce a DocumentSourceLimit.
*/
auto translateLimit(const CNode& cst, const boost::intrusive_ptr<ExpressionContext>& expCtx) {
UserLong limit = cst.numberLong();
return DocumentSourceLimit::create(expCtx, limit);
}
/**
* Unwrap a sample stage CNode and produce a DocumentSourceSample.
*/
auto translateSample(const CNode& cst, const boost::intrusive_ptr<ExpressionContext>& expCtx) {
return DocumentSourceSample::create(expCtx, cst.objectChildren()[0].second.numberLong());
}
/**
* Unwrap a match stage CNode and produce a DocumentSourceMatch.
*/
auto translateMatch(const CNode& cst, const boost::intrusive_ptr<ExpressionContext>& expCtx) {
auto matchExpr =
cst_match_translation::translateMatchExpression(cst, expCtx, ExtensionsCallbackNoop{});
return make_intrusive<DocumentSourceMatch>(std::move(matchExpr), expCtx);
}
/**
* Walk an aggregation pipeline stage object CNode and produce a DocumentSource.
*/
boost::intrusive_ptr<DocumentSource> translateSource(
const CNode& cst, const boost::intrusive_ptr<ExpressionContext>& expCtx) {
switch (cst.firstKeyFieldname()) {
case KeyFieldname::projectInclusion:
return translateProjectInclusion(cst.objectChildren()[0].second, expCtx);
case KeyFieldname::projectExclusion:
return translateProjectExclusion(cst.objectChildren()[0].second, expCtx);
case KeyFieldname::match:
return translateMatch(cst.objectChildren()[0].second, expCtx);
case KeyFieldname::skip:
return translateSkip(cst.objectChildren()[0].second, expCtx);
case KeyFieldname::limit:
return translateLimit(cst.objectChildren()[0].second, expCtx);
case KeyFieldname::sample:
return translateSample(cst.objectChildren()[0].second, expCtx);
default:
MONGO_UNREACHABLE;
}
}
} // namespace
/**
* Walk an expression CNode and produce an agg Expression.
*
* Caller must ensure the ExpressionContext outlives the result.
*/
boost::intrusive_ptr<Expression> translateExpression(const CNode& cst,
ExpressionContext* expCtx,
const VariablesParseState& vps) {
return visit(
OverloadedVisitor{
// When we're not inside an agg operator/function, this is a non-leaf literal.
[&](const CNode::ArrayChildren& array) -> boost::intrusive_ptr<Expression> {
return translateLiteralArray(array, expCtx, vps);
},
// This is either a literal object or an agg operator/function.
[&](const CNode::ObjectChildren& object) -> boost::intrusive_ptr<Expression> {
if (!object.empty() && holds_alternative<KeyFieldname>(object[0].first))
return translateFunctionObject(object, expCtx, vps);
else
return translateLiteralObject(object, expCtx, vps);
},
// If a key occurs outside a particular agg operator/function, it was misplaced.
[](const KeyValue& keyValue) -> boost::intrusive_ptr<Expression> {
switch (keyValue) {
// An absentKey denotes a missing optional argument to an Expression.
case KeyValue::absentKey:
return nullptr;
default:
MONGO_UNREACHABLE;
}
},
[](const NonZeroKey&) -> boost::intrusive_ptr<Expression> { MONGO_UNREACHABLE; },
[&](const ValuePath& vp) -> boost::intrusive_ptr<Expression> {
return visit(
OverloadedVisitor{[&](const AggregationPath& ap) {
return ExpressionFieldPath::createPathFromString(
expCtx, path::vectorToString(ap.components), vps);
},
[&](const AggregationVariablePath& avp) {
return ExpressionFieldPath::createVarFromString(
expCtx, path::vectorToString(avp.components), vps);
}},
vp);
},
// Everything else is a literal leaf.
[&](auto&&) -> boost::intrusive_ptr<Expression> {
return ExpressionConstant::create(expCtx, translateLiteralLeaf(cst));
}},
cst.payload);
}
/**
* Walk a pipeline array CNode and produce a Pipeline.
*/
std::unique_ptr<Pipeline, PipelineDeleter> translatePipeline(
const CNode& cst, const boost::intrusive_ptr<ExpressionContext>& expCtx) {
auto sources = Pipeline::SourceContainer{};
static_cast<void>(std::transform(cst.arrayChildren().begin(),
cst.arrayChildren().end(),
std::back_inserter(sources),
[&](auto&& elem) { return translateSource(elem, expCtx); }));
return Pipeline::create(std::move(sources), expCtx);
}
/**
* Walk a literal leaf CNode and produce an agg Value.
*/
Value translateLiteralLeaf(const CNode& cst) {
return visit(
OverloadedVisitor{// These are illegal since they're non-leaf.
[](const CNode::ArrayChildren&) -> Value { MONGO_UNREACHABLE; },
[](const CNode::ObjectChildren&) -> Value { MONGO_UNREACHABLE; },
[](const CompoundInclusionKey&) -> Value { MONGO_UNREACHABLE; },
[](const CompoundExclusionKey&) -> Value { MONGO_UNREACHABLE; },
[](const CompoundInconsistentKey&) -> Value { MONGO_UNREACHABLE; },
// These are illegal since they're non-literal.
[](const KeyValue&) -> Value { MONGO_UNREACHABLE; },
[](const NonZeroKey&) -> Value { MONGO_UNREACHABLE; },
[](const ValuePath&) -> Value { MONGO_UNREACHABLE; },
// These payloads require a special translation to DocumentValue parlance.
[](const UserUndefined&) { return Value{BSONUndefined}; },
[](const UserNull&) { return Value{BSONNULL}; },
[](const UserMinKey&) { return Value{MINKEY}; },
[](const UserMaxKey&) { return Value{MAXKEY}; },
// The rest convert directly.
[](auto&& payload) {
return Value{payload};
}},
cst.payload);
}
} // namespace mongo::cst_pipeline_translation

View File

@ -1,64 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <memory>
#include "mongo/db/exec/document_value/value.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/pipeline.h"
#include "mongo/db/pipeline/variables.h"
#include "mongo/platform/basic.h"
namespace mongo::cst_pipeline_translation {
/**
* Walk an expression CNode and produce an agg Expression.
*
* Caller must ensure the ExpressionContext outlives the Expression.
*/
boost::intrusive_ptr<Expression> translateExpression(const CNode& cst,
ExpressionContext* expCtx,
const VariablesParseState& vps);
/**
* Walk a pipeline array CNode and produce a Pipeline.
*/
std::unique_ptr<Pipeline, PipelineDeleter> translatePipeline(
const CNode& cst, const boost::intrusive_ptr<ExpressionContext>& expCtx);
/**
* Walk a literal leaf CNode and produce a Value.
*/
Value translateLiteralLeaf(const CNode& cst);
} // namespace mongo::cst_pipeline_translation

File diff suppressed because it is too large Load Diff

View File

@ -1,146 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <string>
#include <variant>
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include "mongo/base/string_data.h"
#include "mongo/bson/json.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/cst_pipeline_translation.h"
#include "mongo/db/cst/key_fieldname.h"
#include "mongo/db/cst/path.h"
#include "mongo/db/exec/document_value/value.h"
#include "mongo/db/exec/document_value/value_comparator.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/query/util/make_data_structure.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/framework.h"
namespace mongo {
namespace {
using namespace std::string_literals;
auto getExpCtx() {
auto nss = NamespaceString::createNamespaceString_forTest("db", "coll");
return ExpressionContextForTest(nss);
}
TEST(CstPipelineTranslationTest, AllElementsTrueTest) {
const auto cst = CNode{CNode::ObjectChildren{
{KeyFieldname::allElementsTrue,
CNode{CNode::ArrayChildren{CNode{AggregationPath{makeVector<std::string>("set"s)}}}}}}};
auto expCtx = getExpCtx();
auto expr =
cst_pipeline_translation::translateExpression(cst, &expCtx, expCtx.variablesParseState);
ASSERT_TRUE(ValueComparator().evaluate(Value(fromjson("{$allElementsTrue: [\"$set\"]}")) ==
expr->serialize()));
}
TEST(CstPipelineTranslationTest, AnyElementsTrueTest) {
const auto cst = CNode{CNode::ObjectChildren{
{KeyFieldname::anyElementTrue,
CNode{CNode::ArrayChildren{CNode{AggregationPath{makeVector<std::string>("set"s)}}}}}}};
auto expCtx = getExpCtx();
auto expr =
cst_pipeline_translation::translateExpression(cst, &expCtx, expCtx.variablesParseState);
ASSERT_TRUE(ValueComparator().evaluate(Value(fromjson("{$anyElementTrue: [\"$set\"]}")) ==
expr->serialize()));
}
TEST(CstPipelineTranslationTest, SetDifferenceTest) {
const auto cst = CNode{CNode::ObjectChildren{
{KeyFieldname::setDifference,
CNode{CNode::ArrayChildren{CNode{AggregationPath{makeVector<std::string>("set"s)}},
CNode{AggregationPath{makeVector<std::string>("set2"s)}}}}}}};
auto expCtx = getExpCtx();
auto expr =
cst_pipeline_translation::translateExpression(cst, &expCtx, expCtx.variablesParseState);
ASSERT_TRUE(ValueComparator().evaluate(
Value(fromjson("{$setDifference: [\"$set\", \"$set2\"]}")) == expr->serialize()));
}
TEST(CstPipelineTranslationTest, SetEqualsTest) {
const auto cst = CNode{CNode::ObjectChildren{
{KeyFieldname::setEquals,
CNode{CNode::ArrayChildren{CNode{AggregationPath{makeVector<std::string>("set"s)}},
CNode{AggregationPath{makeVector<std::string>("set2"s)}}}}}}};
auto expCtx = getExpCtx();
auto expr =
cst_pipeline_translation::translateExpression(cst, &expCtx, expCtx.variablesParseState);
ASSERT_TRUE(ValueComparator().evaluate(Value(fromjson("{$setEquals: [\"$set\", \"$set2\"]}")) ==
expr->serialize()));
}
TEST(CstPipelineTranslationTest, SetIntersectionTest) {
const auto cst = CNode{CNode::ObjectChildren{
{KeyFieldname::setIntersection,
CNode{CNode::ArrayChildren{CNode{AggregationPath{makeVector<std::string>("set"s)}},
CNode{AggregationPath{makeVector<std::string>("set2"s)}},
CNode{AggregationPath{makeVector<std::string>("set3"s)}}}}}}};
auto expCtx = getExpCtx();
auto expr =
cst_pipeline_translation::translateExpression(cst, &expCtx, expCtx.variablesParseState);
ASSERT_TRUE(ValueComparator().evaluate(
Value(fromjson("{$setIntersection: [\"$set\", \"$set2\", \"$set3\"]}")) ==
expr->serialize()));
}
TEST(CstPipelineTranslationTest, SetIsSubsetTest) {
const auto cst = CNode{CNode::ObjectChildren{
{KeyFieldname::setIsSubset,
CNode{CNode::ArrayChildren{CNode{AggregationPath{makeVector<std::string>("set"s)}},
CNode{AggregationPath{makeVector<std::string>("set2"s)}}}}}}};
auto expCtx = getExpCtx();
auto expr =
cst_pipeline_translation::translateExpression(cst, &expCtx, expCtx.variablesParseState);
ASSERT_TRUE(ValueComparator().evaluate(
Value(fromjson("{$setIsSubset: [\"$set\", \"$set2\"]}")) == expr->serialize()));
}
TEST(CstPipelineTranslationTest, SetUnionTest) {
const auto cst = CNode{CNode::ObjectChildren{
{KeyFieldname::setUnion,
CNode{CNode::ArrayChildren{CNode{AggregationPath{makeVector<std::string>("set"s)}},
CNode{AggregationPath{makeVector<std::string>("set2"s)}},
CNode{AggregationPath{makeVector<std::string>("set3"s)}}}}}}};
auto expCtx = getExpCtx();
auto expr =
cst_pipeline_translation::translateExpression(cst, &expCtx, expCtx.variablesParseState);
ASSERT_TRUE(ValueComparator().evaluate(
Value(fromjson("{$setUnion: [\"$set\", \"$set2\", \"$set3\"]}")) == expr->serialize()));
}
} // namespace
} // namespace mongo

View File

@ -1,114 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <boost/move/utility_core.hpp>
#include <boost/none.hpp>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include "mongo/db/cst/cst_sort_translation.h"
#include "mongo/db/cst/key_value.h"
#include "mongo/db/cst/path.h"
#include "mongo/db/exec/document_value/document_metadata_fields.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/field_path.h"
#include "mongo/util/assert_util.h"
#include "mongo/util/intrusive_counter.h"
#include "mongo/util/overloaded_visitor.h" // IWYU pragma: keep
namespace mongo::cst_sort_translation {
SortPattern translateSortSpec(const CNode& cst,
const boost::intrusive_ptr<ExpressionContext>& expCtx) {
// Assume object children, only thing possible for sort.
const auto& children = cst.objectChildren();
std::vector<SortPattern::SortPatternPart> sortKeys;
for (const auto& keyValPair : children) {
auto&& path = path::vectorToString(
std::move(get<SortPath>(get<FieldnamePath>(keyValPair.first)).components));
visit(OverloadedVisitor{
[&](const CNode::ObjectChildren& object) {
// $meta is always the only key in the object, and always has a KeyValue
// as its value. If sorting by textScore, put highest scores first. If
// $meta was specified with randVal order doesn't matter, so always put
// descending.
auto keyVal = get<KeyValue>(object[0].second.payload);
switch (keyVal) {
case KeyValue::randVal:
sortKeys.push_back(SortPattern::SortPatternPart{
false,
boost::none,
make_intrusive<ExpressionMeta>(
expCtx.get(), DocumentMetadataFields::kRandVal)});
break;
case KeyValue::textScore:
sortKeys.push_back(SortPattern::SortPatternPart{
false,
boost::none,
make_intrusive<ExpressionMeta>(
expCtx.get(), DocumentMetadataFields::kTextScore)});
break;
default:
MONGO_UNREACHABLE;
}
},
[&](const KeyValue& keyValue) {
switch (keyValue) {
case KeyValue::intOneKey:
case KeyValue::longOneKey:
case KeyValue::doubleOneKey:
case KeyValue::decimalOneKey:
sortKeys.push_back(SortPattern::SortPatternPart{
true, FieldPath{std::move(path)}, nullptr /* meta */});
break;
case KeyValue::intNegOneKey:
case KeyValue::longNegOneKey:
case KeyValue::doubleNegOneKey:
case KeyValue::decimalNegOneKey:
sortKeys.push_back(SortPattern::SortPatternPart{
false, FieldPath{std::move(path)}, nullptr /* meta */});
break;
default:
MONGO_UNREACHABLE;
}
},
[](auto&&) { MONGO_UNREACHABLE; },
},
keyValPair.second.payload);
}
return SortPattern(std::move(sortKeys));
}
} // namespace mongo::cst_sort_translation

View File

@ -1,45 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <memory>
#include "mongo/db/cst/c_node.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/query/sort_pattern.h"
#include "mongo/platform/basic.h"
namespace mongo::cst_sort_translation {
SortPattern translateSortSpec(const CNode& cst,
const boost::intrusive_ptr<ExpressionContext>& expCtx);
} // namespace mongo::cst_sort_translation

View File

@ -1,168 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include <boost/optional/optional.hpp>
#include <boost/smart_ptr.hpp>
#include <cstddef>
#include <memory>
#include <string>
#include <variant>
#include <boost/smart_ptr/intrusive_ptr.hpp>
#include "mongo/base/string_data.h"
#include "mongo/bson/json.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/cst/cst_sort_translation.h"
#include "mongo/db/cst/key_fieldname.h"
#include "mongo/db/cst/key_value.h"
#include "mongo/db/cst/path.h"
#include "mongo/db/exec/document_value/value.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/expression_context_for_test.h"
#include "mongo/db/pipeline/field_path.h"
#include "mongo/db/query/sort_pattern.h"
#include "mongo/db/query/util/make_data_structure.h"
#include "mongo/unittest/assert.h"
#include "mongo/unittest/framework.h"
#include "mongo/util/intrusive_counter.h"
namespace mongo {
namespace {
auto getExpCtx() {
auto nss = NamespaceString::createNamespaceString_forTest("db", "coll");
return boost::intrusive_ptr<ExpressionContextForTest>{new ExpressionContextForTest(nss)};
}
void assertSortPatternsEQ(SortPattern correct, SortPattern fromTest) {
for (size_t i = 0; i < correct.size(); ++i) {
ASSERT_EQ(correct[i].isAscending, fromTest[i].isAscending);
if (correct[i].fieldPath) {
if (fromTest[i].fieldPath) {
ASSERT_EQ(correct[i].fieldPath->fullPath(), fromTest[i].fieldPath->fullPath());
} else {
FAIL("Pattern missing fieldpath");
}
} else if (fromTest[i].fieldPath) {
FAIL("Pattern incorrectly had fieldpath");
}
if (correct[i].expression) {
if (fromTest[i].expression)
ASSERT_EQ(correct[i].expression->serialize().toString(),
fromTest[i].expression->serialize().toString());
else {
FAIL("Pattern missing expression");
}
} else if (fromTest[i].expression) {
FAIL("Pattern incorrectly had expression");
}
}
}
TEST(CstSortTranslationTest, BasicSortGeneratesCorrectSortPattern) {
const auto cst = CNode{CNode::ObjectChildren{
{SortPath{makeVector<std::string>("val")}, CNode{KeyValue::intOneKey}}}};
auto expCtx = getExpCtx();
auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
auto correctPattern = SortPattern(fromjson("{val: 1}"), expCtx);
assertSortPatternsEQ(correctPattern, pattern);
}
TEST(CstSortTranslationTest, MultiplePartSortGeneratesCorrectSortPattern) {
{
const auto cst = CNode{CNode::ObjectChildren{
{SortPath{makeVector<std::string>("val")}, CNode{KeyValue::intOneKey}},
{SortPath{makeVector<std::string>("test")}, CNode{KeyValue::intNegOneKey}}}};
auto expCtx = getExpCtx();
auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
auto correctPattern = SortPattern(fromjson("{val: 1, test: -1}"), expCtx);
assertSortPatternsEQ(correctPattern, pattern);
}
{
const auto cst = CNode{CNode::ObjectChildren{
{SortPath{makeVector<std::string>("val")}, CNode{KeyValue::doubleOneKey}},
{SortPath{makeVector<std::string>("test")}, CNode{KeyValue::intNegOneKey}},
{SortPath{makeVector<std::string>("third")}, CNode{KeyValue::longNegOneKey}}}};
auto expCtx = getExpCtx();
auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
auto correctPattern = SortPattern(fromjson("{val: 1, test: -1, third: -1}"), expCtx);
assertSortPatternsEQ(correctPattern, pattern);
}
{
const auto cst = CNode{CNode::ObjectChildren{
{SortPath{makeVector<std::string>("val")}, CNode{KeyValue::intOneKey}},
{SortPath{makeVector<std::string>("test")},
CNode{
CNode::ObjectChildren{{KeyFieldname::meta, CNode{KeyValue::randVal}}},
}}}};
auto expCtx = getExpCtx();
auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
auto correctPattern = SortPattern(fromjson("{val: 1, test: {$meta: \"randVal\"}}"), expCtx);
assertSortPatternsEQ(correctPattern, pattern);
}
}
TEST(CstSortTranslationTest, SortWithMetaGeneratesCorrectSortPattern) {
{
const auto cst = CNode{CNode::ObjectChildren{
{SortPath{makeVector<std::string>("val")},
CNode{
CNode::ObjectChildren{{KeyFieldname::meta, CNode{KeyValue::randVal}}},
}}}};
auto expCtx = getExpCtx();
auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
auto correctPattern = SortPattern(fromjson("{val: {$meta: \"randVal\"}}"), expCtx);
assertSortPatternsEQ(correctPattern, pattern);
}
{
const auto cst = CNode{CNode::ObjectChildren{
{SortPath{makeVector<std::string>("val")},
CNode{
CNode::ObjectChildren{{KeyFieldname::meta, CNode{KeyValue::textScore}}},
}}}};
auto expCtx = getExpCtx();
auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
auto correctPattern = SortPattern(fromjson("{val: {$meta: \"textScore\"}}"), expCtx);
assertSortPatternsEQ(correctPattern, pattern);
}
}
TEST(CstSortTranslationTest, SortWithDottedPathTranslatesCorrectly) {
const auto cst =
CNode{CNode::ObjectChildren{{SortPath{{"a", "b", "c"}}, CNode{KeyValue::intOneKey}}}};
auto expCtx = getExpCtx();
auto pattern = cst_sort_translation::translateSortSpec(cst, expCtx);
auto correctPattern = SortPattern(fromjson("{'a.b.c': 1}"), expCtx);
assertSortPatternsEQ(correctPattern, pattern);
}
} // namespace
} // namespace mongo

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,201 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include "mongo/platform/basic.h"
#include "mongo/db/query/util/named_enum.h"
namespace mongo {
#define KEYFIELDNAMES(ENUMIFY) \
ENUMIFY(abs) \
ENUMIFY(acos) \
ENUMIFY(acosh) \
ENUMIFY(add) \
ENUMIFY(allElementsTrue) \
ENUMIFY(andExpr) \
ENUMIFY(anyElementTrue) \
ENUMIFY(asin) \
ENUMIFY(asinh) \
ENUMIFY(atan) \
ENUMIFY(arrayElemAt) \
ENUMIFY(arrayToObject) \
ENUMIFY(asArg) \
ENUMIFY(atan2) \
ENUMIFY(atanh) \
ENUMIFY(caseSensitive) \
ENUMIFY(ceil) \
ENUMIFY(charsArg) \
ENUMIFY(cmp) \
ENUMIFY(collArg) \
ENUMIFY(commentExpr) \
ENUMIFY(concat) \
ENUMIFY(concatArrays) \
ENUMIFY(condArg) \
ENUMIFY(constExpr) \
ENUMIFY(convert) \
ENUMIFY(cos) \
ENUMIFY(cosh) \
ENUMIFY(dateArg) \
ENUMIFY(dateFromParts) \
ENUMIFY(dateFromString) \
ENUMIFY(dateStringArg) \
ENUMIFY(dateToParts) \
ENUMIFY(dateToString) \
ENUMIFY(dayArg) \
ENUMIFY(dayOfMonth) \
ENUMIFY(dayOfWeek) \
ENUMIFY(dayOfYear) \
ENUMIFY(degreesToRadians) \
ENUMIFY(diacriticSensitive) \
ENUMIFY(divide) \
ENUMIFY(elemMatch) \
ENUMIFY(eq) \
ENUMIFY(existsExpr) \
ENUMIFY(exponent) \
ENUMIFY(expr) \
ENUMIFY(findArg) \
ENUMIFY(filter) \
ENUMIFY(first) \
ENUMIFY(floor) \
ENUMIFY(formatArg) \
ENUMIFY(gt) \
ENUMIFY(gte) \
ENUMIFY(hour) \
ENUMIFY(hourArg) \
ENUMIFY(id) \
ENUMIFY(in) \
ENUMIFY(indexOfArray) \
ENUMIFY(indexOfBytes) \
ENUMIFY(indexOfCP) \
ENUMIFY(inhibitOptimization) \
ENUMIFY(inputArg) \
ENUMIFY(isArray) \
ENUMIFY(iso8601Arg) \
ENUMIFY(isoDayOfWeek) \
ENUMIFY(isoDayOfWeekArg) \
ENUMIFY(isoWeek) \
ENUMIFY(isoWeekArg) \
ENUMIFY(isoWeekYear) \
ENUMIFY(isoWeekYearArg) \
ENUMIFY(language) \
ENUMIFY(limit) \
ENUMIFY(literal) \
ENUMIFY(ln) \
ENUMIFY(log) \
ENUMIFY(logten) \
ENUMIFY(lt) \
ENUMIFY(lte) \
ENUMIFY(ltrim) \
ENUMIFY(match) \
ENUMIFY(matchMod) \
ENUMIFY(meta) \
ENUMIFY(millisecond) \
ENUMIFY(millisecondArg) \
ENUMIFY(minute) \
ENUMIFY(minuteArg) \
ENUMIFY(mod) \
ENUMIFY(month) \
ENUMIFY(monthArg) \
ENUMIFY(multiply) \
ENUMIFY(ne) \
ENUMIFY(norExpr) \
ENUMIFY(notExpr) \
ENUMIFY(onErrorArg) \
ENUMIFY(onNullArg) \
ENUMIFY(optionsArg) \
ENUMIFY(orExpr) \
ENUMIFY(pipelineArg) \
ENUMIFY(pow) \
ENUMIFY(projectExclusion) \
ENUMIFY(projectInclusion) \
ENUMIFY(radiansToDegrees) \
ENUMIFY(regexArg) \
ENUMIFY(regexFind) \
ENUMIFY(regexFindAll) \
ENUMIFY(regexMatch) \
ENUMIFY(replaceAll) \
ENUMIFY(replacementArg) \
ENUMIFY(replaceOne) \
ENUMIFY(round) \
ENUMIFY(rtrim) \
ENUMIFY(sample) \
ENUMIFY(search) \
ENUMIFY(second) \
ENUMIFY(secondArg) \
ENUMIFY(setDifference) \
ENUMIFY(setEquals) \
ENUMIFY(setIntersection) \
ENUMIFY(setIsSubset) \
ENUMIFY(setUnion) \
ENUMIFY(sin) \
ENUMIFY(sinh) \
ENUMIFY(sizeArg) \
ENUMIFY(skip) \
ENUMIFY(slice) \
ENUMIFY(split) \
ENUMIFY(sqrt) \
ENUMIFY(strcasecmp) \
ENUMIFY(strLenBytes) \
ENUMIFY(strLenCP) \
ENUMIFY(substr) \
ENUMIFY(substrBytes) \
ENUMIFY(substrCP) \
ENUMIFY(subtract) \
ENUMIFY(tan) \
ENUMIFY(tanh) \
ENUMIFY(text) \
ENUMIFY(timezoneArg) \
ENUMIFY(toArg) \
ENUMIFY(toBool) \
ENUMIFY(toDate) \
ENUMIFY(toDecimal) \
ENUMIFY(toDouble) \
ENUMIFY(toInt) \
ENUMIFY(toLong) \
ENUMIFY(toLower) \
ENUMIFY(toObjectId) \
ENUMIFY(toString) \
ENUMIFY(toUpper) \
ENUMIFY(trim) \
ENUMIFY(trunc) \
ENUMIFY(type) \
ENUMIFY(unionWith) \
ENUMIFY(week) \
ENUMIFY(where) \
ENUMIFY(year) \
ENUMIFY(yearArg) \
ENUMIFY(tsSecond) \
ENUMIFY(tsIncrement)
QUERY_UTIL_NAMED_ENUM_DEFINE(KeyFieldname, KEYFIELDNAMES)
#undef KEYFIELDNAMES
} // namespace mongo

View File

@ -1,68 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include "mongo/platform/basic.h"
#include "mongo/db/query/util/named_enum.h"
namespace mongo {
#define KEYVALUES(ENUMIFY) \
ENUMIFY(absentKey) \
ENUMIFY(decimalNegOneKey) \
ENUMIFY(decimalOneKey) \
ENUMIFY(decimalZeroKey) \
ENUMIFY(doubleNegOneKey) \
ENUMIFY(doubleOneKey) \
ENUMIFY(doubleZeroKey) \
ENUMIFY(falseKey) \
ENUMIFY(geoNearDistance) \
ENUMIFY(geoNearPoint) \
ENUMIFY(indexKey) \
ENUMIFY(intNegOneKey) \
ENUMIFY(intOneKey) \
ENUMIFY(intZeroKey) \
ENUMIFY(longNegOneKey) \
ENUMIFY(longOneKey) \
ENUMIFY(longZeroKey) \
ENUMIFY(randVal) \
ENUMIFY(recordId) \
ENUMIFY(searchHighlights) \
ENUMIFY(searchScore) \
ENUMIFY(sortKey) \
ENUMIFY(textScore) \
ENUMIFY(timeseriesBucketMaxTime) \
ENUMIFY(timeseriesBucketMinTime) \
ENUMIFY(trueKey)
QUERY_UTIL_NAMED_ENUM_DEFINE(KeyValue, KEYVALUES)
#undef KEYVALUES
} // namespace mongo

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,145 +0,0 @@
/**
* Copyright (C) 2020-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include "mongo/platform/basic.h"
#include <numeric>
#include <string>
#include <variant>
#include <vector>
namespace mongo {
/**
* A path occurring as a fieldname in a $project stage or a find project which indicates source/
* destination object fields for inclusion/exclusion and destination fields for computed projection.
* Of the syntactic form: "a" or "a.b.c".
*/
struct ProjectionPath {
auto operator==(const ProjectionPath& other) const {
return components == other.components;
}
auto operator!=(const ProjectionPath& other) const {
return !(*this == other);
}
std::vector<std::string> components;
};
/**
* A path occurring as a fieldname in a find project indicating predicate application to array
* elements. Of the syntactic form: "a.$" or "a.b.c.$".
*/
struct PositionalProjectionPath {
auto operator==(const PositionalProjectionPath& other) const {
return components == other.components;
}
auto operator!=(const PositionalProjectionPath& other) const {
return !(*this == other);
}
std::vector<std::string> components;
};
/**
* A path occurring as a value in aggregation expressions acting as a field reference. Of the
* syntactic form: "$a" or "$a.b.c".
*/
struct AggregationPath {
auto operator==(const AggregationPath& other) const {
return components == other.components;
}
auto operator!=(const AggregationPath& other) const {
return !(*this == other);
}
std::vector<std::string> components;
};
/**
* A path occurring as a value in aggregation expressions acting as variable access. Of the
* syntactic form: "$$a" or "$$a.b.c".
*/
struct AggregationVariablePath {
auto operator==(const AggregationVariablePath& other) const {
return components == other.components;
}
auto operator!=(const AggregationVariablePath& other) const {
return !(*this == other);
}
std::vector<std::string> components;
};
/**
* A path occurring in a sort specification.
*/
struct SortPath {
auto operator==(const SortPath& other) const {
return components == other.components;
}
auto operator!=(const SortPath& other) const {
return !(*this == other);
}
std::vector<std::string> components;
};
namespace path {
template <typename StringType>
inline auto vectorToString(const std::vector<StringType>& vector) {
return std::accumulate(
std::next(vector.cbegin()),
vector.cend(),
std::string{vector[0]},
[](auto&& pathString, auto&& element) { return pathString + "." + element; });
}
template <typename PathType>
inline auto vectorToString(const PathType& path) {
return vectorToString(path.components);
}
} // namespace path
/**
* A path in the fieldname position in input BSON syntax. Such as "a.b" in '{"a.b": ""}'.
*/
using FieldnamePath = std::variant<ProjectionPath, PositionalProjectionPath, SortPath>;
/**
* A path in the value position in input BSON syntax. Such as "$a.b" in '{"": "$a.b"}'.
*/
using ValuePath = std::variant<AggregationPath, AggregationVariablePath>;
} // namespace mongo

View File

@ -822,7 +822,6 @@ env.CppUnitTest(
"$BUILD_DIR/mongo/db/change_stream_pre_image_util",
"$BUILD_DIR/mongo/db/change_stream_pre_images_collection_manager",
"$BUILD_DIR/mongo/db/change_streams_cluster_parameter",
"$BUILD_DIR/mongo/db/cst/cst",
"$BUILD_DIR/mongo/db/exec/document_value/document_value",
"$BUILD_DIR/mongo/db/exec/document_value/document_value_test_util",
"$BUILD_DIR/mongo/db/mongohasher",

View File

@ -57,7 +57,7 @@ env.Library(
],
LIBDEPS=[
"$BUILD_DIR/mongo/crypto/encrypted_field_config",
"$BUILD_DIR/mongo/db/cst/cst",
"$BUILD_DIR/mongo/db/pipeline/pipeline",
"$BUILD_DIR/mongo/db/query_expressions",
"collation/collator_factory_interface",
"collation/collator_interface",
@ -528,6 +528,7 @@ env.CppUnitTest(
"$BUILD_DIR/mongo/db/pipeline/abt_translation",
"$BUILD_DIR/mongo/db/pipeline/aggregation_request_helper",
"$BUILD_DIR/mongo/db/pipeline/document_source_mock",
"$BUILD_DIR/mongo/db/pipeline/pipeline",
"$BUILD_DIR/mongo/db/query/plan_cache/plan_cache_test_util",
"$BUILD_DIR/mongo/db/query_exec",
"$BUILD_DIR/mongo/db/record_id_helpers",

View File

@ -41,7 +41,6 @@
#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonobj.h"
#include "mongo/db/cst/c_node.h"
#include "mongo/db/exec/document_value/document_metadata_fields.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"