mirror of https://github.com/mongodb/mongo
SERVER-53888: Remove CST code (#25976)
GitOrigin-RevId: 65399cc37ddf15d2df63c568bd1d6f8b38a39bbb
This commit is contained in:
parent
fe18adb137
commit
3aeb26bdd0
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ env.SConscript(
|
|||
"catalog",
|
||||
"commands",
|
||||
"concurrency",
|
||||
"cst",
|
||||
"exec",
|
||||
"fts",
|
||||
"ftdc",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
load("//bazel:mongo_src_rules.bzl", "mongo_cc_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
exports_files(
|
||||
glob([
|
||||
"*.h",
|
||||
"*.cpp",
|
||||
]),
|
||||
)
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
version: 1.0.0
|
||||
filters:
|
||||
- "*":
|
||||
approvers:
|
||||
- 10gen/query-optimization
|
||||
|
|
@ -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',
|
||||
# ],
|
||||
# )
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue