diff --git a/.evergreen/auth_aws/README.md b/.evergreen/auth_aws/README.md deleted file mode 100644 index 028e3d0f2..000000000 --- a/.evergreen/auth_aws/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Configuration Scripts for End-to-end Testing - -These scripts were taken from [mongo-enterprise-modules](https://github.com/10gen/mongo-enterprise-modules/tree/master/jstests/external_auth_aws) -and intended to simplify creating users, attaching roles to existing EC2 instances, launching an Amazon ECS container instance, etc. \ No newline at end of file diff --git a/.evergreen/auth_aws/aws_e2e_assume_role.js b/.evergreen/auth_aws/aws_e2e_assume_role.js deleted file mode 100644 index ae5169667..000000000 --- a/.evergreen/auth_aws/aws_e2e_assume_role.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Verify the AWS IAM Auth works with temporary credentials from sts:AssumeRole - */ - -load("lib/aws_e2e_lib.js"); - -(function() { -"use strict"; - -const ASSUMED_ROLE = "arn:aws:sts::557821124784:assumed-role/authtest_user_assume_role/*"; - -function getAssumeCredentials() { - const config = readSetupJson(); - - const env = { - AWS_ACCESS_KEY_ID: config["iam_auth_assume_aws_account"], - AWS_SECRET_ACCESS_KEY: config["iam_auth_assume_aws_secret_access_key"], - }; - - const role_name = config["iam_auth_assume_role_name"]; - - const python_command = getPython3Binary() + - ` -u lib/aws_assume_role.py --role_name=${role_name} > creds.json`; - - const ret = runShellCmdWithEnv(python_command, env); - assert.eq(ret, 0, "Failed to assume role on the current machine"); - - const result = cat("creds.json"); - try { - return JSON.parse(result); - } catch (e) { - jsTestLog("Failed to parse: " + result); - throw e; - } -} - -const credentials = getAssumeCredentials(); -const admin = Mongo().getDB("admin"); -const external = admin.getMongo().getDB("$external"); - -assert(admin.auth("bob", "pwd123")); -assert.commandWorked(external.runCommand({createUser: ASSUMED_ROLE, roles:[{role: 'read', db: "aws"}]})); -assert(external.auth({ - user: credentials["AccessKeyId"], - pwd: credentials["SecretAccessKey"], - awsIamSessionToken: credentials["SessionToken"], - mechanism: 'MONGODB-AWS' -})); -}()); diff --git a/.evergreen/auth_aws/aws_e2e_ec2.js b/.evergreen/auth_aws/aws_e2e_ec2.js deleted file mode 100644 index a492db86d..000000000 --- a/.evergreen/auth_aws/aws_e2e_ec2.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Verify the AWS IAM EC2 hosted auth works - */ -load("lib/aws_e2e_lib.js"); - -(function() { -"use strict"; - -// This varies based on hosting EC2 as the account id and role name can vary -const AWS_ACCOUNT_ARN = "arn:aws:sts::557821124784:assumed-role/authtest_instance_profile_role/*"; - -function assignInstanceProfile() { - const config = readSetupJson(); - - const env = { - AWS_ACCESS_KEY_ID: config["iam_auth_ec2_instance_account"], - AWS_SECRET_ACCESS_KEY: config["iam_auth_ec2_instance_secret_access_key"], - }; - - const instanceProfileName = config["iam_auth_ec2_instance_profile"]; - const python_command = getPython3Binary() + - ` -u lib/aws_assign_instance_profile.py --instance_profile_arn=${instanceProfileName}`; - - const ret = runShellCmdWithEnv(python_command, env); - if (ret == 2) { - print("WARNING: Request limit exceeded for AWS API"); - return false; - } - - assert.eq(ret, 0, "Failed to assign an instance profile to the current machine"); - return true; -} - -if (!assignInstanceProfile()) { - return; -} - -const admin = Mongo().getDB("admin"); -const external = admin.getMongo().getDB("$external"); - -assert(admin.auth("bob", "pwd123")); -assert.commandWorked(external.runCommand({createUser: AWS_ACCOUNT_ARN, roles:[{role: 'read', db: "aws"}]})); - -// Try the command line -const smoke = runMongoProgram("mongo", - "--host", - "localhost", - '--authenticationMechanism', - 'MONGODB-AWS', - '--authenticationDatabase', - '$external', - "--eval", - "1"); -assert.eq(smoke, 0, "Could not auth with smoke user"); - -// Try the auth function -assert(external.auth({mechanism: 'MONGODB-AWS'})); -}()); diff --git a/.evergreen/auth_aws/aws_e2e_ecs.js b/.evergreen/auth_aws/aws_e2e_ecs.js deleted file mode 100644 index 8efd8cb43..000000000 --- a/.evergreen/auth_aws/aws_e2e_ecs.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Validate that MONGODB-AWS auth works from ECS temporary credentials. - */ -load("lib/aws_e2e_lib.js"); - -(function() { - 'use strict'; - - assert.eq(typeof mongo_binaries != 'undefined', true, "mongo_binaries must be set"); - assert.eq(typeof project_dir != 'undefined', true, "project_dir must be set"); - - const config = readSetupJson(); - - const base_command = getPython3Binary() + " -u lib/container_tester.py"; - const run_prune_command = base_command + ' -v remote_gc_services ' + - ' --cluster ' + config['iam_auth_ecs_cluster']; - - const run_test_command = base_command + ' -d -v run_e2e_test' + - ' --cluster ' + config['iam_auth_ecs_cluster'] + ' --task_definition ' + - config['iam_auth_ecs_task_definition'] + ' --subnets ' + - config['iam_auth_ecs_subnet_a'] + ' --subnets ' + - config['iam_auth_ecs_subnet_b'] + ' --security_group ' + - config['iam_auth_ecs_security_group'] + - ` --files ${mongo_binaries}/mongod:/root/mongod ${mongo_binaries}/mongo:/root/mongo ` + - " lib/ecs_hosted_test.js:/root/ecs_hosted_test.js " + - `${project_dir}:/root` + - " --script lib/ecs_hosted_test.sh"; - - // Pass in the AWS credentials as environment variables - // AWS_SHARED_CREDENTIALS_FILE does not work in evergreen for an unknown - // reason - const env = { - AWS_ACCESS_KEY_ID: config['iam_auth_ecs_account'], - AWS_SECRET_ACCESS_KEY: config['iam_auth_ecs_secret_access_key'], - }; - - // Prune other containers - let ret = runWithEnv(['/bin/sh', '-c', run_prune_command], env); - assert.eq(ret, 0, 'Prune Container failed'); - - // Run the test in a container - ret = runWithEnv(['/bin/sh', '-c', run_test_command], env); - assert.eq(ret, 0, 'Container Test failed'); -}()); diff --git a/.evergreen/auth_aws/aws_e2e_regular_aws.js b/.evergreen/auth_aws/aws_e2e_regular_aws.js deleted file mode 100644 index 1c4f2d032..000000000 --- a/.evergreen/auth_aws/aws_e2e_regular_aws.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Validate that the server supports real credentials from AWS and can talk to a real AWS STS - * service - */ -load("lib/aws_e2e_lib.js"); - -(function() { -"use strict"; - -const admin = Mongo().getDB("admin"); -const external = admin.getMongo().getDB("$external"); -assert(admin.auth("bob", "pwd123")); - -const config = readSetupJson(); -assert.commandWorked( - external.runCommand({createUser: config["iam_auth_ecs_account_arn"], roles:[{role: 'read', db: "aws"}]})); - -assert(external.auth({ - user: config["iam_auth_ecs_account"], - pwd: config["iam_auth_ecs_secret_access_key"], - mechanism: 'MONGODB-AWS' -})); -}()); \ No newline at end of file diff --git a/.evergreen/auth_aws/lib/aws_assign_instance_profile.py b/.evergreen/auth_aws/lib/aws_assign_instance_profile.py deleted file mode 100644 index cb3ad154d..000000000 --- a/.evergreen/auth_aws/lib/aws_assign_instance_profile.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -""" -Script for assign an instance policy to the current machine. -""" - -import argparse -import urllib.request -import logging -import sys -import time - -import boto3 -import botocore - -LOGGER = logging.getLogger(__name__) - -def _get_local_instance_id(): - return urllib.request.urlopen('http://169.254.169.254/latest/meta-data/instance-id').read().decode() - -def _has_instance_profile(): - base_url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/" - try: - print("Reading: " + base_url) - iam_role = urllib.request.urlopen(base_url).read().decode() - except urllib.error.HTTPError as e: - print(e) - if e.code == 404: - return False - raise e - - try: - url = base_url + iam_role - print("Reading: " + url) - req = urllib.request.urlopen(url) - except urllib.error.HTTPError as e: - print(e) - if e.code == 404: - return False - raise e - - return True - -def _wait_instance_profile(): - retry = 60 - while not _has_instance_profile() and retry: - time.sleep(5) - retry -= 1 - - if retry == 0: - raise ValueError("Timeout on waiting for instance profile") - -def _assign_instance_policy(iam_instance_arn): - - if _has_instance_profile(): - print("IMPORTANT: Found machine already has instance profile, skipping the assignment") - return - - instance_id = _get_local_instance_id() - - ec2_client = boto3.client("ec2", 'us-east-1') - - #https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.associate_iam_instance_profile - try: - response = ec2_client.associate_iam_instance_profile( - IamInstanceProfile={ - 'Arn' : iam_instance_arn, - }, - InstanceId = instance_id) - - print(response) - - # Wait for the instance profile to be assigned by polling the local instance metadata service - _wait_instance_profile() - - except botocore.exceptions.ClientError as ce: - if ce.response["Error"]["Code"] == "RequestLimitExceeded": - print("WARNING: RequestLimitExceeded, exiting with error code 2") - sys.exit(2) - raise - -def main() -> None: - """Execute Main entry point.""" - - parser = argparse.ArgumentParser(description='IAM Assign Instance frontend.') - - parser.add_argument('-v', "--verbose", action='store_true', help="Enable verbose logging") - parser.add_argument('-d', "--debug", action='store_true', help="Enable debug logging") - - parser.add_argument('--instance_profile_arn', type=str, help="Name of instance profile") - - args = parser.parse_args() - - if args.debug: - logging.basicConfig(level=logging.DEBUG) - elif args.verbose: - logging.basicConfig(level=logging.INFO) - - _assign_instance_policy(args.instance_profile_arn) - - -if __name__ == "__main__": - main() diff --git a/.evergreen/auth_aws/lib/aws_assume_role.py b/.evergreen/auth_aws/lib/aws_assume_role.py deleted file mode 100644 index 6df1fc7ef..000000000 --- a/.evergreen/auth_aws/lib/aws_assume_role.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -""" -Script for assuming an aws role. -""" - -import argparse -import uuid -import logging - -import boto3 - -LOGGER = logging.getLogger(__name__) - -STS_DEFAULT_ROLE_NAME = "arn:aws:iam::579766882180:role/mark.benvenuto" - -def _assume_role(role_name): - sts_client = boto3.client("sts") - - response = sts_client.assume_role(RoleArn=role_name, RoleSessionName=str(uuid.uuid4()), DurationSeconds=900) - - creds = response["Credentials"] - - - print(f"""{{ - "AccessKeyId" : "{creds["AccessKeyId"]}", - "SecretAccessKey" : "{creds["SecretAccessKey"]}", - "SessionToken" : "{creds["SessionToken"]}", - "Expiration" : "{str(creds["Expiration"])}" -}}""") - - -def main() -> None: - """Execute Main entry point.""" - - parser = argparse.ArgumentParser(description='Assume Role frontend.') - - parser.add_argument('-v', "--verbose", action='store_true', help="Enable verbose logging") - parser.add_argument('-d', "--debug", action='store_true', help="Enable debug logging") - - parser.add_argument('--role_name', type=str, default=STS_DEFAULT_ROLE_NAME, help="Role to assume") - - args = parser.parse_args() - - if args.debug: - logging.basicConfig(level=logging.DEBUG) - elif args.verbose: - logging.basicConfig(level=logging.INFO) - - _assume_role(args.role_name) - - -if __name__ == "__main__": - main() diff --git a/.evergreen/auth_aws/lib/aws_e2e_lib.js b/.evergreen/auth_aws/lib/aws_e2e_lib.js deleted file mode 100644 index d38471ac8..000000000 --- a/.evergreen/auth_aws/lib/aws_e2e_lib.js +++ /dev/null @@ -1,39 +0,0 @@ - -function readSetupJson() { - let result; - try { - result = cat("aws_e2e_setup.json"); - } catch (e) { - jsTestLog( - "Failed to parse read aws_e2e_setup.json. See evergreen.yml for how to generate this file which contains evergreen secrets."); - throw e; - } - - try { - return JSON.parse(result); - } catch (e) { - jsTestLog("Failed to parse: aws_e2e_setup.json"); - throw e; - } -} - -function runWithEnv(args, env) { - const pid = _startMongoProgram({args: args, env: env}); - return waitProgram(pid); -} - -function runShellCmdWithEnv(argStr, env) { - if (_isWindows()) { - return runWithEnv(['cmd.exe', '/c', argStr], env); - } else { - return runWithEnv(['/bin/sh', '-c', argStr], env); - } -} - -function getPython3Binary() { - if (_isWindows()) { - return "python.exe"; - } - - return "python3"; -} diff --git a/.evergreen/auth_aws/lib/container_tester.py b/.evergreen/auth_aws/lib/container_tester.py deleted file mode 100644 index eb3703c8f..000000000 --- a/.evergreen/auth_aws/lib/container_tester.py +++ /dev/null @@ -1,385 +0,0 @@ -#!/usr/bin/env python3 -""" -Script for testing mongodb in containers. - -Requires ssh, scp, and sh on local and remote hosts. -Assumes remote host is Linux -""" - -import argparse -import datetime -import logging -import os -import pprint -import subprocess -import uuid - -import boto3 - -LOGGER = logging.getLogger(__name__) - - -############################################################################ -# Default configuration settings for working with a ECS cluster in a region -# - -# These settings depend on a cluster, task subnets, and security group already setup -ECS_DEFAULT_CLUSTER = "arn:aws:ecs:us-east-2:579766882180:cluster/tf-mcb-ecs-cluster" -ECS_DEFAULT_TASK_DEFINITION = "arn:aws:ecs:us-east-2:579766882180:task-definition/tf-app:2" -ECS_DEFAULT_SUBNETS = ['subnet-a5e114cc'] -# Must allow ssh from 0.0.0.0 -ECS_DEFAULT_SECURITY_GROUP = 'sg-051a91d96332f8f3a' - -# This is just a string local to this file -DEFAULT_SERVICE_NAME = 'script-test' - -# Garbage collection threshold for old/stale services -DEFAULT_GARBAGE_COLLECTION_THRESHOLD = datetime.timedelta(hours=1) - -############################################################################ - - -def _run_process(params, cwd=None): - LOGGER.info("RUNNING COMMAND: %s", params) - ret = subprocess.run(params, cwd=cwd) - return ret.returncode - -def _userandhostandport(endpoint): - user_and_host = endpoint.find("@") - if user_and_host == -1: - raise ValueError("Invalid endpoint, Endpoint must be user@host:port") - (user, host) = (endpoint[:user_and_host], endpoint[user_and_host + 1:]) - - colon = host.find(":") - if colon == -1: - return (user, host, "22") - return (user, host[:colon], host[colon + 1:]) - -def _scp(endpoint, src, dest): - (user, host, port) = _userandhostandport(endpoint) - cmd = ["scp", "-o", "StrictHostKeyChecking=no", "-P", port, src, "%s@%s:%s" % (user, host, dest)] - if os.path.isdir(src): - cmd.insert(5, "-r") - _run_process(cmd) - -def _ssh(endpoint, cmd): - (user, host, port) = _userandhostandport(endpoint) - cmd = ["ssh", "-o", "StrictHostKeyChecking=no", "-p", port, "%s@%s" % (user, host), cmd ] - ret = _run_process(cmd) - LOGGER.info("RETURN CODE: %s", ret) - return ret - -def _run_test_args(args): - run_test(args.endpoint, args.script, args.files) - -def run_test(endpoint, script, files): - """ - Run a test on a machine - - Steps - 1. Copy over a files which are tuples of (src, dest) - 2. Copy over the test script to "/tmp/test.sh" - 3. Run the test script and return the results - """ - LOGGER.info("Copying files to %s", endpoint) - - for file in files: - colon = file.find(":") - (src, dest) = (file[:colon], file[colon + 1:]) - _scp(endpoint, src, dest) - - LOGGER.info("Copying script to %s", endpoint) - _scp(endpoint, script, "/tmp/test.sh") - return_code = _ssh(endpoint, "/bin/bash -x /tmp/test.sh") - if return_code != 0: - LOGGER.error("FAILED: %s", return_code) - raise ValueError(f"test failed with {return_code}") - -def _get_region(arn): - return arn.split(':')[3] - - -def _remote_ps_container_args(args): - remote_ps_container(args.cluster) - -def remote_ps_container(cluster): - """ - Get a list of task running in the cluster with their network addresses. - - Emulates the docker ps and ecs-cli ps commands. - """ - ecs_client = boto3.client('ecs', region_name=_get_region(cluster)) - ec2_client = boto3.client('ec2', region_name=_get_region(cluster)) - - tasks = ecs_client.list_tasks(cluster=cluster) - - task_list = ecs_client.describe_tasks(cluster=cluster, tasks=tasks['taskArns']) - - #Example from ecs-cli tool - #Name State Ports TaskDefinition Health - #aa2c2642-3013-4370-885e-8b8d956e753d/sshd RUNNING 3.15.149.114:22->22/tcp sshd:1 UNKNOWN - - print("Name State Public IP Private IP TaskDefinition Health") - for task in task_list['tasks']: - - taskDefinition = task['taskDefinitionArn'] - taskDefinition_short = taskDefinition[taskDefinition.rfind('/') + 1:] - - private_ip_address = None - enis = [] - for b in [ a['details'] for a in task["attachments"] if a['type'] == 'ElasticNetworkInterface']: - for c in b: - if c['name'] == 'networkInterfaceId': - enis.append(c['value']) - elif c['name'] == 'privateIPv4Address': - private_ip_address = c['value'] - assert enis - assert private_ip_address - - eni = ec2_client.describe_network_interfaces(NetworkInterfaceIds=enis) - public_ip = [n["Association"]["PublicIp"] for n in eni["NetworkInterfaces"]][0] - - for container in task['containers']: - taskArn = container['taskArn'] - task_id = taskArn[taskArn.rfind('/')+ 1:] - name = container['name'] - task_id = task_id + "/" + name - lastStatus = container['lastStatus'] - - print("{:<43}{:<9}{:<25}{:<25}{:<16}".format(task_id, lastStatus, public_ip, private_ip_address, taskDefinition_short )) - -def _remote_create_container_args(args): - remote_create_container(args.cluster, args.task_definition, args.service, args.subnets, args.security_group) - -def remote_create_container(cluster, task_definition, service_name, subnets, security_group): - """ - Create a task in ECS - """ - ecs_client = boto3.client('ecs', region_name=_get_region(cluster)) - - resp = ecs_client.create_service(cluster=cluster, serviceName=service_name, - taskDefinition = task_definition, - desiredCount = 1, - launchType='FARGATE', - networkConfiguration={ - 'awsvpcConfiguration': { - 'subnets': subnets, - 'securityGroups': [ - security_group, - ], - 'assignPublicIp': "ENABLED" - } - } - ) - - pprint.pprint(resp) - - service_arn = resp["service"]["serviceArn"] - print(f"Waiting for Service {service_arn} to become active...") - - waiter = ecs_client.get_waiter('services_stable') - - waiter.wait(cluster=cluster, services=[service_arn]) - -def _remote_stop_container_args(args): - remote_stop_container(args.cluster, args.service) - -def remote_stop_container(cluster, service_name): - """ - Stop a ECS task - """ - ecs_client = boto3.client('ecs', region_name=_get_region(cluster)) - - resp = ecs_client.delete_service(cluster=cluster, service=service_name, force=True) - pprint.pprint(resp) - - service_arn = resp["service"]["serviceArn"] - - print(f"Waiting for Service {service_arn} to become inactive...") - waiter = ecs_client.get_waiter('services_inactive') - - waiter.wait(cluster=cluster, services=[service_arn]) - -def _remote_gc_services_container_args(args): - remote_gc_services_container(args.cluster) - -def remote_gc_services_container(cluster): - """ - Delete all ECS services over then a given treshold. - """ - ecs_client = boto3.client('ecs', region_name=_get_region(cluster)) - - services = ecs_client.list_services(cluster=cluster) - if not services["serviceArns"]: - return - - services_details = ecs_client.describe_services(cluster=cluster, services=services["serviceArns"]) - - not_expired_now = datetime.datetime.now().astimezone() - DEFAULT_GARBAGE_COLLECTION_THRESHOLD - - for service in services_details["services"]: - created_at = service["createdAt"] - - # Find the services that we created "too" long ago - if created_at < not_expired_now: - print("DELETING expired service %s which was created at %s." % (service["serviceName"], created_at)) - - remote_stop_container(cluster, service["serviceName"]) - -def remote_get_public_endpoint_str(cluster, service_name): - """ - Get an SSH connection string for the remote service via the public ip address - """ - ecs_client = boto3.client('ecs', region_name=_get_region(cluster)) - ec2_client = boto3.client('ec2', region_name=_get_region(cluster)) - - tasks = ecs_client.list_tasks(cluster=cluster, serviceName=service_name) - - task_list = ecs_client.describe_tasks(cluster=cluster, tasks=tasks['taskArns']) - - for task in task_list['tasks']: - - enis = [] - for b in [ a['details'] for a in task["attachments"] if a['type'] == 'ElasticNetworkInterface']: - for c in b: - if c['name'] == 'networkInterfaceId': - enis.append(c['value']) - assert enis - - eni = ec2_client.describe_network_interfaces(NetworkInterfaceIds=enis) - public_ip = [n["Association"]["PublicIp"] for n in eni["NetworkInterfaces"]][0] - break - - return f"root@{public_ip}:22" - -def remote_get_endpoint_str(cluster, service_name): - """ - Get an SSH connection string for the remote service via the private ip address - """ - ecs_client = boto3.client('ecs', region_name=_get_region(cluster)) - - tasks = ecs_client.list_tasks(cluster=cluster, serviceName=service_name) - - task_list = ecs_client.describe_tasks(cluster=cluster, tasks=tasks['taskArns']) - - for task in task_list['tasks']: - - private_ip_address = None - for b in [ a['details'] for a in task["attachments"] if a['type'] == 'ElasticNetworkInterface']: - for c in b: - if c['name'] == 'privateIPv4Address': - private_ip_address = c['value'] - assert private_ip_address - break - - return f"root@{private_ip_address}:22" - -def _remote_get_endpoint_args(args): - _remote_get_endpoint(args.cluster, args.service) - -def _remote_get_endpoint(cluster, service_name): - endpoint = remote_get_endpoint_str(cluster, service_name) - print(endpoint) - -def _get_caller_identity(args): - sts_client = boto3.client('sts') - - pprint.pprint(sts_client.get_caller_identity()) - - -def _run_e2e_test_args(args): - _run_e2e_test(args.script, args.files, args.cluster, args.task_definition, args.subnets, args.security_group) - -def _run_e2e_test(script, files, cluster, task_definition, subnets, security_group): - """ - Run a test end-to-end - - 1. Start an ECS service - 2. Copy the files over and run the test - 3. Stop the ECS service - """ - service_name = str(uuid.uuid4()) - - remote_create_container(cluster, task_definition, service_name, subnets, security_group) - - # The build account hosted ECS tasks are only available via the private ip address - endpoint = remote_get_endpoint_str(cluster, service_name) - if cluster == ECS_DEFAULT_CLUSTER: - # The test account hosted ECS tasks are the opposite, only public ip address access - endpoint = remote_get_public_endpoint_str(cluster, service_name) - - try: - run_test(endpoint, script, files) - finally: - remote_stop_container(cluster, service_name) - - -def main() -> None: - """Execute Main entry point.""" - - parser = argparse.ArgumentParser(description='ECS container tester.') - - parser.add_argument('-v', "--verbose", action='store_true', help="Enable verbose logging") - parser.add_argument('-d', "--debug", action='store_true', help="Enable debug logging") - - sub = parser.add_subparsers(title="Container Tester subcommands", help="sub-command help") - - run_test_cmd = sub.add_parser('run_test', help='Run Test') - run_test_cmd.add_argument("--endpoint", required=True, type=str, help="User and Host and port, ie user@host:port") - run_test_cmd.add_argument("--script", required=True, type=str, help="script to run") - run_test_cmd.add_argument("--files", type=str, nargs="*", help="Files to copy, each string must be a pair of src:dest joined by a colon") - run_test_cmd.set_defaults(func=_run_test_args) - - remote_ps_cmd = sub.add_parser('remote_ps', help='Stop Local Container') - remote_ps_cmd.add_argument("--cluster", type=str, default=ECS_DEFAULT_CLUSTER, help="ECS Cluster to target") - remote_ps_cmd.set_defaults(func=_remote_ps_container_args) - - remote_create_cmd = sub.add_parser('remote_create', help='Create Remote Container') - remote_create_cmd.add_argument("--cluster", type=str, default=ECS_DEFAULT_CLUSTER, help="ECS Cluster to target") - remote_create_cmd.add_argument("--service", type=str, default=DEFAULT_SERVICE_NAME, help="ECS Service to create") - remote_create_cmd.add_argument("--task_definition", type=str, default=ECS_DEFAULT_TASK_DEFINITION, help="ECS Task Definition to use to create service") - remote_create_cmd.add_argument("--subnets", type=str, nargs="*", default=ECS_DEFAULT_SUBNETS, help="EC2 subnets to use") - remote_create_cmd.add_argument("--security_group", type=str, default=ECS_DEFAULT_SECURITY_GROUP, help="EC2 security group use") - remote_create_cmd.set_defaults(func=_remote_create_container_args) - - remote_stop_cmd = sub.add_parser('remote_stop', help='Stop Remote Container') - remote_stop_cmd.add_argument("--cluster", type=str, default=ECS_DEFAULT_CLUSTER, help="ECS Cluster to target") - remote_stop_cmd.add_argument("--service", type=str, default=DEFAULT_SERVICE_NAME, help="ECS Service to stop") - remote_stop_cmd.set_defaults(func=_remote_stop_container_args) - - remote_gc_services_cmd = sub.add_parser('remote_gc_services', help='GC Remote Container') - remote_gc_services_cmd.add_argument("--cluster", type=str, default=ECS_DEFAULT_CLUSTER, help="ECS Cluster to target") - remote_gc_services_cmd.set_defaults(func=_remote_gc_services_container_args) - - get_caller_identity_cmd = sub.add_parser('get_caller_identity', help='Get the AWS IAM caller identity') - get_caller_identity_cmd.set_defaults(func=_get_caller_identity) - - remote_get_endpoint_cmd = sub.add_parser('remote_get_endpoint', help='Get SSH remote endpoint') - remote_get_endpoint_cmd.add_argument("--cluster", type=str, default=ECS_DEFAULT_CLUSTER, help="ECS Cluster to target") - remote_get_endpoint_cmd.add_argument("--service", type=str, default=DEFAULT_SERVICE_NAME, help="ECS Service to stop") - remote_get_endpoint_cmd.set_defaults(func=_remote_get_endpoint_args) - - run_e2e_test_cmd = sub.add_parser('run_e2e_test', help='Run Test') - run_e2e_test_cmd.add_argument("--script", required=True, type=str, help="script to run") - run_e2e_test_cmd.add_argument("--files", type=str, nargs="*", help="Files to copy, each string must be a pair of src:dest joined by a colon") - run_e2e_test_cmd.add_argument("--cluster", type=str, default=ECS_DEFAULT_CLUSTER, help="ECS Cluster to target") - run_e2e_test_cmd.add_argument("--task_definition", type=str, default=ECS_DEFAULT_TASK_DEFINITION, help="ECS Task Definition to use to create service") - run_e2e_test_cmd.add_argument("--subnets", type=str, nargs="*", default=ECS_DEFAULT_SUBNETS, help="EC2 subnets to use") - run_e2e_test_cmd.add_argument("--security_group", type=str, default=ECS_DEFAULT_SECURITY_GROUP, help="EC2 security group use") - run_e2e_test_cmd.set_defaults(func=_run_e2e_test_args) - - args = parser.parse_args() - - print("AWS_SHARED_CREDENTIALS_FILE: %s" % (os.getenv("AWS_SHARED_CREDENTIALS_FILE"))) - - if args.debug: - logging.basicConfig(level=logging.DEBUG) - elif args.verbose: - logging.basicConfig(level=logging.INFO) - - - args.func(args) - - -if __name__ == "__main__": - main() diff --git a/.evergreen/auth_aws/lib/ecs_hosted_test.js b/.evergreen/auth_aws/lib/ecs_hosted_test.js deleted file mode 100644 index 17d4b3703..000000000 --- a/.evergreen/auth_aws/lib/ecs_hosted_test.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Verify the AWS IAM ECS hosted auth works - */ - -(function() { -"use strict"; - -// This varies based on hosting ECS task as the account id and role name can vary -const AWS_ACCOUNT_ARN = "arn:aws:sts::557821124784:assumed-role/ecsTaskExecutionRole/*"; - -const conn = MongoRunner.runMongod({ - setParameter: { - "authenticationMechanisms": "MONGODB-AWS,SCRAM-SHA-256", - }, - auth: "", -}); - -const external = conn.getDB("$external"); -const admin = conn.getDB("admin"); - -assert.commandWorked(admin.runCommand({createUser: "admin", pwd: "pwd", roles: ['root']})); -assert(admin.auth("admin", "pwd")); - -assert.commandWorked(external.runCommand({createUser: AWS_ACCOUNT_ARN, roles:[{role: 'read', db: "aws"}]})); - -const uri = "mongodb://127.0.0.1:20000/aws?authMechanism=MONGODB-AWS"; -const program = "/root/src/.evergreen/run-mongodb-aws-ecs-test.sh"; - -// Try the command line -const smoke = runMongoProgram(program, uri); -assert.eq(smoke, 0, "Could not auth with smoke user"); - -// Try the auth function -assert(external.auth({mechanism: 'MONGODB-AWS'})); - -MongoRunner.stopMongod(conn); -}()); diff --git a/.evergreen/auth_aws/lib/ecs_hosted_test.sh b/.evergreen/auth_aws/lib/ecs_hosted_test.sh deleted file mode 100644 index 7dddbc80b..000000000 --- a/.evergreen/auth_aws/lib/ecs_hosted_test.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# A shell script to run in an ECS hosted task - -# The environment variable is always set during interactive logins -# But for non-interactive logs, ~/.bashrc does not appear to be read on Ubuntu but it works on Fedora -[[ -z "${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}" ]] && export $(strings /proc/1/environ | grep AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) - -env - -mkdir -p /data/db || true - -/root/mongo --verbose --nodb ecs_hosted_test.js - -RET_CODE=$? -echo RETURN CODE: $RET_CODE -exit $RET_CODE diff --git a/.evergreen/config/functions.yml b/.evergreen/config/functions.yml index 3cb013b87..e91c21f87 100644 --- a/.evergreen/config/functions.yml +++ b/.evergreen/config/functions.yml @@ -126,6 +126,26 @@ functions: - command: attach.results params: file_location: "${DRIVERS_TOOLS}/results.json" + - command: shell.exec + params: + working_dir: src + script: | + ${PREPARE_SHELL} + if [ -f test-results.xml ]; then + curl -Os https://cli.codecov.io/latest/linux/codecov + curl -Os https://cli.codecov.io/latest/linux/codecov.SHA256SUM + shasum -a 256 -c codecov.SHA256SUM + sudo chmod +x codecov + ./codecov upload-process \ + --report-type test_results \ + --disable-search \ + --fail-on-error \ + --token ${CODECOV_TOKEN} \ + --flag "${MONGODB_VERSION}-${TOPOLOGY}" \ + --file test-results.xml + else + echo "Skipping codecov test result upload" + fi "bootstrap mongo-orchestration": - command: shell.exec @@ -143,6 +163,12 @@ functions: LOAD_BALANCER=${LOAD_BALANCER} \ REQUIRE_API_VERSION=${REQUIRE_API_VERSION} \ bash ${DRIVERS_TOOLS}/.evergreen/run-orchestration.sh + - command: shell.exec + params: + script: | + printf "\n" >> mo-expansion.yml + printf "MONGODB_VERSION: '%s'\n" "${MONGODB_VERSION}" >> mo-expansion.yml + printf "TOPOLOGY: '%s'\n" "${TOPOLOGY}" >> mo-expansion.yml # run-orchestration generates expansion file with MONGODB_URI and CRYPT_SHARED_LIB_PATH - command: expansions.update params: diff --git a/.evergreen/config/generate-config.php b/.evergreen/config/generate-config.php old mode 100644 new mode 100755 index 82a1d701a..c39a10829 --- a/.evergreen/config/generate-config.php +++ b/.evergreen/config/generate-config.php @@ -1,4 +1,4 @@ -#!/bin/env php +#!/usr/bin/env php - - Primary Root Certificate Authority - Most Certificates are issued by this CA. - Subject: {CN: 'Kernel Test CA'} - Issuer: self - include_header: false - output_path: '../drivers-evergreen-tools/.evergreen/ocsp/rsa' - extensions: - basicConstraints: - critical: true - CA: true - -- name: 'server.pem' - description: >- - Certificate with OCSP for the mongodb server. - Subject: - CN: 'localhost' - C: US - ST: NY - L: OCSP-1 - Issuer: 'ca.pem' - include_header: false - output_path: '../drivers-evergreen-tools/.evergreen/ocsp/rsa' - extensions: - basicConstraints: {CA: false} - subjectAltName: - DNS: localhost - IP: 127.0.0.1 - authorityInfoAccess: 'OCSP;URI:http://localhost:9001/power/level,OCSP;URI:http://localhost:8100/status' - subjectKeyIdentifier: hash - keyUsage: [digitalSignature, keyEncipherment] - extendedKeyUsage: [serverAuth, clientAuth] - -- name: 'server-mustStaple.pem' - description: >- - Certificate with Must Staple OCSP for the mongodb server. - Subject: - CN: 'localhost' - C: US - ST: NY - L: OCSP-1 - Issuer: 'ca.pem' - include_header: false - output_path: '../drivers-evergreen-tools/.evergreen/ocsp/rsa' - extensions: - basicConstraints: {CA: false} - subjectAltName: - DNS: localhost - IP: 127.0.0.1 - authorityInfoAccess: 'OCSP;URI:http://localhost:9001/power/level,OCSP;URI:http://localhost:8100/status' - mustStaple: true - subjectKeyIdentifier: hash - keyUsage: [digitalSignature, keyEncipherment] - extendedKeyUsage: [serverAuth, clientAuth] - -- name: 'server-singleEndpoint.pem' - description: >- - Certificate with a single OCSP endpoint for the mongodb server. - Subject: - CN: 'localhost' - C: US - ST: NY - L: OCSP-1 - Issuer: 'ca.pem' - include_header: false - output_path: '../drivers-evergreen-tools/.evergreen/ocsp/rsa' - extensions: - basicConstraints: {CA: false} - subjectAltName: - DNS: localhost - IP: 127.0.0.1 - authorityInfoAccess: 'OCSP;URI:http://localhost:8100/status' - subjectKeyIdentifier: hash - keyUsage: [digitalSignature, keyEncipherment] - extendedKeyUsage: [serverAuth, clientAuth] - -- name: 'server-mustStaple-singleEndpoint.pem' - description: >- - Certificate with Must Staple OCSP and one OCSP endpoint for the mongodb server. - Subject: - CN: 'localhost' - C: US - ST: NY - L: OCSP-1 - Issuer: 'ca.pem' - include_header: false - output_path: '../drivers-evergreen-tools/.evergreen/ocsp/rsa' - extensions: - basicConstraints: {CA: false} - subjectAltName: - DNS: localhost - IP: 127.0.0.1 - authorityInfoAccess: 'OCSP;URI:http://localhost:8100/status' - mustStaple: true - subjectKeyIdentifier: hash - keyUsage: [digitalSignature, keyEncipherment] - extendedKeyUsage: [serverAuth, clientAuth] - -- name: 'ocsp-responder.crt' - description: Certificate and key for the OCSP responder - Subject: - CN: 'localhost' - C: US - ST: NY - L: OCSP-3 - Issuer: 'ca.pem' - include_header: false - keyfile: 'ocsp-responder.key' - output_path: '../drivers-evergreen-tools/.evergreen/ocsp/rsa' - extensions: - basicConstraints: {CA: false} - keyUsage: [nonRepudiation, digitalSignature, keyEncipherment] - extendedKeyUsage: [OCSPSigning] - #noCheck: true - -### -# ECDSA tree -### - -# These are all special cases handled internally by mkcert.py -# Do NOT change the names - -- name: 'ecdsa-ca-ocsp.pem' - description: Root of ECDSA tree for OCSP testing - Issuer: self - tags: [ecdsa] - -- name: 'ecdsa-server-ocsp.pem' - description: ECDSA server certificate w/OCSP - Issuer: 'ecdsa-ca-ocsp.pem' - tags: [ecdsa, ocsp] - -- name: 'ecdsa-server-ocsp-mustStaple.pem' - description: ECDSA server certificate w/OCSP + must-staple - Issuer: 'ecdsa-ca-ocsp.pem' - tags: [ecdsa, ocsp, must-staple] - -- name: 'ecdsa-ocsp-responder.crt' - description: ECDSA certificate and key for OCSP responder - Issuer: 'ecdsa-ca-ocsp.pem' - tags: [ecdsa, ocsp, responder ] - -- name: 'ecdsa-server-ocsp-singleEndpoint.pem' - description: ECDSA server certificate w/OCSP + one OCSP endpoint - Issuer: 'ecdsa-ca-ocsp.pem' - tags: [ecdsa, ocsp, single-ocsp-endpoint] - -- name: 'ecdsa-server-ocsp-mustStaple-singleEndpoint.pem' - description: ECDSA server certificate w/OCSP + must-staple + one OCSP endpoint - Issuer: 'ecdsa-ca-ocsp.pem' - tags: [ecdsa, ocsp, must-staple, single-ocsp-endpoint] diff --git a/.evergreen/ocsp/ecdsa/ca.crt b/.evergreen/ocsp/ecdsa/ca.crt deleted file mode 100644 index 623739ecb..000000000 --- a/.evergreen/ocsp/ecdsa/ca.crt +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB9jCCAZygAwIBAgIERIhZ3jAKBggqhkjOPQQDAjB6MQswCQYDVQQGEwJVUzER -MA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNV -BAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEdMBsGA1UEAwwUS2VybmVsIFRl -c3QgRVNDREEgQ0EwHhcNMjAwMzE3MTk0NjU5WhcNNDAwMzEyMTk0NjU5WjB6MQsw -CQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3Jr -IENpdHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEdMBsGA1UE -AwwUS2VybmVsIFRlc3QgRVNDREEgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC -AAT1rsrbhlZEQAubaPkS23tOfSEdWNd+u7N5kV4nxKQDNxPcScnSGrb41tBEINdG -LQ/SopWZx9O8UJSrh8sqaV1AoxAwDjAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMC -A0gAMEUCIDEvg1FnzNQNnLDxyOthbOqpX58A0YfLjgGb8xAvvdr4AiEAtvF2jMt6 -/o4HVXXKdohjBJbETbr7XILEvnZ4Zt7QNl8= ------END CERTIFICATE----- diff --git a/.evergreen/ocsp/ecdsa/ca.key b/.evergreen/ocsp/ecdsa/ca.key deleted file mode 100644 index 05935962b..000000000 --- a/.evergreen/ocsp/ecdsa/ca.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgMzE6ziHkSWt+sE2O -RMFZ9wqjOg88cWTuMMYrKXXL1UWhRANCAAT1rsrbhlZEQAubaPkS23tOfSEdWNd+ -u7N5kV4nxKQDNxPcScnSGrb41tBEINdGLQ/SopWZx9O8UJSrh8sqaV1A ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/ecdsa/ca.pem b/.evergreen/ocsp/ecdsa/ca.pem deleted file mode 100644 index b5037745c..000000000 --- a/.evergreen/ocsp/ecdsa/ca.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIB9jCCAZygAwIBAgIERIhZ3jAKBggqhkjOPQQDAjB6MQswCQYDVQQGEwJVUzER -MA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNV -BAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEdMBsGA1UEAwwUS2VybmVsIFRl -c3QgRVNDREEgQ0EwHhcNMjAwMzE3MTk0NjU5WhcNNDAwMzEyMTk0NjU5WjB6MQsw -CQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3Jr -IENpdHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEdMBsGA1UE -AwwUS2VybmVsIFRlc3QgRVNDREEgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC -AAT1rsrbhlZEQAubaPkS23tOfSEdWNd+u7N5kV4nxKQDNxPcScnSGrb41tBEINdG -LQ/SopWZx9O8UJSrh8sqaV1AoxAwDjAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMC -A0gAMEUCIDEvg1FnzNQNnLDxyOthbOqpX58A0YfLjgGb8xAvvdr4AiEAtvF2jMt6 -/o4HVXXKdohjBJbETbr7XILEvnZ4Zt7QNl8= ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgMzE6ziHkSWt+sE2O -RMFZ9wqjOg88cWTuMMYrKXXL1UWhRANCAAT1rsrbhlZEQAubaPkS23tOfSEdWNd+ -u7N5kV4nxKQDNxPcScnSGrb41tBEINdGLQ/SopWZx9O8UJSrh8sqaV1A ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/ecdsa/mock-delegate-revoked.sh b/.evergreen/ocsp/ecdsa/mock-delegate-revoked.sh deleted file mode 100755 index 1e40fba5a..000000000 --- a/.evergreen/ocsp/ecdsa/mock-delegate-revoked.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env sh -python3 ../ocsp_mock.py \ - --ca_file ca.pem \ - --ocsp_responder_cert ocsp-responder.crt \ - --ocsp_responder_key ocsp-responder.key \ - -p 8100 \ - -v \ - --fault revoked diff --git a/.evergreen/ocsp/ecdsa/mock-delegate-valid.sh b/.evergreen/ocsp/ecdsa/mock-delegate-valid.sh deleted file mode 100755 index 5074a7eca..000000000 --- a/.evergreen/ocsp/ecdsa/mock-delegate-valid.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh -python3 ../ocsp_mock.py \ - --ca_file ca.pem \ - --ocsp_responder_cert ocsp-responder.crt \ - --ocsp_responder_key ocsp-responder.key \ - -p 8100 \ - -v diff --git a/.evergreen/ocsp/ecdsa/mock-revoked.sh b/.evergreen/ocsp/ecdsa/mock-revoked.sh deleted file mode 100755 index a6bf2ef02..000000000 --- a/.evergreen/ocsp/ecdsa/mock-revoked.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env sh -# Use the CA as the OCSP responder -python3 ../ocsp_mock.py \ - --ca_file ca.pem \ - --ocsp_responder_cert ca.crt \ - --ocsp_responder_key ca.key \ - -p 8100 \ - -v \ - --fault revoked - diff --git a/.evergreen/ocsp/ecdsa/mock-valid.sh b/.evergreen/ocsp/ecdsa/mock-valid.sh deleted file mode 100755 index c89ce9e95..000000000 --- a/.evergreen/ocsp/ecdsa/mock-valid.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh -python3 ../ocsp_mock.py \ - --ca_file ca.pem \ - --ocsp_responder_cert ca.crt \ - --ocsp_responder_key ca.key \ - -p 8100 \ - -v diff --git a/.evergreen/ocsp/ecdsa/ocsp-responder.crt b/.evergreen/ocsp/ecdsa/ocsp-responder.crt deleted file mode 100644 index 4d3f3e929..000000000 --- a/.evergreen/ocsp/ecdsa/ocsp-responder.crt +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICVTCCAfygAwIBAgIEfpRhITAKBggqhkjOPQQDAjB6MQswCQYDVQQGEwJVUzER -MA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNV -BAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEdMBsGA1UEAwwUS2VybmVsIFRl -c3QgRVNDREEgQ0EwHhcNMjAwMzE3MTk0NzAwWhcNNDAwMzEyMTk0NzAwWjBsMQsw -CQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3Jr -IENpdHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEPMA0GA1UE -AwwGc2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERca9Bv0PDLkCULyx -axwx8nyPqonFF88MQiZpY7wK7atBfWkpZ9B/ukq5p+xVDXxS49huEIQUWOZ5xosF -frma96N+MHwwCQYDVR0TBAIwADAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEw -HQYDVR0OBBYEFNQUc8MKrQDR4wAFZZ2o9PNLAiUHMAsGA1UdDwQEAwIF4DAnBgNV -HSUEIDAeBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMJMAoGCCqGSM49BAMC -A0cAMEQCIBQs56OofXC3Io6DjP4ccgpkX8cLHpMRb3jfZ6MxulniAiBVLoXo8K23 -YmpwoWKLFBKBdtGU+WDdD01Mb8X4iQ1gYg== ------END CERTIFICATE----- diff --git a/.evergreen/ocsp/ecdsa/ocsp-responder.key b/.evergreen/ocsp/ecdsa/ocsp-responder.key deleted file mode 100644 index 9e7eaa64e..000000000 --- a/.evergreen/ocsp/ecdsa/ocsp-responder.key +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgxxFxGTsPETczP0SW -69vnqYXZIgk+qG61j6JKElHa6duhRANCAARFxr0G/Q8MuQJQvLFrHDHyfI+qicUX -zwxCJmljvArtq0F9aSln0H+6Srmn7FUNfFLj2G4QhBRY5nnGiwV+uZr3 ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/ecdsa/rename.sh b/.evergreen/ocsp/ecdsa/rename.sh deleted file mode 100755 index cf72559c0..000000000 --- a/.evergreen/ocsp/ecdsa/rename.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -[ ! -f ecdsa-ca-ocsp.pem ] || mv ecdsa-ca-ocsp.pem ca.pem -[ ! -f ecdsa-ca-ocsp.crt ] || mv ecdsa-ca-ocsp.crt ca.crt -[ ! -f ecdsa-ca-ocsp.key ] || mv ecdsa-ca-ocsp.key ca.key -[ ! -f ecdsa-server-ocsp.pem ] || mv ecdsa-server-ocsp.pem server.pem -[ ! -f ecdsa-server-ocsp-mustStaple.pem ] || mv ecdsa-server-ocsp-mustStaple.pem server-mustStaple.pem -[ ! -f ecdsa-server-ocsp-singleEndpoint.pem ] || mv ecdsa-server-ocsp-singleEndpoint.pem server-singleEndpoint.pem -[ ! -f ecdsa-server-ocsp-mustStaple-singleEndpoint.pem ] || mv ecdsa-server-ocsp-mustStaple-singleEndpoint.pem server-mustStaple-singleEndpoint.pem -[ ! -f ecdsa-ocsp-responder.crt ] || mv ecdsa-ocsp-responder.crt ocsp-responder.crt -[ ! -f ecdsa-ocsp-responder.key ] || mv ecdsa-ocsp-responder.key ocsp-responder.key diff --git a/.evergreen/ocsp/ecdsa/server-mustStaple-singleEndpoint.pem b/.evergreen/ocsp/ecdsa/server-mustStaple-singleEndpoint.pem deleted file mode 100644 index c2d3caa3d..000000000 --- a/.evergreen/ocsp/ecdsa/server-mustStaple-singleEndpoint.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICmzCCAkGgAwIBAgIEK6+qITAKBggqhkjOPQQDAjB6MQswCQYDVQQGEwJVUzER -MA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNV -BAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEdMBsGA1UEAwwUS2VybmVsIFRl -c3QgRVNDREEgQ0EwHhcNMjAwNDE3MjIwNzM4WhcNNDAwNDEyMjIwNzM4WjBsMQsw -CQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3Jr -IENpdHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEPMA0GA1UE -AwwGc2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQp0AXlVttI8EFDhm -YZZTGT0W9XZvUwk+HCVvTyRruyFI/VRW6PvLuCrMpFiXrM6kSoDQDDwcIH4jBv6u -y5mhYaOBwjCBvzAJBgNVHRMEAjAAMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAA -ATAdBgNVHQ4EFgQUHnyVeKPYHhZOYzAfQW+C48W+mQowCwYDVR0PBAQDAgWgMB0G -A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA4BggrBgEFBQcBAQQsMCowKAYI -KwYBBQUHMAGGHGh0dHA6Ly9sb2NhbGhvc3Q6ODEwMC9zdGF0dXMwEQYIKwYBBQUH -ARgEBTADAgEFMAoGCCqGSM49BAMCA0gAMEUCIHiAly+9pDK3z4shFjqQZILGcvaP -/71l3WSdKAjfKd1LAiEA9CpCiaGR1a5D8qSvr518WZtqOVB+YsEk63aJs/2PtM0= ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgniP9x8ixUXWk16LR -EKiL5dqh2aH/ON6EmULoaReDLTKhRANCAARCnQBeVW20jwQUOGZhllMZPRb1dm9T -CT4cJW9PJGu7IUj9VFbo+8u4KsykWJeszqRKgNAMPBwgfiMG/q7LmaFh ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/ecdsa/server-mustStaple.pem b/.evergreen/ocsp/ecdsa/server-mustStaple.pem deleted file mode 100644 index b539779ef..000000000 --- a/.evergreen/ocsp/ecdsa/server-mustStaple.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICyjCCAnCgAwIBAgIEA54uVTAKBggqhkjOPQQDAjB6MQswCQYDVQQGEwJVUzER -MA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNV -BAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEdMBsGA1UEAwwUS2VybmVsIFRl -c3QgRVNDREEgQ0EwHhcNMjAwMzI2MTU1NzU1WhcNNDAwMzIxMTU1NzU1WjBsMQsw -CQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3Jr -IENpdHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEPMA0GA1UE -AwwGc2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEJjAN/Hd2R/RRBoAu -YouPhTbS/y2DiD47YQaUu1TlnrvABcvIgkMKYfbeNIhBfu44KzF2sKsmKrG6T6rs -NdJ3pqOB8TCB7jAJBgNVHRMEAjAAMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAA -ATAdBgNVHQ4EFgQUvHVMhH4zuedQN+9sQJ8LN7jvy3owCwYDVR0PBAQDAgWgMB0G -A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBnBggrBgEFBQcBAQRbMFkwLQYI -KwYBBQUHMAGGIWh0dHA6Ly9sb2NhbGhvc3Q6OTAwMS9wb3dlci9sZXZlbDAoBggr -BgEFBQcwAYYcaHR0cDovL2xvY2FsaG9zdDo4MTAwL3N0YXR1czARBggrBgEFBQcB -GAQFMAMCAQUwCgYIKoZIzj0EAwIDSAAwRQIgDiL8zqWkCR5Rc/YoAgV81qryUMrK -BQoP7fb1M0KKarECIQDPa5q1pFu+5UZ8gn7CP4/9xDcBiG6tQYK5N0FHAZXzEg== ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg1IHezsqNUk0tfGOS -E2RcM7R00ue1/E8/pBBUGSt7RW2hRANCAAQmMA38d3ZH9FEGgC5ii4+FNtL/LYOI -PjthBpS7VOWeu8AFy8iCQwph9t40iEF+7jgrMXawqyYqsbpPquw10nem ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/ecdsa/server-singleEndpoint.pem b/.evergreen/ocsp/ecdsa/server-singleEndpoint.pem deleted file mode 100644 index fb2cfc596..000000000 --- a/.evergreen/ocsp/ecdsa/server-singleEndpoint.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICiTCCAi6gAwIBAgIELzCNWTAKBggqhkjOPQQDAjB6MQswCQYDVQQGEwJVUzER -MA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNV -BAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEdMBsGA1UEAwwUS2VybmVsIFRl -c3QgRVNDREEgQ0EwHhcNMjAwNDE3MjIwNzQ0WhcNNDAwNDEyMjIwNzQ0WjBsMQsw -CQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3Jr -IENpdHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEPMA0GA1UE -AwwGc2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESvwx4QUCP0f5Dr8N -MMfO40epXIcain4+XEVy8hcAtR0nYD0QpnFJSX7E4b5eY7A/Lr7UEKx64Qg3qYEl -FgbezaOBrzCBrDAJBgNVHRMEAjAAMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAA -ATAdBgNVHQ4EFgQUfOg4eUnUTje/rTmAHnZ3XzdyStIwCwYDVR0PBAQDAgWgMB0G -A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA4BggrBgEFBQcBAQQsMCowKAYI -KwYBBQUHMAGGHGh0dHA6Ly9sb2NhbGhvc3Q6ODEwMC9zdGF0dXMwCgYIKoZIzj0E -AwIDSQAwRgIhAKT+d/zTlhzZnOeU05Gi6hJAC0W9Fq4K2Sh04Cdys9kgAiEAyEla -DrZl0P+kGIJN49CUTHBiXN1t6nSRflNrkFiPFmI= ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgqD1jXcZlgcRjdj1l -i2i0L0+hE4YmhdetvKwZ8REk8jqhRANCAARK/DHhBQI/R/kOvw0wx87jR6lchxqK -fj5cRXLyFwC1HSdgPRCmcUlJfsThvl5jsD8uvtQQrHrhCDepgSUWBt7N ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/ecdsa/server.pem b/.evergreen/ocsp/ecdsa/server.pem deleted file mode 100644 index d120e1852..000000000 --- a/.evergreen/ocsp/ecdsa/server.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICtzCCAl2gAwIBAgIEP6OYOTAKBggqhkjOPQQDAjB6MQswCQYDVQQGEwJVUzER -MA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAOBgNV -BAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEdMBsGA1UEAwwUS2VybmVsIFRl -c3QgRVNDREEgQ0EwHhcNMjAwMzI2MTU1ODA2WhcNNDAwMzIxMTU1ODA2WjBsMQsw -CQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3Jr -IENpdHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEPMA0GA1UE -AwwGc2VydmVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEK4FR+soHPeGhF5c+ -bPBX9/+gm+RimTqlXQAkHQHopLETOVexyt0eAVJe/euPAdKx3JvQ2fx2YOaBZK2U -D98UoKOB3jCB2zAJBgNVHRMEAjAAMBoGA1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAA -ATAdBgNVHQ4EFgQU2JCna5G/Yd+Hd9hkAoWXxSjQ7acwCwYDVR0PBAQDAgWgMB0G -A1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjBnBggrBgEFBQcBAQRbMFkwLQYI -KwYBBQUHMAGGIWh0dHA6Ly9sb2NhbGhvc3Q6OTAwMS9wb3dlci9sZXZlbDAoBggr -BgEFBQcwAYYcaHR0cDovL2xvY2FsaG9zdDo4MTAwL3N0YXR1czAKBggqhkjOPQQD -AgNIADBFAiEA3F6MCGLS+gBDMl3+GTAVxYYuxLbhW92CQLwh/FbDozYCIHQzJ2G/ -ht6PGW9nKueW0yDfppBVlxBmlKody9ugpcpO ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgp33qfUjflX1C7ROa -e5F/RNyIhLE9hnxg4eFQQTqdxUqhRANCAAQrgVH6ygc94aEXlz5s8Ff3/6Cb5GKZ -OqVdACQdAeiksRM5V7HK3R4BUl79648B0rHcm9DZ/HZg5oFkrZQP3xSg ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/mock-ocsp-responder-requirements.txt b/.evergreen/ocsp/mock-ocsp-responder-requirements.txt deleted file mode 100644 index 0344252b6..000000000 --- a/.evergreen/ocsp/mock-ocsp-responder-requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -asn1crypto==1.3.0 -flask==1.1.1 -oscrypto==1.2.0 diff --git a/.evergreen/ocsp/mock_ocsp_responder.py b/.evergreen/ocsp/mock_ocsp_responder.py deleted file mode 100755 index 6274e97ac..000000000 --- a/.evergreen/ocsp/mock_ocsp_responder.py +++ /dev/null @@ -1,614 +0,0 @@ -# -# This file has been modified in 2019 by MongoDB Inc. -# - -# OCSPBuilder is derived from https://github.com/wbond/ocspbuilder -# OCSPResponder is derived from https://github.com/threema-ch/ocspresponder - -# Copyright (c) 2015-2018 Will Bond - -# Permission is hereby granted, free of charge, to any person obtaining a copy of -# this software and associated documentation files (the "Software"), to deal in -# the Software without restriction, including without limitation the rights to -# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -# of the Software, and to permit persons to whom the Software is furnished to do -# so, subject to the following conditions: - -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. - -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Copyright 2016 Threema GmbH - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from __future__ import unicode_literals, division, absolute_import, print_function - -import logging -import base64 -import inspect -import re -import enum -import sys -import textwrap -from datetime import datetime, timezone, timedelta -from typing import Callable, Tuple, Optional - -from asn1crypto import x509, keys, core, ocsp -from asn1crypto.ocsp import OCSPRequest, OCSPResponse -from oscrypto import asymmetric -from flask import Flask, request, Response - -__version__ = '0.10.2' -__version_info__ = (0, 10, 2) - -logger = logging.getLogger(__name__) - -if sys.version_info < (3,): - byte_cls = str -else: - byte_cls = bytes - -def _pretty_message(string, *params): - """ - Takes a multi-line string and does the following: - - dedents - - converts newlines with text before and after into a single line - - strips leading and trailing whitespace - :param string: - The string to format - :param *params: - Params to interpolate into the string - :return: - The formatted string - """ - - output = textwrap.dedent(string) - - # Unwrap lines, taking into account bulleted lists, ordered lists and - # underlines consisting of = signs - if output.find('\n') != -1: - output = re.sub('(?<=\\S)\n(?=[^ \n\t\\d\\*\\-=])', ' ', output) - - if params: - output = output % params - - output = output.strip() - - return output - - -def _type_name(value): - """ - :param value: - A value to get the object name of - :return: - A unicode string of the object name - """ - - if inspect.isclass(value): - cls = value - else: - cls = value.__class__ - if cls.__module__ in set(['builtins', '__builtin__']): - return cls.__name__ - return '%s.%s' % (cls.__module__, cls.__name__) - -def _writer(func): - """ - Decorator for a custom writer, but a default reader - """ - - name = func.__name__ - return property(fget=lambda self: getattr(self, '_%s' % name), fset=func) - - -class OCSPResponseBuilder(object): - - _response_status = None - _certificate = None - _certificate_status = None - _revocation_date = None - _certificate_issuer = None - _hash_algo = None - _key_hash_algo = None - _nonce = None - _this_update = None - _next_update = None - _response_data_extensions = None - _single_response_extensions = None - - def __init__(self, response_status, certificate_status_list=[], revocation_date=None): - """ - Unless changed, responses will use SHA-256 for the signature, - and will be valid from the moment created for one week. - :param response_status: - A unicode string of OCSP response type: - - "successful" - when the response includes information about the certificate - - "malformed_request" - when the request could not be understood - - "internal_error" - when an internal error occured with the OCSP responder - - "try_later" - when the OCSP responder is temporarily unavailable - - "sign_required" - when the OCSP request must be signed - - "unauthorized" - when the responder is not the correct responder for the certificate - :param certificate_list: - A list of tuples with certificate serial number and certificate status objects. - certificate_status: - A unicode string of the status of the certificate. Only required if - the response_status is "successful". - - "good" - when the certificate is in good standing - - "revoked" - when the certificate is revoked without a reason code - - "key_compromise" - when a private key is compromised - - "ca_compromise" - when the CA issuing the certificate is compromised - - "affiliation_changed" - when the certificate subject name changed - - "superseded" - when the certificate was replaced with a new one - - "cessation_of_operation" - when the certificate is no longer needed - - "certificate_hold" - when the certificate is temporarily invalid - - "remove_from_crl" - only delta CRLs - when temporary hold is removed - - "privilege_withdrawn" - one of the usages for a certificate was removed - - "unknown" - the responder doesn't know about the certificate being requested - :param revocation_date: - A datetime.datetime object of when the certificate was revoked, if - the response_status is "successful" and the certificate status is - not "good" or "unknown". - """ - self._response_status = response_status - self._certificate_status_list = certificate_status_list - self._revocation_date = revocation_date - - self._key_hash_algo = 'sha1' - self._hash_algo = 'sha256' - self._response_data_extensions = {} - self._single_response_extensions = {} - - @_writer - def nonce(self, value): - """ - The nonce that was provided during the request. - """ - - if not isinstance(value, byte_cls): - raise TypeError(_pretty_message( - ''' - nonce must be a byte string, not %s - ''', - _type_name(value) - )) - - self._nonce = value - - @_writer - def certificate_issuer(self, value): - """ - An asn1crypto.x509.Certificate object of the issuer of the certificate. - This should only be set if the OCSP responder is not the issuer of - the certificate, but instead a special certificate only for OCSP - responses. - """ - - if value is not None: - is_oscrypto = isinstance(value, asymmetric.Certificate) - if not is_oscrypto and not isinstance(value, x509.Certificate): - raise TypeError(_pretty_message( - ''' - certificate_issuer must be an instance of - asn1crypto.x509.Certificate or - oscrypto.asymmetric.Certificate, not %s - ''', - _type_name(value) - )) - - if is_oscrypto: - value = value.asn1 - - self._certificate_issuer = value - - @_writer - def next_update(self, value): - """ - A datetime.datetime object of when the response may next change. This - should only be set if responses are cached. If responses are generated - fresh on every request, this should not be set. - """ - - if not isinstance(value, datetime): - raise TypeError(_pretty_message( - ''' - next_update must be an instance of datetime.datetime, not %s - ''', - _type_name(value) - )) - - self._next_update = value - - def build(self, responder_private_key=None, responder_certificate=None): - """ - Validates the request information, constructs the ASN.1 structure and - signs it. - The responder_private_key and responder_certificate parameters are onlystr - required if the response_status is "successful". - :param responder_private_key: - An asn1crypto.keys.PrivateKeyInfo or oscrypto.asymmetric.PrivateKey - object for the private key to sign the response with - :param responder_certificate: - An asn1crypto.x509.Certificate or oscrypto.asymmetric.Certificate - object of the certificate associated with the private key - :return: - An asn1crypto.ocsp.OCSPResponse object of the response - """ - if self._response_status != 'successful': - return ocsp.OCSPResponse({ - 'response_status': self._response_status - }) - - is_oscrypto = isinstance(responder_private_key, asymmetric.PrivateKey) - if not isinstance(responder_private_key, keys.PrivateKeyInfo) and not is_oscrypto: - raise TypeError(_pretty_message( - ''' - responder_private_key must be an instance ofthe c - asn1crypto.keys.PrivateKeyInfo or - oscrypto.asymmetric.PrivateKey, not %s - ''', - _type_name(responder_private_key) - )) - - cert_is_oscrypto = isinstance(responder_certificate, asymmetric.Certificate) - if not isinstance(responder_certificate, x509.Certificate) and not cert_is_oscrypto: - raise TypeError(_pretty_message( - ''' - responder_certificate must be an instance of - asn1crypto.x509.Certificate or - oscrypto.asymmetric.Certificate, not %s - ''', - _type_name(responder_certificate) - )) - - if cert_is_oscrypto: - responder_certificate = responder_certificate.asn1 - - if self._certificate_status_list is None: - raise ValueError(_pretty_message( - ''' - certificate_status_list must be set if the response_status is - "successful" - ''' - )) - - def _make_extension(name, value): - return { - 'extn_id': name, - 'critical': False, - 'extn_value': value - } - - responses = [] - for serial, status in self._certificate_status_list: - response_data_extensions = [] - single_response_extensions = [] - for name, value in self._response_data_extensions.items(): - response_data_extensions.append(_make_extension(name, value)) - if self._nonce: - response_data_extensions.append( - _make_extension('nonce', self._nonce) - ) - - if not response_data_extensions: - response_data_extensions = None - - for name, value in self._single_response_extensions.items(): - single_response_extensions.append(_make_extension(name, value)) - - if self._certificate_issuer: - single_response_extensions.append( - _make_extension( - 'certificate_issuer', - [ - x509.GeneralName( - name='directory_name', - value=self._certificate_issuer.subject - ) - ] - ) - ) - - if not single_response_extensions: - single_response_extensions = None - - responder_key_hash = getattr(responder_certificate.public_key, self._key_hash_algo) - - if status == 'good': - cert_status = ocsp.CertStatus( - name='good', - value=core.Null() - ) - elif status == 'unknown': - cert_status = ocsp.CertStatus( - name='unknown', - value=core.Null() - ) - else: - reason = status if status != 'revoked' else 'unspecified' - cert_status = ocsp.CertStatus( - name='revoked', - value={ - 'revocation_time': self._revocation_date, - 'revocation_reason': reason, - } - ) - - issuer = self._certificate_issuer if self._certificate_issuer else responder_certificate - - produced_at = datetime.now(timezone.utc).replace(microsecond=0) - - if self._this_update is None: - self._this_update = produced_at - - if self._next_update is None: - self._next_update = (self._this_update + timedelta(days=7)).replace(microsecond=0) - - response = { - 'cert_id': { - 'hash_algorithm': { - 'algorithm': self._key_hash_algo - }, - 'issuer_name_hash': getattr(issuer.subject, self._key_hash_algo), - 'issuer_key_hash': getattr(issuer.public_key, self._key_hash_algo), - 'serial_number': serial, - }, - 'cert_status': cert_status, - 'this_update': self._this_update, - 'next_update': self._next_update, - 'single_extensions': single_response_extensions - } - responses.append(response) - - response_data = ocsp.ResponseData({ - 'responder_id': ocsp.ResponderId(name='by_key', value=responder_key_hash), - 'produced_at': produced_at, - 'responses': responses, - 'response_extensions': response_data_extensions - }) - - signature_algo = responder_private_key.algorithm - if signature_algo == 'ec': - signature_algo = 'ecdsa' - - signature_algorithm_id = '%s_%s' % (self._hash_algo, signature_algo) - - if responder_private_key.algorithm == 'rsa': - sign_func = asymmetric.rsa_pkcs1v15_sign - elif responder_private_key.algorithm == 'dsa': - sign_func = asymmetric.dsa_sign - elif responder_private_key.algorithm == 'ec': - sign_func = asymmetric.ecdsa_sign - - if not is_oscrypto: - responder_private_key = asymmetric.load_private_key(responder_private_key) - signature_bytes = sign_func(responder_private_key, response_data.dump(), self._hash_algo) - - certs = None - if self._certificate_issuer and getattr(self._certificate_issuer.public_key, self._key_hash_algo) != responder_key_hash: - certs = [responder_certificate] - - return ocsp.OCSPResponse({ - 'response_status': self._response_status, - 'response_bytes': { - 'response_type': 'basic_ocsp_response', - 'response': { - 'tbs_response_data': response_data, - 'signature_algorithm': {'algorithm': signature_algorithm_id}, - 'signature': signature_bytes, - 'certs': certs, - } - } - }) - -# Enums - -class ResponseStatus(enum.Enum): - successful = 'successful' - malformed_request = 'malformed_request' - internal_error = 'internal_error' - try_later = 'try_later' - sign_required = 'sign_required' - unauthorized = 'unauthorized' - - -class CertificateStatus(enum.Enum): - good = 'good' - revoked = 'revoked' - key_compromise = 'key_compromise' - ca_compromise = 'ca_compromise' - affiliation_changed = 'affiliation_changed' - superseded = 'superseded' - cessation_of_operation = 'cessation_of_operation' - certificate_hold = 'certificate_hold' - remove_from_crl = 'remove_from_crl' - privilege_withdrawn = 'privilege_withdrawn' - unknown = 'unknown' - - -# API endpoints -FAULT_REVOKED = "revoked" -FAULT_UNKNOWN = "unknown" - -app = Flask(__name__) -class OCSPResponder: - - def __init__(self, issuer_cert: str, responder_cert: str, responder_key: str, - fault: str, next_update_seconds: int): - """ - Create a new OCSPResponder instance. - - :param issuer_cert: Path to the issuer certificate. - :param responder_cert: Path to the certificate of the OCSP responder - with the `OCSP Signing` extension. - :param responder_key: Path to the private key belonging to the - responder cert. - :param validate_func: A function that - given a certificate serial - - will return the appropriate :class:`CertificateStatus` and - - depending on the status - a revocation datetime. - :param cert_retrieve_func: A function that - given a certificate serial - - will return the corresponding certificate as a string. - :param next_update_seconds: The ``nextUpdate`` value that will be written - into the response. Default: 9 hours. - - """ - # Certs and keys - self._issuer_cert = asymmetric.load_certificate(issuer_cert) - self._responder_cert = asymmetric.load_certificate(responder_cert) - self._responder_key = asymmetric.load_private_key(responder_key) - - # Next update - self._next_update_seconds = next_update_seconds - - self._fault = fault - - def _fail(self, status: ResponseStatus) -> OCSPResponse: - builder = OCSPResponseBuilder(response_status=status.value) - return builder.build() - - def parse_ocsp_request(self, request_der: bytes) -> OCSPRequest: - """ - Parse the request bytes, return an ``OCSPRequest`` instance. - """ - return OCSPRequest.load(request_der) - - def validate(self): - time = datetime(2018, 1, 1, 1, 00, 00, 00, timezone.utc) - if self._fault == FAULT_REVOKED: - return (CertificateStatus.revoked, time) - elif self._fault == FAULT_UNKNOWN: - return (CertificateStatus.unknown, None) - elif self._fault != None: - raise NotImplemented('Fault type could not be found') - return (CertificateStatus.good, time) - - def _build_ocsp_response(self, ocsp_request: OCSPRequest) -> OCSPResponse: - """ - Create and return an OCSP response from an OCSP request. - """ - # Get the certificate serial - tbs_request = ocsp_request['tbs_request'] - request_list = tbs_request['request_list'] - if len(request_list) < 1: - logger.warning('Received OCSP request with no requests') - raise NotImplemented('Empty requests not supported') - - single_request = request_list[0] # TODO: Support more than one request - req_cert = single_request['req_cert'] - serial = req_cert['serial_number'].native - - # Check certificate status - try: - certificate_status, revocation_date = self.validate() - except Exception as e: - logger.exception('Could not determine certificate status: %s', e) - return self._fail(ResponseStatus.internal_error) - - certificate_status_list = [(serial, certificate_status.value)] - - # Build the response - builder = OCSPResponseBuilder(**{ - 'response_status': ResponseStatus.successful.value, - 'certificate_status_list': certificate_status_list, - 'revocation_date': revocation_date, - }) - - # Parse extensions - for extension in tbs_request['request_extensions']: - extn_id = extension['extn_id'].native - critical = extension['critical'].native - value = extension['extn_value'].parsed - - # This variable tracks whether any unknown extensions were encountered - unknown = False - - # Handle nonce extension - if extn_id == 'nonce': - builder.nonce = value.native - - # That's all we know - else: - unknown = True - - # If an unknown critical extension is encountered (which should not - # usually happen, according to RFC 6960 4.1.2), we should throw our - # hands up in despair and run. - if unknown is True and critical is True: - logger.warning('Could not parse unknown critical extension: %r', - dict(extension.native)) - return self._fail(ResponseStatus.internal_error) - - # If it's an unknown non-critical extension, we can safely ignore it. - elif unknown is True: - logger.info('Ignored unknown non-critical extension: %r', dict(extension.native)) - - # Set certificate issuer - builder.certificate_issuer = self._issuer_cert - - # Set next update date - now = datetime.now(timezone.utc) - builder.next_update = (now + timedelta(seconds=self._next_update_seconds)).replace(microsecond=0) - - return builder.build(self._responder_key, self._responder_cert) - - def build_http_response(self, request_der: bytes) -> Response: - global app - response_der = self._build_ocsp_response(request_der).dump() - resp = app.make_response((response_der, 200)) - resp.headers['content_type'] = 'application/ocsp-response' - return resp - - -responder = None - -def init_responder(issuer_cert: str, responder_cert: str, responder_key: str, fault: str, next_update_seconds: int): - global responder - responder = OCSPResponder(issuer_cert=issuer_cert, responder_cert=responder_cert, responder_key=responder_key, fault=fault, next_update_seconds=next_update_seconds) - -def init(port=8080, debug=False): - logger.info('Launching %sserver on port %d', 'debug' if debug else '', port) - app.run(port=port, debug=debug) - -@app.route('/', methods=['GET']) -def _handle_root(): - return 'ocsp-responder' - -@app.route('/status/', defaults={'u_path': ''}, methods=['GET']) -@app.route('/status/', methods=['GET']) -def _handle_get(u_path): - global responder - """ - An OCSP GET request contains the DER-in-base64 encoded OCSP request in the - HTTP request URL. - """ - der = base64.b64decode(u_path) - ocsp_request = responder.parse_ocsp_request(der) - return responder.build_http_response(ocsp_request) - -@app.route('/status', methods=['POST']) -def _handle_post(): - global responder - """ - An OCSP POST request contains the DER encoded OCSP request in the HTTP - request body. - """ - ocsp_request = responder.parse_ocsp_request(request.data) - return responder.build_http_response(ocsp_request) diff --git a/.evergreen/ocsp/ocsp_mock.py b/.evergreen/ocsp/ocsp_mock.py deleted file mode 100755 index 04963b385..000000000 --- a/.evergreen/ocsp/ocsp_mock.py +++ /dev/null @@ -1,48 +0,0 @@ -#! /usr/bin/env python3 -""" -Python script to interface as a mock OCSP responder. -""" - -import argparse -import logging -import sys -import os - -sys.path.append(os.path.join(os.getcwd() ,'src', 'third_party', 'mock_ocsp_responder')) - -import mock_ocsp_responder - -def main(): - """Main entry point""" - parser = argparse.ArgumentParser(description="MongoDB Mock OCSP Responder.") - - parser.add_argument('-p', '--port', type=int, default=8080, help="Port to listen on") - - parser.add_argument('--ca_file', type=str, required=True, help="CA file for OCSP responder") - - parser.add_argument('-v', '--verbose', action='count', help="Enable verbose tracing") - - parser.add_argument('--ocsp_responder_cert', type=str, required=True, help="OCSP Responder Certificate") - - parser.add_argument('--ocsp_responder_key', type=str, required=True, help="OCSP Responder Keyfile") - - parser.add_argument('--fault', choices=[mock_ocsp_responder.FAULT_REVOKED, mock_ocsp_responder.FAULT_UNKNOWN, None], default=None, type=str, help="Specify a specific fault to test") - - parser.add_argument('--next_update_seconds', type=int, default=32400, help="Specify how long the OCSP response should be valid for") - - args = parser.parse_args() - if args.verbose: - logging.basicConfig(level=logging.DEBUG) - - print('Initializing OCSP Responder') - mock_ocsp_responder.init_responder(issuer_cert=args.ca_file, responder_cert=args.ocsp_responder_cert, responder_key=args.ocsp_responder_key, fault=args.fault, next_update_seconds=args.next_update_seconds) - - if args.verbose: - mock_ocsp_responder.init(args.port, debug=True) - else: - mock_ocsp_responder.init(args.port) - - print('Mock OCSP Responder is running on port %s' % (str(args.port))) - -if __name__ == '__main__': - main() diff --git a/.evergreen/ocsp/rsa/ca.crt b/.evergreen/ocsp/rsa/ca.crt deleted file mode 100644 index ee6dc5a65..000000000 --- a/.evergreen/ocsp/rsa/ca.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDeTCCAmGgAwIBAgIEZLtwgzANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV -UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO -BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVs -IFRlc3QgQ0EwHhcNMjAwMjA2MjAxMzExWhcNNDAwMjA4MjAxMzExWjB0MQswCQYD -VQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENp -dHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwO -S2VybmVsIFRlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0 -D1mnIrh7RRrCUEocNYLMZ2azo6c6NUTqSAMQyDDvRUsezil2NCqKo0ptMRtmb8Ws -yuaRUkjFhh9M69kiuj89GKRALXxExHjWX7e8iS1NTGL+Uakc1J23Z5FvlUyVLucC -fcAZ6MvcC7n6qpzUxkqz1u/27Ze9nv2mleLYBVWbGpjSHAUDuZzMCBs5Q/QrUwL7 -4cIxNsS0iHpYI3aee67cmFoK4guN9LBOtviyXUTP22kJLXe41HDjdWh01+FxcuwH -rGmeGQwiSlw48wkdoC0M51SwpHEq+K91BqGsTboC5mshqKA88OPf5JK9ied/OsNX -+K6p5v3RVHn89VaWiTorAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI -hvcNAQELBQADggEBAAd1jj1GECUEJMH00IX3VFgb2RpJ4Qi8TKAZgMMHdE7Cyv4M -p4w/zvQC1F6i54n+TWucq3I+c33lEj63ybFdJO5HOWoGzC/f5qO7z0gYdP2Ltdxg -My2uVZNQS+B8hF9MhGUeFnOpzAbKW2If3KN1fn/m2NDYGEK/Z2t7ZkpOcpEW5Lib -vX+BBG/s4DeyhRXy+grs0ASU/z8VOhZYSJpgdbvXsY4RXXloTDcWIlNqra5K6+3T -nVEkBDm0Qw97Y6FsqBVxk4kgWC6xNxQ4Sp+Sg4wthMQ70iFGlMin0kYRo7kAIUF9 -M+v2vMwTFWkcl0BT5LobE39kWVbQKEVPH7nkItE= ------END CERTIFICATE----- diff --git a/.evergreen/ocsp/rsa/ca.key b/.evergreen/ocsp/rsa/ca.key deleted file mode 100644 index 9d10cb2db..000000000 --- a/.evergreen/ocsp/rsa/ca.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC0D1mnIrh7RRrC -UEocNYLMZ2azo6c6NUTqSAMQyDDvRUsezil2NCqKo0ptMRtmb8WsyuaRUkjFhh9M -69kiuj89GKRALXxExHjWX7e8iS1NTGL+Uakc1J23Z5FvlUyVLucCfcAZ6MvcC7n6 -qpzUxkqz1u/27Ze9nv2mleLYBVWbGpjSHAUDuZzMCBs5Q/QrUwL74cIxNsS0iHpY -I3aee67cmFoK4guN9LBOtviyXUTP22kJLXe41HDjdWh01+FxcuwHrGmeGQwiSlw4 -8wkdoC0M51SwpHEq+K91BqGsTboC5mshqKA88OPf5JK9ied/OsNX+K6p5v3RVHn8 -9VaWiTorAgMBAAECggEBAJ7umazMGdg80/TGF9Q0a2JutplDj5zyXgUJUSNkAMWB -/V+Qi8pZG1/J6CzfVpche3McmU2WOsOWslQcLUnY6W7NLFW1kGXGof5e+HgDASik -jxB6FfJrvVagpR+/wZxAjQmG46Q69o4hD6SxKcMpz9BTnPXxG6n1B2EeFd+lPb2r -zf/C4uXBczWn5rFXkj0DZGq81ZXewcnUNnxjQnccVCuYW+hqYxznSxqWTCD6hsvg -sGceqv0Ppp6TqMSECCIIJ+kVlbiAC2i6mnoertheFVrNUdwDb8nRn6fs8T+F0ShW -PdxIfSvAaBKqvseJqqueVpuwVcdSl+moJYlCdMb4cUECgYEA30AIHvMQq/s33ipV -62xOKXcEZ7tKaJrAbJvG4cx934wNiQ0tLwRNlonGbuTjsUaPRvagVeJND/UPIsfH -ZwoY1Uw25fZNaveoQtU8LQBAG53R5yaMiUH48JWVvKRdfG09zr6EFCM/k2loHS1W -/CiDlaIl59B8REnihyn0wvkiaIsCgYEAznlZRhlruk+n2sWklierav4M8GEK22+/ -A/UP1eUnlcHgSaFZoM0sukSrisZnj6zu/BAfFEVN5czra3ARrLClLQteFREr2BMF -9XymrjNG99QkBAall7BGpfkDW/D2DFZa4G5R6AMG+pYZHCU84U4QT5ZKyfdhTUbQ -uTYx2F31COECgYAIUm+7D56AerXjbzqSsw/a1dfxMfcdHR+tLMVmJ2RNz/+1KyuT -BBsMUIh4G8otEo9GuuzRJsVuodj1l/Lj8WlpkhS9z8elBCRekWpT1x2Mqf5oGnTE -rRPli/3v8USW3c+fBFUSFxpImXZLGCSU88Gr80ZsdMYdGY/7L+Iy3myc7wKBgQC1 -uHeqCpWV1KWXFnxU63UjJZWdussjdqZXhUf6qUS9uXT9WNTZgbrr9aRE73oWKc3s -awPvg0+cAU7xsCDeLFoz2t1jDUnZUmTcOmk4yEidtkg8gt0bNDn5ucALG3hyQ06Y -WIAeAwwRYCmZa+y5H0ubwFryhpdMvBbX66rTE16mAQKBgC5PJd9zLEzyLj/jUfZ0 -xOwXubu9GejOuCiVwKMTn73nvdi57zFBOrDxSl9yVCRhve61L5fcJixRDiwx8qtd -VGclRMxbVPKVfKpAyKjpsmZXk3IPHjXjJb3fYLXAnzRHk6v+yjVn4fy2Z93pW/cF -wBgQNqXLNTGrBzrFi469oc1s ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/rsa/ca.pem b/.evergreen/ocsp/rsa/ca.pem deleted file mode 100644 index afa468f04..000000000 --- a/.evergreen/ocsp/rsa/ca.pem +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDeTCCAmGgAwIBAgIEZLtwgzANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV -UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO -BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVs -IFRlc3QgQ0EwHhcNMjAwMjA2MjAxMzExWhcNNDAwMjA4MjAxMzExWjB0MQswCQYD -VQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENp -dHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwO -S2VybmVsIFRlc3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0 -D1mnIrh7RRrCUEocNYLMZ2azo6c6NUTqSAMQyDDvRUsezil2NCqKo0ptMRtmb8Ws -yuaRUkjFhh9M69kiuj89GKRALXxExHjWX7e8iS1NTGL+Uakc1J23Z5FvlUyVLucC -fcAZ6MvcC7n6qpzUxkqz1u/27Ze9nv2mleLYBVWbGpjSHAUDuZzMCBs5Q/QrUwL7 -4cIxNsS0iHpYI3aee67cmFoK4guN9LBOtviyXUTP22kJLXe41HDjdWh01+FxcuwH -rGmeGQwiSlw48wkdoC0M51SwpHEq+K91BqGsTboC5mshqKA88OPf5JK9ied/OsNX -+K6p5v3RVHn89VaWiTorAgMBAAGjEzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI -hvcNAQELBQADggEBAAd1jj1GECUEJMH00IX3VFgb2RpJ4Qi8TKAZgMMHdE7Cyv4M -p4w/zvQC1F6i54n+TWucq3I+c33lEj63ybFdJO5HOWoGzC/f5qO7z0gYdP2Ltdxg -My2uVZNQS+B8hF9MhGUeFnOpzAbKW2If3KN1fn/m2NDYGEK/Z2t7ZkpOcpEW5Lib -vX+BBG/s4DeyhRXy+grs0ASU/z8VOhZYSJpgdbvXsY4RXXloTDcWIlNqra5K6+3T -nVEkBDm0Qw97Y6FsqBVxk4kgWC6xNxQ4Sp+Sg4wthMQ70iFGlMin0kYRo7kAIUF9 -M+v2vMwTFWkcl0BT5LobE39kWVbQKEVPH7nkItE= ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC0D1mnIrh7RRrC -UEocNYLMZ2azo6c6NUTqSAMQyDDvRUsezil2NCqKo0ptMRtmb8WsyuaRUkjFhh9M -69kiuj89GKRALXxExHjWX7e8iS1NTGL+Uakc1J23Z5FvlUyVLucCfcAZ6MvcC7n6 -qpzUxkqz1u/27Ze9nv2mleLYBVWbGpjSHAUDuZzMCBs5Q/QrUwL74cIxNsS0iHpY -I3aee67cmFoK4guN9LBOtviyXUTP22kJLXe41HDjdWh01+FxcuwHrGmeGQwiSlw4 -8wkdoC0M51SwpHEq+K91BqGsTboC5mshqKA88OPf5JK9ied/OsNX+K6p5v3RVHn8 -9VaWiTorAgMBAAECggEBAJ7umazMGdg80/TGF9Q0a2JutplDj5zyXgUJUSNkAMWB -/V+Qi8pZG1/J6CzfVpche3McmU2WOsOWslQcLUnY6W7NLFW1kGXGof5e+HgDASik -jxB6FfJrvVagpR+/wZxAjQmG46Q69o4hD6SxKcMpz9BTnPXxG6n1B2EeFd+lPb2r -zf/C4uXBczWn5rFXkj0DZGq81ZXewcnUNnxjQnccVCuYW+hqYxznSxqWTCD6hsvg -sGceqv0Ppp6TqMSECCIIJ+kVlbiAC2i6mnoertheFVrNUdwDb8nRn6fs8T+F0ShW -PdxIfSvAaBKqvseJqqueVpuwVcdSl+moJYlCdMb4cUECgYEA30AIHvMQq/s33ipV -62xOKXcEZ7tKaJrAbJvG4cx934wNiQ0tLwRNlonGbuTjsUaPRvagVeJND/UPIsfH -ZwoY1Uw25fZNaveoQtU8LQBAG53R5yaMiUH48JWVvKRdfG09zr6EFCM/k2loHS1W -/CiDlaIl59B8REnihyn0wvkiaIsCgYEAznlZRhlruk+n2sWklierav4M8GEK22+/ -A/UP1eUnlcHgSaFZoM0sukSrisZnj6zu/BAfFEVN5czra3ARrLClLQteFREr2BMF -9XymrjNG99QkBAall7BGpfkDW/D2DFZa4G5R6AMG+pYZHCU84U4QT5ZKyfdhTUbQ -uTYx2F31COECgYAIUm+7D56AerXjbzqSsw/a1dfxMfcdHR+tLMVmJ2RNz/+1KyuT -BBsMUIh4G8otEo9GuuzRJsVuodj1l/Lj8WlpkhS9z8elBCRekWpT1x2Mqf5oGnTE -rRPli/3v8USW3c+fBFUSFxpImXZLGCSU88Gr80ZsdMYdGY/7L+Iy3myc7wKBgQC1 -uHeqCpWV1KWXFnxU63UjJZWdussjdqZXhUf6qUS9uXT9WNTZgbrr9aRE73oWKc3s -awPvg0+cAU7xsCDeLFoz2t1jDUnZUmTcOmk4yEidtkg8gt0bNDn5ucALG3hyQ06Y -WIAeAwwRYCmZa+y5H0ubwFryhpdMvBbX66rTE16mAQKBgC5PJd9zLEzyLj/jUfZ0 -xOwXubu9GejOuCiVwKMTn73nvdi57zFBOrDxSl9yVCRhve61L5fcJixRDiwx8qtd -VGclRMxbVPKVfKpAyKjpsmZXk3IPHjXjJb3fYLXAnzRHk6v+yjVn4fy2Z93pW/cF -wBgQNqXLNTGrBzrFi469oc1s ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/rsa/mock-delegate-revoked.sh b/.evergreen/ocsp/rsa/mock-delegate-revoked.sh deleted file mode 100755 index adf026ce1..000000000 --- a/.evergreen/ocsp/rsa/mock-delegate-revoked.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env sh -python3 ../ocsp_mock.py \ - --ca_file ca.pem \ - --ocsp_responder_cert ocsp_responder.crt \ - --ocsp_responder_key ocsp_responder.key \ - -p 8100 \ - -v \ - --fault revoked diff --git a/.evergreen/ocsp/rsa/mock-delegate-valid.sh b/.evergreen/ocsp/rsa/mock-delegate-valid.sh deleted file mode 100755 index 5074a7eca..000000000 --- a/.evergreen/ocsp/rsa/mock-delegate-valid.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh -python3 ../ocsp_mock.py \ - --ca_file ca.pem \ - --ocsp_responder_cert ocsp-responder.crt \ - --ocsp_responder_key ocsp-responder.key \ - -p 8100 \ - -v diff --git a/.evergreen/ocsp/rsa/mock-revoked.sh b/.evergreen/ocsp/rsa/mock-revoked.sh deleted file mode 100755 index 4a17926b9..000000000 --- a/.evergreen/ocsp/rsa/mock-revoked.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env sh -python3 ../ocsp_mock.py \ - --ca_file ca.pem \ - --ocsp_responder_cert ca.crt \ - --ocsp_responder_key ca.key \ - -p 8100 \ - -v \ - --fault revoked diff --git a/.evergreen/ocsp/rsa/mock-valid.sh b/.evergreen/ocsp/rsa/mock-valid.sh deleted file mode 100755 index c89ce9e95..000000000 --- a/.evergreen/ocsp/rsa/mock-valid.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh -python3 ../ocsp_mock.py \ - --ca_file ca.pem \ - --ocsp_responder_cert ca.crt \ - --ocsp_responder_key ca.key \ - -p 8100 \ - -v diff --git a/.evergreen/ocsp/rsa/ocsp-responder.crt b/.evergreen/ocsp/rsa/ocsp-responder.crt deleted file mode 100644 index 58caba358..000000000 --- a/.evergreen/ocsp/rsa/ocsp-responder.crt +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDgzCCAmugAwIBAgIEA0v5yzANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV -UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO -BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVs -IFRlc3QgQ0EwHhcNMjAwMjA2MjMyMjU4WhcNNDAwMjA4MjMyMjU4WjBiMRAwDgYD -VQQKDAdNb25nb0RCMQ8wDQYDVQQLDAZLZXJuZWwxEjAQBgNVBAMMCWxvY2FsaG9z -dDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMQ8wDQYDVQQHDAZPQ1NQLTMwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDiHYXGCSOK3gxlEmNSLepoFJbv -hfYxxaqAWEceiTQdRpN97YRr/ywPm0+932EsE6/gIjqVs8IOtsiFKK1lQ9sL/9f+ -ckS5gj9AR+Cta+FLDRP5plE+aao5no0kA8qMx2HHd47XFnuxKtUztRmgmTBNYbYh -PdY1kjBSRyuXXBn1V6TRaYhk6dsK56Zvhgo6Y3YqpjpldePa4E0XpUlBhY020QXt -K3iWFauEYKcKR2JI2oVjY0tR60zf3GHkMLCe7SdbofCdwkBHcCctLSp4xYb44JGb -JX1npM1mhxR4pnp80tbEXNvXQ4S3kmd7/QFUYE4IdXVkXNhkK6PtIdDKbLa9AgMB -AAGjLzAtMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMBMGA1UdJQQMMAoGCCsGAQUF -BwMJMA0GCSqGSIb3DQEBCwUAA4IBAQB5igUUQSzxzWvL+28TDYFuNnTB0hvqTnd7 -ZVyk8RVBiUkudxEmt5uFRWT6GOc7Y1H6w4igtuhhqxAeG9bUob+VQkCyc4GxaHSO -oBtl/Zu+ts+0gUUlm+Bs6wFnFsGhM0awV/vqigDADZT2jbqbHBm2lP99eq8fsi6L -kpohhbuTVWjLuViARYIOJLoBnNRpVXqwD5A8uNqwZI2OVGh1cQYNZcmfLJ1u2j5C -ycohoa+o8NGgkxEhG2QETdVodfHT2dUgzPDvO42CVa3MK7J0sovBU5DeuIDPV/hh -j+v5A8L8gMiNpkLClqt2TEiFH2GItWDNQjTgrLq9iFUgJnbwuj4F ------END CERTIFICATE----- diff --git a/.evergreen/ocsp/rsa/ocsp-responder.key b/.evergreen/ocsp/rsa/ocsp-responder.key deleted file mode 100644 index ab3001e7f..000000000 --- a/.evergreen/ocsp/rsa/ocsp-responder.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDiHYXGCSOK3gxl -EmNSLepoFJbvhfYxxaqAWEceiTQdRpN97YRr/ywPm0+932EsE6/gIjqVs8IOtsiF -KK1lQ9sL/9f+ckS5gj9AR+Cta+FLDRP5plE+aao5no0kA8qMx2HHd47XFnuxKtUz -tRmgmTBNYbYhPdY1kjBSRyuXXBn1V6TRaYhk6dsK56Zvhgo6Y3YqpjpldePa4E0X -pUlBhY020QXtK3iWFauEYKcKR2JI2oVjY0tR60zf3GHkMLCe7SdbofCdwkBHcCct -LSp4xYb44JGbJX1npM1mhxR4pnp80tbEXNvXQ4S3kmd7/QFUYE4IdXVkXNhkK6Pt -IdDKbLa9AgMBAAECggEBAMMYOe4OwI323LbwUKX9W/0Flt1/tlZneJ9Yi7R7KW4B -EQ1cPB96gafNl9X5wLvpGJzIq8ey28MaTpUl7cYr7/nAe7rdGRL+oFh0LBU1uaOp -2wxSRlMVlHw2owzqAH/LIECclbBbg8nvbRk6Lqx0wEpj/mNcGVELm4nCQohMPVGC -9/8GZ63r+tS35jry9SBG0X4R5jYKsNzgNgcjR+lgMv/2FfpuZDryk9TWIP9ApQoc -7/DpTfC6P34f/ermfo4f2GEmRJsTACphA0kkpQX/n88r35cUSGeO5M9jYICUeCFw -IK4L6KNQcTRVOknFYeVJembVrj0RYKtWT+oU84a4XPkCgYEA+k7fcXhU2K+NX8RN -7HUPbxBE/TfLTNHdLTuWCUI77j+J3LUPNQ4BUyue+pUaFxI7Huc6x1zvvD27EqJ8 -0ge5MkFNflTUdUotuU/FKg7GKOU7rfdEvthzU2MbAZrHc0SeF+9/YrpvWZ+ZMKQ5 -IBQhiloFLsVGpGFzzF/MjpFdYo8CgYEA50HQxDDmfzmvNnURRZZV0lQ203m9I4KF -DbL2x59q0DaJkUpFr3uyvghAoz4y/OD5vNIwbzHWbmDQEA06v7iFoJ6BcJFG1syc -7A7KTB3PNQK4+ASG6pC3tYJ78mWtJwK130hFpuVkS/VPhQZJ/21EcWj9V153SZpA -RUqv/L+lx/MCgYEAs7E7p3IDNyuQClgauM2wrsK3RDFxuUxPw9Eq/KqX64mhptg0 -epn7SYHfN3Uirb1gw+arw8NsN275hX8wrHbu9Kz8vNyZSTpfaNFjcbX5fBJUrab9 -qyQoZoyXLqe214FDHVvJz06X8Xcpukmq2OSaz3+giNsGw6tSPj3n09F3gPECgYBI -1NGK+FufdetYm0X1RIOC2kLqF00aAeElj1dpRyu8p3Br8ZhAzBRfBPpWbyBfw/rj -HM9kNa3y1Uqxw3jdKJ/tFf5uFVLaE1bYgU/06O55I4Jdmg9jkHBLGe0vShZeUtw0 -le5ZwaT0xy1kF7b2WtNTZF1lRrsK0ymqqPsD/teXQQKBgBTyYVxPEHKr86kEQqL5 -/OKByVpqAxA7LQ1lTLNV9lXMRawp848flv/Uc8pj43MitAIiYazfXkpeeC6gGntJ -kkRT9jraOzy51tVAIh2KXm3l6KY/gnYTO3UXrxZOZU4IA7OttP3BG7xKq/9HP+kV -5P1bAkqo+n3XNxKoSSeJteCd ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/rsa/server-mustStaple-singleEndpoint.pem b/.evergreen/ocsp/rsa/server-mustStaple-singleEndpoint.pem deleted file mode 100644 index 47112c02b..000000000 --- a/.evergreen/ocsp/rsa/server-mustStaple-singleEndpoint.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEFzCCAv+gAwIBAgIETUEXPjANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV -UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO -BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVs -IFRlc3QgQ0EwHhcNMjAwNDIxMTkxNDA3WhcNNDAwNDIzMTkxNDA3WjBiMRAwDgYD -VQQKDAdNb25nb0RCMQ8wDQYDVQQLDAZLZXJuZWwxEjAQBgNVBAMMCWxvY2FsaG9z -dDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMQ8wDQYDVQQHDAZPQ1NQLTEwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDEWuLtsdzYxDK//wc9VXyyQPlS -AmkRHrLrTH0OSSPBvXK0NSkHtOjh3gX4jzTN8jTpEVkbfYt1EInZnucWOcX7mRRP -LRp0Fcq7j1pCPJ15uNSZDqDnfEA8kiY2Qg9n9oAIR2yk3FFj/8raBB13EnzOHeq4 -27BXH7oOgOgvd8PyuOB1OmNKjCLf5laaRbB+/lyrGfPFwmNcgH2lxtkfeBhTM5kS -vDkbAFIX6KqeWtvaV+WRPcyooa0FvNXTfCiS26qtw4rMZnWNODG13pgJCPckDZt7 -kX9qM+cS4L4oj6Hm3NrWkTpJzOFOQwZMily0X6ee1IH9m0yaLS7vq3pKlr67AgMB -AAGjgcIwgb8wCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB -BQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBQX4EjmQUUFCdz2ZKGKMHEPkkGHCDA4 -BggrBgEFBQcBAQQsMCowKAYIKwYBBQUHMAGGHGh0dHA6Ly9sb2NhbGhvc3Q6ODEw -MC9zdGF0dXMwEQYIKwYBBQUHARgEBTADAgEFMBoGA1UdEQQTMBGCCWxvY2FsaG9z -dIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAkZd/uV401ejVjqMaQ5ogkdo97Isz -Rjrx6dDY1Ll+5LzViqdRlXiAAc/bUq8NhYQkbjUC7b931meksIRRdtJUZx9zLt43 -npjjGKDdWEilLKKwT1IvKaAb2A7hmrT4WkwDtHZODvvpE+wvmEQ2LwthHDs+FwqN -2YDTuxdhO8mMePDXfK0Ch4WJQaJV/PT0sI34sYoeF7KC0TACWKwG08+qI9vawujq -qWw5fRwNTqxAj9X66wp6RdE6bJ3mWOrPmUppaDww3yRGVxdsWKCC8WoH3etNl8Km -iwDcp+WF+DmoOt2VAcvzoQsvsoUGdaMHYQ1MTJb5YsURr3BuGmcEUQI/yA== ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDEWuLtsdzYxDK/ -/wc9VXyyQPlSAmkRHrLrTH0OSSPBvXK0NSkHtOjh3gX4jzTN8jTpEVkbfYt1EInZ -nucWOcX7mRRPLRp0Fcq7j1pCPJ15uNSZDqDnfEA8kiY2Qg9n9oAIR2yk3FFj/8ra -BB13EnzOHeq427BXH7oOgOgvd8PyuOB1OmNKjCLf5laaRbB+/lyrGfPFwmNcgH2l -xtkfeBhTM5kSvDkbAFIX6KqeWtvaV+WRPcyooa0FvNXTfCiS26qtw4rMZnWNODG1 -3pgJCPckDZt7kX9qM+cS4L4oj6Hm3NrWkTpJzOFOQwZMily0X6ee1IH9m0yaLS7v -q3pKlr67AgMBAAECggEBAJqjLUafJdt9IK6+TVhLZAoKS4//n/lAoQ3YTkCa71Mc -PSKZHzgXjLSdIzyuo5px3qOS6wdQZy0JmlbN4xZI55gO5cS5M7UqmGAANMgnbqm3 -G49yytujqf9J5lgizHlG02wxu+lWLa9AeuQaC46D+9BkFUACnCzxKplTgggoHSSg -fTm/AKPRg/ZxejoorqveHK3IGjwVxk/2b6aqcCsr0GCuR5ons7hCQ66clvAqR/AH -ejz77lM/Nn6jq29Dgq/KhX22uabjML1yHFxZW0gF58chpJWTP8Rn3FEDw2mgMdao -C5C9Im9WWHquy05GQZRP/V5bhPuAgg5E4X+nCyn4eTECgYEA7eXTp+zLsGYe6l4a -MvXohDKMCouDF8w40hyIvJ9lF5ikQEhnJRQLPzbM7qx1AeeQwZevtyNBchX0nVwJ -VRd9c5qsSFkar489vBvhjEJ4B57B7KNoE5BHaR0+tfzWsWwK6BxHl9PmeGSn59i/ -7UwBhIzaC6dyJpTowCu/Scv8LhMCgYEA00vR/qeC0L7YPSG+VjHwerFhzUCTfnbd -wFpJM+N6PMRZA4GRWQLLxGmzPohjSfzwWMgUCXjopWiWOnxTEUlvTQgCWLAceMlk -rbTnHBtlXPPpSHvlmgVEG0+U/CpqONY7upEYrbt1xPMxNponS/7Yl0BXB2Qx8Je4 -pXs2H7wTIbkCgYEAgFOxUKwTVBxCIPqR91tfCbCaijWniXbIT87Ek7sHtSrJr0Nf -IEknp/nPog+1LknTdBp21rtV2kytnxS+lAAP1ARjWsN1+a2zB32itR5F0RZ6VUPw -KF1zp+f2pAS3aw109LAMjoHnmJnzWMU7Aq41Q2MXW6H/mYBJ7R+sGArJBbECgYBY -Y1Qx+bLATcU5NV9gwT0+pesqqEPK2ECFEX+jxBnDR8OQsuexW3kP7cN8eiNGtRd5 -nCC9oaV4ZBrL1mwNRDHaAGqy3ODcKisCezVeTZuGWcYRezqdxmwqHI1POxL6Oav8 -rGutaUinna/Njoi3wqCqDNEbF2/InD8ygisu9UbviQKBgQCS4Mxw+uOl5WvXjrze -z6B8L69MkT4fidgGkqimfpjk+N5Yt49otfvlvFPjkrVqR3lMqrqjV2r0W4fTeoSo -SDE3vZFZC9mi6ptUbgrW+aYqLHYQGYsJQXmj48Nkm/9uhkN1YEE9o04uSau1yVg+ -fDqxV7pLZwfnUbvGnYGjBShkMQ== ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/rsa/server-mustStaple.pem b/.evergreen/ocsp/rsa/server-mustStaple.pem deleted file mode 100644 index 5c80602e4..000000000 --- a/.evergreen/ocsp/rsa/server-mustStaple.pem +++ /dev/null @@ -1,53 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIERjCCAy6gAwIBAgIEJ++lZzANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV -UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO -BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVs -IFRlc3QgQ0EwHhcNMjAwMzE5MTU1NjIyWhcNNDAwMzIxMTU1NjIyWjBiMRAwDgYD -VQQKDAdNb25nb0RCMQ8wDQYDVQQLDAZLZXJuZWwxEjAQBgNVBAMMCWxvY2FsaG9z -dDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMQ8wDQYDVQQHDAZPQ1NQLTEwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDykV3fTFJgaqjfAgbAC7TGPk9V -VVsRYRgLF8Zjh9GDRU/TQ6pGZG7qo64D11oQurW0WT2Zv/lqhXW4mWNFv8+qoS5L -9z2Dtmxr8CZbb6YftA0e22KPUuDCQ5nYhOY21A6SYFwqEZ6ZsrZAMkgfhx+TY1kZ -0jZM/jgkvRtpG9I8BbddHyF8eFATCJ41DnLOzjfNukd5zKSIdVxY6r+ZBOr29kii -dcNHkCAck7+WXl9/KSqH7jF5asU0S3x/68G2R/qdKAxki9b2fe70N3XGZE0P2WHi -lq2aJeE0eqjAv+hBGiEb4iJl0s8iheardrHFeL4EMbiiVfVdVCHKkp58wjB9AgMB -AAGjgfEwge4wCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB -BQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTOLiS9HKGWpiVKx81nNuRlK+HAITBn -BggrBgEFBQcBAQRbMFkwLQYIKwYBBQUHMAGGIWh0dHA6Ly9sb2NhbGhvc3Q6OTAw -MS9wb3dlci9sZXZlbDAoBggrBgEFBQcwAYYcaHR0cDovL2xvY2FsaG9zdDo4MTAw -L3N0YXR1czARBggrBgEFBQcBGAQFMAMCAQUwGgYDVR0RBBMwEYIJbG9jYWxob3N0 -hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCg3NfTO8eCdhtmdDVF5WwP4/lXMYJY -5wn7PhYKMyUQI3rjUpQRIQwCVerHAJAiiflpgefxB8PD5spHPFq6RqAvH9SKpP5x -nyhiRdo51BmijCIl7VNdqyM5ZgDAN2fm2m56mDxpo9xqeTWg83YK8YY1xvBHl3jl -vQC+bBJzhaTp6SYXMc/70qIPcln0IElbuLN8vL4aG6xULkivtjiv7qBSZrNrBMSf -QJan9En4wcNGFt5ozrgJthZHTTX9pXOGVZe4LXbPCQSrBxZiBD9bITUyhtbeYhYR -4yfXjr7IeuoX+0g6+EEtxqrbWfIkJ3D7UaxAorZEsCt18GC7fap9/fzv ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDykV3fTFJgaqjf -AgbAC7TGPk9VVVsRYRgLF8Zjh9GDRU/TQ6pGZG7qo64D11oQurW0WT2Zv/lqhXW4 -mWNFv8+qoS5L9z2Dtmxr8CZbb6YftA0e22KPUuDCQ5nYhOY21A6SYFwqEZ6ZsrZA -Mkgfhx+TY1kZ0jZM/jgkvRtpG9I8BbddHyF8eFATCJ41DnLOzjfNukd5zKSIdVxY -6r+ZBOr29kiidcNHkCAck7+WXl9/KSqH7jF5asU0S3x/68G2R/qdKAxki9b2fe70 -N3XGZE0P2WHilq2aJeE0eqjAv+hBGiEb4iJl0s8iheardrHFeL4EMbiiVfVdVCHK -kp58wjB9AgMBAAECggEATA91Bf3insUTKspx32pMRxVmvvVC1xJA/cl4teDyu1zS -iQZgsC3x8bVdbWrrnO9O5rxM6pcd2F786OOAE3Dv5ysfX0apjVF4cegdvvIlfy9w -JcrY/uQYAhI8fX4+ydZ4s0Fv5OkdeEhniX26y9gM+KRgXg5iZIYaiLqbi7vjkloE -NBIDWGj8PCNKUVc2PSbZFVMMTc+7qZeUR0WRKr9CsaXBiEkWKfuw4MH1YUL0HJOs -uLd/oYg0l0eHPluUkKQW+KVq1GKsmr2sSc8NOcGtVTsUygSgX4hw36V7Vw3MfQRv -sNIgKp3RDEyynoXRoG3laHrib1GdYwDKRsHB2znKQQKBgQD+NAOOqoEx0lmlg/Wf -sNImv+3da0owE1TqTMHBWXriGo+DwqT+d9S+M5x3JMpmgH9vTEDlLOM2+qF8M3B3 -TLlu1k7F8D1G7YCdIZwMLUNCekCSHsqQcU9HMHlQqXd2cxFqWbyATk9tvJzj7xC9 -zMhaKGKvIS/EF0Ld8kIvrINmGQKBgQD0SExjk4yshv/DvWknxfJr8OupgQrriLHA -Hrk+n84Iv/4vzupgKsXJQE6VN0xM6e/ANhGATuxiaA3UE4p6K9wJNryHrw/wdnyf -I9AR0Cea9F4pa26BBCdLtQuyRqgl7dBZA1n3il7vKX6wB0MLoy/uYWYCedk4w+9d -acqh7S0CBQKBgBl8x5qHV/rR13E0AO2pAfkmp0fbGQ4m8g2n8olbWmnPNfKFEpv9 -EdScQiTkCHMskRpsr9kKniGGEajtU2pyw+jsDevkwZAaAho/I3FJHIRO06iS88Z1 -xfgiUReYVkUHFojuRGss7uPW1Hg6IRiWrsPzZqmejzZ/CpJMVvyGtIoJAoGAXmo7 -LBlxO5WJ8SuaIwc85T9etkrr35EbsnetfWjihzs9kVjV+YlOnLRAKygOU4PvaEj9 -hqv6bSZugdNzqDifeOgxAfhFntkM3a1H1DqxtBBS/ItLUI48aeR1utfYUaCS8HR9 -J1HR03okPwDvhuXxtp7qgHZ74JbKQz6KVP+Ib8kCgYEA7w0NnuOoZ0la17XuyQzA -UeTZZavgm0tNqqT4JcPiUV9zkR8WJsFQE704KQ8BjDyeYMWwe8EpfJaqsGqdJKGo -RnnxwNuwT4uSNb78MxXXVRG0fN/2iu70lNySKOl/DmA8siRc/weQj5JPsGbyZkjZ -IsaTqaZQUdtbZ7vRukyPo8Q= ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/rsa/server-singleEndpoint.pem b/.evergreen/ocsp/rsa/server-singleEndpoint.pem deleted file mode 100644 index 66849f535..000000000 --- a/.evergreen/ocsp/rsa/server-singleEndpoint.pem +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEBDCCAuygAwIBAgIEZ0Q/vTANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV -UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO -BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVs -IFRlc3QgQ0EwHhcNMjAwNDEwMjA1NDEzWhcNNDAwNDEyMjA1NDEzWjBiMRAwDgYD -VQQKDAdNb25nb0RCMQ8wDQYDVQQLDAZLZXJuZWwxEjAQBgNVBAMMCWxvY2FsaG9z -dDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMQ8wDQYDVQQHDAZPQ1NQLTEwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDH3pCRRVSRghw0+55xvfRRWQx/ -5BO/M7XGtiLwAUU+R42FyQYdu8rgZQavtDLU/KQaws6xoIBvl0YezBGRTbEa4pM/ -ATeGSTz9Xdo5Zp9oQgb41yimdjVCxTrdMUAtocHi5UurkmuBJcyZ6UHLvQ11whgL -tZfGFO3drhLm8A/mDFr4o+9LX4q+9qh+cDFEWnTx5j16ZN2pWNR8lFF5pu/wsqPL -CJEC/dq95EuwQJoupjF+bC/faGx+b1/CLx2HyCR0pDSvNq9AlK9W0qw5br01qIhT -+mvv6+nPs8zzdixrguNlzHsEZN/pPnFZ3xBZii88F4xfxfPoPE+rfPO5M6DfAgMB -AAGjga8wgawwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB -BQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRWLvOTPv7G74DYdxd+Lv/7DzLabDA4 -BggrBgEFBQcBAQQsMCowKAYIKwYBBQUHMAGGHGh0dHA6Ly9sb2NhbGhvc3Q6ODEw -MC9zdGF0dXMwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEB -CwUAA4IBAQAJvPmDizmNplKTdBu4YsF2E7EsAfJREuN7TKAbLsiYtyAMuIu5BIv6 -Ma4pcxeJUvYML5czHoPwjXNC9+M7aTsb18q8ZRAJzY2kVhvzhT2lVH5YFC8vhJ92 -aeX8GTpoa+lslXuvVe8os+tGcQzqMtiVF2xZHbAYOiAno+fVQey9VSjU+pXIcKUT -7nF/b0rRHHo8ziPsfI+h3kKWttywB+iQ60Zlt3ajlfWgTuL1fdbt9GEFl68Rhhsy -6s1h8oXSSM0VIBzJKqrubJgziXH2kVN9p1XtQcCwW2lrZ3z0GQq5nLvIsgTQHwx6 -FsuONP/eS2esZIn7LwT2nSNa/Hfh9pq/ ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDH3pCRRVSRghw0 -+55xvfRRWQx/5BO/M7XGtiLwAUU+R42FyQYdu8rgZQavtDLU/KQaws6xoIBvl0Ye -zBGRTbEa4pM/ATeGSTz9Xdo5Zp9oQgb41yimdjVCxTrdMUAtocHi5UurkmuBJcyZ -6UHLvQ11whgLtZfGFO3drhLm8A/mDFr4o+9LX4q+9qh+cDFEWnTx5j16ZN2pWNR8 -lFF5pu/wsqPLCJEC/dq95EuwQJoupjF+bC/faGx+b1/CLx2HyCR0pDSvNq9AlK9W -0qw5br01qIhT+mvv6+nPs8zzdixrguNlzHsEZN/pPnFZ3xBZii88F4xfxfPoPE+r -fPO5M6DfAgMBAAECggEAR6QUR64FMR7lA2zJj1WaNGpp25GiLl/XoUF55nNeIYO+ -S50Rryi4AJTVv7cknUltfRYkxnCUeOtNPA7DoUSq3csnImdKQr0PunWgmgCZ1OIN -47YjoP8v+h3+Cnjz2ydm+vBbnkUeea1V2DlO1zuNjo8i1Vei7mJkHJift92GpVtY -DW5GrTZfntPJXHQQjz6nGn5mQxTlEi1WafPPyDoqykwAIonehIyhYd7UCDw/e62D -XMWk8Bo7YmX0Y3utQF2tuu1ih2zz5+NXwviycBqE9GL4eoHZgdKJrPBA/nRBsy5J -SqorCKxLODvl77EIdqPUDZsyGzvWlmoEyDtthsvsiQKBgQDtFaIQW+DizGqibfuT -6/z5+4G8ZAp+FVJU/0Z/SmOX/ro2LzYhlV0l71OWvMVKxCfp30zlYaYWNo+R8h40 -O6zSsKcSE7JLFNh53euPz49Ium+N5OFZ6Yez7HBD/5sjWEt+iGDUZAr9SlqMZAGN -PhUSu51QvFj1kqqVbXxmA3TkiwKBgQDX0NOCri8J4jqBk8TuY4AZS3zq/2lVmFYh -81itma43zXRG3z8hFFct/CBqxObGwi1MQAGZAG7EeOvnH6FPV/85Tej1Wd0VtV8f -ryWgjSvZDt3dATZBKVcVibTfazdkfeqze2wtYRjFqNPlAitSF7HN8iOC8B02dIMq -ec6UM9w7fQKBgA03CHqK9IUPyd3V7ZD4NXilqTycAu22OImeVQqhVd3SCAUfKpBC -qBeGOI2NZh3dwy/JD5s1jzFrxyLmcQKOVPrFd/qM+IIw3kQkt42jjyQJqFArctg1 -KShBRJy1sasNr9+UsHkGPoqRy2xJ4sBBtqD9ri4i4X6Gt1Vu7eEtziUzAoGAafd0 -Uz8Zg53cIlGfKXobpM/m9zAP1WJmMGdfDGZgH7A2vrHROnnVUJPyitpBgihHu5/V -6P1IZhoFosdqGh5YCBgUIZxNLOKQYWtLa2jFtd9R2rlEnXwh8UZbVDQ9z47wFc6t -UB7T3gHGgTSudrGBsWCKRTmG7n0JBmsmnqhUI7UCgYBb4nBED+kaMGFdzRdpq8Dv -+KgShSjWa+4U2S4QZ3MYtb+rIMsAoRO4K8S3VMqIsun3S7T5szyp72jBqAQTHiHA -eGlRTrirc9dR8x6CO66UUf5tGMG05P7qo23Qoip+t1/rcCgrBH7er68AhMIZbxfK -2dj9RqANXyIWWI320Y+VkA== ------END PRIVATE KEY----- diff --git a/.evergreen/ocsp/rsa/server.pem b/.evergreen/ocsp/rsa/server.pem deleted file mode 100644 index abf978ef8..000000000 --- a/.evergreen/ocsp/rsa/server.pem +++ /dev/null @@ -1,53 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEMzCCAxugAwIBAgIET11AjzANBgkqhkiG9w0BAQsFADB0MQswCQYDVQQGEwJV -UzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3JrIENpdHkxEDAO -BgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEXMBUGA1UEAwwOS2VybmVs -IFRlc3QgQ0EwHhcNMjAwMzE5MTU1NjI1WhcNNDAwMzIxMTU1NjI1WjBiMRAwDgYD -VQQKDAdNb25nb0RCMQ8wDQYDVQQLDAZLZXJuZWwxEjAQBgNVBAMMCWxvY2FsaG9z -dDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMQ8wDQYDVQQHDAZPQ1NQLTEwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC0usokl3+yXDtLeYquHQyAkXIw -lY4tukv8nEgLtUlK0kt9Q7P8EdZigzFUeJtL7piFCTIaLuv6e4UqLLDXbIANxD/J -NXXQPtBasOSzdgZ2ToUj5ANPv0QegsFubpYGq5LXsMdKTRE8uTB91PJBvRzxY2Nx -O1kdQcIrYpSYXqKsNgq/8iAPrmAdZ3y+S7OBuNyvlQJZqWoB1Y0ZWuR1QrcLMgdm -q2SdBzZT/3P+r/dbHMKdDZ5JdJ9Nm4ylOG7mhZkfb38JfdvWedzXDMu6TzS2W67o -yM90Cj9Lt+UyHLJ2jlcsZSZp4km6Oj5RBNVhd95SFckvPJxLzSyFlpjOIXsNAgMB -AAGjgd4wgdswCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB -BQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBTe7IMKaO1aQILcpoj5wLFgIRuPHzBn -BggrBgEFBQcBAQRbMFkwLQYIKwYBBQUHMAGGIWh0dHA6Ly9sb2NhbGhvc3Q6OTAw -MS9wb3dlci9sZXZlbDAoBggrBgEFBQcwAYYcaHR0cDovL2xvY2FsaG9zdDo4MTAw -L3N0YXR1czAaBgNVHREEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJKoZIhvcNAQEL -BQADggEBAFMVds6y5Qy7DlFsca0u8WE+ckoONa427bWBqNx8b/Hwaj3N3C58XQx/ -EZRNt9XVy/LoEHr+NmOWsCl69fINeVpx8Ftot8XPbFG9YxL/xbJ3lvWesPR6bwpm -PZqGiwfl1VrZvuobXADz0Rfru7B7LPkurpSxDiNBf/9JuLPYe9ffZwdFWQoehw07 -b9FKVaJ7mSHno/5f4Z/uKau91sL0kiKKG9Lo2JEIEmpp8HJ3OKCFh7DFkeDlRCDl -WyYxF4g/PfvJQm2Hd89cu8m3RX84rLa9jn1RGL/8bmxE0dxk4Di/t9gl5KGWIH9Q -LBeVRSQmH9GbI/WmldMLkGkvARYYTp8= ------END CERTIFICATE----- ------BEGIN PRIVATE KEY----- -MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC0usokl3+yXDtL -eYquHQyAkXIwlY4tukv8nEgLtUlK0kt9Q7P8EdZigzFUeJtL7piFCTIaLuv6e4Uq -LLDXbIANxD/JNXXQPtBasOSzdgZ2ToUj5ANPv0QegsFubpYGq5LXsMdKTRE8uTB9 -1PJBvRzxY2NxO1kdQcIrYpSYXqKsNgq/8iAPrmAdZ3y+S7OBuNyvlQJZqWoB1Y0Z -WuR1QrcLMgdmq2SdBzZT/3P+r/dbHMKdDZ5JdJ9Nm4ylOG7mhZkfb38JfdvWedzX -DMu6TzS2W67oyM90Cj9Lt+UyHLJ2jlcsZSZp4km6Oj5RBNVhd95SFckvPJxLzSyF -lpjOIXsNAgMBAAECggEAIjNe4YHR5nzRs7yyY7SXkxTzGQKUP08L5ifk8mJCFmip -ZHEVdFQjz8yn3yZbrQjfz/0ngBD1Exeg4ZRHetzLds92iqsVOm1InIDxJozlOCov -w9T4U3UMfQGdfTpsJaL+TNblP8hJxMX+yTEtDwesnHmEbf8fJAw3pGIpYJQ4EIJv -1uPzyB8EsrTjj23a5NPF/FGdzzO+HP5fhNNIUmP83pqonXLUSy0v5rsRFNxNMBn3 -SPRWq+Z779eLQXnRjW/6hKssSBFg6zAOi3Gc4oDbrDa2WEbZ0BEU+JW3XduN91bU -SsO3yQ+VL+CQn5wvXGIsc4EHH6wO8Bs0vXfD7zeLgQKBgQDrHOzPymI0p0PnxL2+ -8LrSU1x0WdedPZJugwwfUYMfn7sjKx+FyVLvM+7wuJ8zsMOAab2AHv3S0Nxkovhb -aa4lH9SUAHILcU+nb7M6E+mwSr65AemGspvGz4ZC6L52CGVzRfIcoBDD0T8OZGH0 -4IeiqOluqtvgCoW4UV1dyw0nPQKBgQDEyQwcim5ghEQ7V2eDefE5yxNlkNEnSVnG -DNubM8KURR8jehpDWkIlxQ4p2tLBWGB0YeOCG9NmwfLnQUStvSFE6/XjP5bBJlov -jT66T98NgFRfUeVkcCAiVT/LlDzXWXXPLyZSY+bxtn8UA1NYNu0pLCLDR9TlH1dK -FKwiomdgEQKBgEimcHqo4/23LeGBRsyooGH7hlchp+GbtBLYBbfrvSPZfL8aRSxX -EHx/xLa3peIYHeEhS4A6k15AUcn7HdlJZ5lrI4n0NUlZ4y4u8ufgXVavUg3jDGEl -8cLWP3uPZcMdRxP+qhi0UVng36Y32JkNhHv7y935h+XL+pQA+GPSKadVAoGAPPvp -SvcDmdmjo5hEthQWU8jBbBpjFv++WIgnjoON65E4QzBV70WLdlUJPKNZ6R1QVwD3 -Fp00+IVml5A8jnMsWkWd4B0WxSjzjgUByY9zGqYIf7nLk0LEUp+Es7xu1nYc8mY0 -RBg9u+7IlxUowQ/Uk4vgAhDCw3bhAE5Dwj/+NWECgYBWnBz5l+Uar9oT1ErRSbJW -RVYx3iIsqka1lox/zw5nme1Q/dv2uTQy6uZRn9U9ikqGwePuMEexQTR1csnMqeMM -4i3pDFpGBfTZXJAvH790ak6eZ0eBXqzJlTyEjll4r4zXHk+slm/tAgpIg0Ps3J9j -Sd+bTtG47gpb4sRbqEtQFQ== ------END PRIVATE KEY----- diff --git a/.evergreen/orchestration/configs/sharded_clusters/basic.json b/.evergreen/orchestration/configs/sharded_clusters/basic.json deleted file mode 100644 index fd03f5686..000000000 --- a/.evergreen/orchestration/configs/sharded_clusters/basic.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "id": "shard_cluster_1", - "shards": [ - { - "id": "sh01", - "shardParams": { - "members": [ - { - "procParams": { - "ipv6": true, - "bind_ip": "127.0.0.1,::1", - "shardsvr": true, - "port": 27217 - } - }, - { - "procParams": { - "ipv6": true, - "bind_ip": "127.0.0.1,::1", - "shardsvr": true, - "port": 27218 - } - }, - { - "procParams": { - "ipv6": true, - "bind_ip": "127.0.0.1,::1", - "shardsvr": true, - "port": 27219 - } - } - ] - } - } - ], - "routers": [ - { - "ipv6": true, - "bind_ip": "127.0.0.1,::1", - "port": 27017 - }, - { - "ipv6": true, - "bind_ip": "127.0.0.1,::1", - "port": 27018 - } - ] -} diff --git a/.evergreen/x509gen/82e9b7a6.0 b/.evergreen/x509gen/82e9b7a6.0 deleted file mode 100644 index 6ac86cfcc..000000000 --- a/.evergreen/x509gen/82e9b7a6.0 +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDfzCCAmegAwIBAgIDB1MGMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy -aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u -Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx -CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIwMjMxMVoXDTM5MDUyMjIwMjMxMVoweTEb -MBkGA1UEAxMSRHJpdmVycyBUZXN0aW5nIENBMRAwDgYDVQQLEwdEcml2ZXJzMRAw -DgYDVQQKEwdNb25nb0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREwDwYDVQQI -EwhOZXcgWW9yazELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQCl7VN+WsQfHlwapcOpTLZVoeMAl1LTbWTFuXSAavIyy0W1Ytky1UP/ -bxCSW0mSWwCgqoJ5aXbAvrNRp6ArWu3LsTQIEcD3pEdrFIVQhYzWUs9fXqPyI9k+ -QNNQ+MRFKeGteTPYwF2eVEtPzUHU5ws3+OKp1m6MCLkwAG3RBFUAfddUnLvGoZiT -pd8/eNabhgHvdrCw+tYFCWvSjz7SluEVievpQehrSEPKe8DxJq/IM3tSl3tdylzT -zeiKNO7c7LuQrgjAfrZl7n2SriHIlNmqiDR/kdd8+TxBuxjFlcf2WyHCO3lIcIgH -KXTlhUCg50KfHaxHu05Qw0x8869yIzqbAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8w -DQYJKoZIhvcNAQELBQADggEBAEHuhTL8KQZcKCTSJbYA9MgZj7U32arMGBbc1hiq -VBREwvdVz4+9tIyWMzN9R/YCKmUTnCq8z3wTlC8kBtxYn/l4Tj8nJYcgLJjQ0Fwe -gT564CmvkUat8uXPz6olOCdwkMpJ9Sj62i0mpgXJdBfxKQ6TZ9yGz6m3jannjZpN -LchB7xSAEWtqUgvNusq0dApJsf4n7jZ+oBZVaQw2+tzaMfaLqHgMwcu1FzA8UKCD -sxCgIsZUs8DdxaD418Ot6nPfheOTqe24n+TTa+Z6O0W0QtnofJBx7tmAo1aEc57i -77s89pfwIJetpIlhzNSMKurCAocFCJMJLAASJFuu6dyDvPo= ------END CERTIFICATE----- \ No newline at end of file diff --git a/.evergreen/x509gen/altname.pem b/.evergreen/x509gen/altname.pem deleted file mode 100644 index ff0fd61e6..000000000 --- a/.evergreen/x509gen/altname.pem +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAgyOELJgWP2akoidBdtchvdRF8gZrR8rwORDmST1tmH5aKiH2 -e/lkWf+pxmXnvmLXoOKk3HHGgyZ7v1sDDB0z7/rAimECqxnqJ90GFq8rGR60jCL/ -hs+30m0U9CNAvjzD5yFruaisPeCZuZXEA06QbTbTaSD4u/n7fgZVGSnj2m+DFel5 -S7dgL4Pa7Vh2nua8QPfczLLQI/uP9Ma5ZXjk2C2V+QBkmK64OanGY6yXn8+m5Lp1 -cKhhQUiXVVO1BgFHw65FapTrhG2zgyuaqvb5F062V+XGIwZhWhDz4cTgCx0dFKU+ -WQGXuEDDY3EzaOd6Ds3h6WkCRDs9cn2i0j4taQIDAQABAoIBAHeCTXkKXPQIia6Q -0dMIuWIy6k9nVCtIIWYQJZ3HUnJva6IL84IFxFNUcBczVV+m2lVvVsjjEwMAdjPs -MDnA/00LGp7BS9o8Mq2DeoH/vuoUlntDhdUIxcAJ0teurNjxraKcTX0T32xAnDeJ -6ekNlwdAuKeM+cDtTykJglH9X/324eOT8sEkpohkTJaszs3PEqgN9jrHttVatmft -KGT06aANBrEH61xr/nfBehd3R7WyVsIUmlihlIIBwbxyycdMSxHIiE1Qno252Ek7 -GJp/dPqO2pwIH47cop48SsZLFVosqaZs3jkEIDkQkyd7tvmVG69aFBPz5+PTvdRv -fufuvXUCgYEA1gTnvln9/PmC9mKFTDGdKLhFIypyOhKl1lUoDgcmCencjwu28yTA -+A2fKZQFupiHYvSg5kbvmr7FGVtKLNPJWocvr7jqPvrVLCzvs6l94LhGCTVyOmgn -e09xyDx3xQTuJmpg+4LD1jImL3OLO3fplbslwisip2CWzHZR6h3QRVMCgYEAnNy5 -F81xbimMVcubQve6LPzZq1pYaUM5ppkejNdBoEKR28+GP0NQ7YbN6iu2LXlbpWk/ -IrAyUmDUpnXFsiRDDWnPol6JzYTovzeZG+NCMJWkaQEOzm8BpUsC2UBvsX55ddxt -WM4CkLOxo7KXfQwYAMKc/H8tFE7DXloH82U7jtMCgYB+PuiBFc7IYlrJgjZFSuL8 -+S33X3uAHC3tL9Bv7fGXWXd8fhmOdfjKmiZwPVvfxUffrJQZInEGpE/Z9EreBJQ7 -LZGIo5iyS/5hj6RaI7oYTDssBXX7VCMuDx/8UQcJli3xRUEuO+XPvUdfKFZSXxrP -81SDpDRN7aEmvQj3BF0t9wKBgCgX5ptl4HtG1V7MhufMB+Md0ckRc42cKC0j8AIR -tu1udneXiHm9C/9aOGGFQLBI15rk1sVYAdS6eT/+1EQfLqBMDk0zGsfUE+VkIZdW -NAHVDcvlAFLVXrdP/+9ln+bfK85rQ+ux5Ef2Fg6ARGYq5Cu1koibPPt20krYejXF -Bz8PAoGBAKbCmptnjdu4QF+rGLfYyVnrtyUuRgN+Q0MCIag1dBTag6rC17xDYJ6g -3Txzzb9xAZ35pSHroB7TSr32vRUQVrAcfldW4mousr9A0pDoc/E2axtE1YmzSYwk -jqgu3PeWrtwBthUEoRXbQAed97bKW+gUU677u9IFRCS2YIfwDV5R ------END RSA PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDnTCCAoWgAwIBAgIDCRkUMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy -aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u -Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx -CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIyMzQzNloXDTM5MDUyMjIyMzQzNlowcDES -MBAGA1UEAxMJbG9jYWxob3N0MRAwDgYDVQQLEwdEcml2ZXJzMRAwDgYDVQQKEwdN -b25nb0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREwDwYDVQQIEwhOZXcgWW9y -azELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCD -I4QsmBY/ZqSiJ0F21yG91EXyBmtHyvA5EOZJPW2YfloqIfZ7+WRZ/6nGZee+Yteg -4qTcccaDJnu/WwMMHTPv+sCKYQKrGeon3QYWrysZHrSMIv+Gz7fSbRT0I0C+PMPn -IWu5qKw94Jm5lcQDTpBtNtNpIPi7+ft+BlUZKePab4MV6XlLt2Avg9rtWHae5rxA -99zMstAj+4/0xrlleOTYLZX5AGSYrrg5qcZjrJefz6bkunVwqGFBSJdVU7UGAUfD -rkVqlOuEbbODK5qq9vkXTrZX5cYjBmFaEPPhxOALHR0UpT5ZAZe4QMNjcTNo53oO -zeHpaQJEOz1yfaLSPi1pAgMBAAGjNzA1MDMGA1UdEQQsMCqCCWxvY2FsaG9zdIcE -fwAAAYIXYWx0ZXJuYXRpdmUubW9uZ29kYi5jb20wDQYJKoZIhvcNAQELBQADggEB -AADOro10g1QReF0QVX2w+yVwCWy8FUzuksX0RI0RCFRJPo79SH7o2IZFGbLlBL8K -MMsgSrzRW/HcyE91fv0R2b7kvqfD3Eo1W1ocufjVg+3e4uuwm9k9SLjSI6mE4hEf -H6BeFoZhUdbrq9l/ez+NK+3ToHAl1bGLkipfnB522gRO1CjkpiY2knaaNQtjd/a9 -7QXqUs+KMJx42yqjBbVE6MdA2ypNMMIc8AgI5kRKEBGHpS4Z6VNZN4Pus1atGlRW -OwkjHK5pnT1TAKSODjfFw5VlXGztGTPKuJhM2/X7Qi0bO8b7NmH7cjDBATmZF5O8 -FAxIQ8+3qUPMXYkb1ipLOdQ= ------END CERTIFICATE----- diff --git a/.evergreen/x509gen/ca.pem b/.evergreen/x509gen/ca.pem deleted file mode 100644 index 6ac86cfcc..000000000 --- a/.evergreen/x509gen/ca.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDfzCCAmegAwIBAgIDB1MGMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy -aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u -Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx -CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIwMjMxMVoXDTM5MDUyMjIwMjMxMVoweTEb -MBkGA1UEAxMSRHJpdmVycyBUZXN0aW5nIENBMRAwDgYDVQQLEwdEcml2ZXJzMRAw -DgYDVQQKEwdNb25nb0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREwDwYDVQQI -EwhOZXcgWW9yazELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw -ggEKAoIBAQCl7VN+WsQfHlwapcOpTLZVoeMAl1LTbWTFuXSAavIyy0W1Ytky1UP/ -bxCSW0mSWwCgqoJ5aXbAvrNRp6ArWu3LsTQIEcD3pEdrFIVQhYzWUs9fXqPyI9k+ -QNNQ+MRFKeGteTPYwF2eVEtPzUHU5ws3+OKp1m6MCLkwAG3RBFUAfddUnLvGoZiT -pd8/eNabhgHvdrCw+tYFCWvSjz7SluEVievpQehrSEPKe8DxJq/IM3tSl3tdylzT -zeiKNO7c7LuQrgjAfrZl7n2SriHIlNmqiDR/kdd8+TxBuxjFlcf2WyHCO3lIcIgH -KXTlhUCg50KfHaxHu05Qw0x8869yIzqbAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8w -DQYJKoZIhvcNAQELBQADggEBAEHuhTL8KQZcKCTSJbYA9MgZj7U32arMGBbc1hiq -VBREwvdVz4+9tIyWMzN9R/YCKmUTnCq8z3wTlC8kBtxYn/l4Tj8nJYcgLJjQ0Fwe -gT564CmvkUat8uXPz6olOCdwkMpJ9Sj62i0mpgXJdBfxKQ6TZ9yGz6m3jannjZpN -LchB7xSAEWtqUgvNusq0dApJsf4n7jZ+oBZVaQw2+tzaMfaLqHgMwcu1FzA8UKCD -sxCgIsZUs8DdxaD418Ot6nPfheOTqe24n+TTa+Z6O0W0QtnofJBx7tmAo1aEc57i -77s89pfwIJetpIlhzNSMKurCAocFCJMJLAASJFuu6dyDvPo= ------END CERTIFICATE----- \ No newline at end of file diff --git a/.evergreen/x509gen/client-private.pem b/.evergreen/x509gen/client-private.pem deleted file mode 100644 index 551a43a75..000000000 --- a/.evergreen/x509gen/client-private.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAsNS8UEuin7/K29jXfIOLpIoh1jEyWVqxiie2Onx7uJJKcoKo -khA3XeUnVN0k6X5MwYWcN52xcns7LYtyt06nRpTG2/emoV44w9uKTuHsvUbiOwSV -m/ToKQQ4FUFZoqorXH+ZmJuIpJNfoW+3CkE1vEDCIecIq6BNg5ySsPtvSuSJHGjp -mc7/5ZUDvFE2aJ8QbJU3Ws0HXiEb6ymi048LlzEL2VKX3w6mqqh+7dcZGAy7qYk2 -5FZ9ktKvCeQau7mTyU1hsPrKFiKtMN8Q2ZAItX13asw5/IeSTq2LgLFHlbj5Kpq4 -GmLdNCshzH5X7Ew3IYM8EHmsX8dmD6mhv7vpVwIDAQABAoIBABOdpb4qhcG+3twA -c/cGCKmaASLnljQ/UU6IFTjrsjXJVKTbRaPeVKX/05sgZQXZ0t3s2mV5AsQ2U1w8 -Cd+3w+qaemzQThW8hAOGCROzEDX29QWi/o2sX0ydgTMqaq0Wv3SlWv6I0mGfT45y -/BURIsrdTCvCmz2erLqa1dL4MWJXRFjT9UTs5twlecIOM2IHKoGGagFhymRK4kDe -wTRC9fpfoAgyfus3pCO/wi/F8yKGPDEwY+zgkhrJQ+kSeki7oKdGD1H540vB8gRt -EIqssE0Y6rEYf97WssQlxJgvoJBDSftOijS6mwvoasDUwfFqyyPiirawXWWhHXkc -DjIi/XECgYEA5xfjilw9YyM2UGQNESbNNunPcj7gDZbN347xJwmYmi9AUdPLt9xN -3XaMqqR22k1DUOxC/5hH0uiXir7mDfqmC+XS/ic/VOsa3CDWejkEnyGLiwSHY502 -wD/xWgHwUiGVAG9HY64vnDGm6L3KGXA2oqxanL4V0+0+Ht49pZ16i8sCgYEAw+Ox -CHGtpkzjCP/z8xr+1VTSdpc/4CP2HONnYopcn48KfQnf7Nale69/1kZpypJlvQSG -eeA3jMGigNJEkb8/kaVoRLCisXcwLc0XIfCTeiK6FS0Ka30D/84Qm8UsHxRdpGkM -kYITAa2r64tgRL8as4/ukeXBKE+oOhX43LeEfyUCgYBkf7IX2Ndlhsm3GlvIarxy -NipeP9PGdR/hKlPbq0OvQf9R1q7QrcE7H7Q6/b0mYNV2mtjkOQB7S2WkFDMOP0P5 -BqDEoKLdNkV/F9TOYH+PCNKbyYNrodJOt0Ap6Y/u1+Xpw3sjcXwJDFrO+sKqX2+T -PStG4S+y84jBedsLbDoAEwKBgQCTz7/KC11o2yOFqv09N+WKvBKDgeWlD/2qFr3w -UU9K5viXGVhqshz0k5z25vL09Drowf1nAZVpFMO2SPOMtq8VC6b+Dfr1xmYIaXVH -Gu1tf77CM9Zk/VSDNc66e7GrUgbHBK2DLo+A+Ld9aRIfTcSsMbNnS+LQtCrQibvb -cG7+MQKBgQCY11oMT2dUekoZEyW4no7W5D74lR8ztMjp/fWWTDo/AZGPBY6cZoZF -IICrzYtDT/5BzB0Jh1f4O9ZQkm5+OvlFbmoZoSbMzHL3oJCBOY5K0/kdGXL46WWh -IRJSYakNU6VIS7SjDpKgm9D8befQqZeoSggSjIIULIiAtYgS80vmGA== ------END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/.evergreen/x509gen/client-public.pem b/.evergreen/x509gen/client-public.pem deleted file mode 100644 index 53e4e034f..000000000 --- a/.evergreen/x509gen/client-public.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDgzCCAmugAwIBAgIDAxOUMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy -aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u -Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx -CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIzNTU1NFoXDTM5MDUyMjIzNTU1NFowaTEP -MA0GA1UEAxMGY2xpZW50MRAwDgYDVQQLEwdEcml2ZXJzMQwwCgYDVQQKEwNNREIx -FjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYD -VQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALDUvFBLop+/ -ytvY13yDi6SKIdYxMllasYontjp8e7iSSnKCqJIQN13lJ1TdJOl+TMGFnDedsXJ7 -Oy2LcrdOp0aUxtv3pqFeOMPbik7h7L1G4jsElZv06CkEOBVBWaKqK1x/mZibiKST -X6FvtwpBNbxAwiHnCKugTYOckrD7b0rkiRxo6ZnO/+WVA7xRNmifEGyVN1rNB14h -G+spotOPC5cxC9lSl98Opqqofu3XGRgMu6mJNuRWfZLSrwnkGru5k8lNYbD6yhYi -rTDfENmQCLV9d2rMOfyHkk6ti4CxR5W4+SqauBpi3TQrIcx+V+xMNyGDPBB5rF/H -Zg+pob+76VcCAwEAAaMkMCIwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUF -BwMCMA0GCSqGSIb3DQEBCwUAA4IBAQAqRcLAGvYMaGYOV4HJTzNotT2qE0I9THNQ -wOV1fBg69x6SrUQTQLjJEptpOA288Wue6Jt3H+p5qAGV5GbXjzN/yjCoItggSKxG -Xg7279nz6/C5faoIKRjpS9R+MsJGlttP9nUzdSxrHvvqm62OuSVFjjETxD39DupE -YPFQoHOxdFTtBQlc/zIKxVdd20rs1xJeeU2/L7jtRBSPuR/Sk8zot7G2/dQHX49y -kHrq8qz12kj1T6XDXf8KZawFywXaz0/Ur+fUYKmkVk1T0JZaNtF4sKqDeNE4zcns -p3xLVDSl1Q5Gwj7bgph9o4Hxs9izPwiqjmNaSjPimGYZ399zcurY ------END CERTIFICATE----- \ No newline at end of file diff --git a/.evergreen/x509gen/client.pem b/.evergreen/x509gen/client.pem deleted file mode 100644 index 5b0700109..000000000 --- a/.evergreen/x509gen/client.pem +++ /dev/null @@ -1,48 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAsNS8UEuin7/K29jXfIOLpIoh1jEyWVqxiie2Onx7uJJKcoKo -khA3XeUnVN0k6X5MwYWcN52xcns7LYtyt06nRpTG2/emoV44w9uKTuHsvUbiOwSV -m/ToKQQ4FUFZoqorXH+ZmJuIpJNfoW+3CkE1vEDCIecIq6BNg5ySsPtvSuSJHGjp -mc7/5ZUDvFE2aJ8QbJU3Ws0HXiEb6ymi048LlzEL2VKX3w6mqqh+7dcZGAy7qYk2 -5FZ9ktKvCeQau7mTyU1hsPrKFiKtMN8Q2ZAItX13asw5/IeSTq2LgLFHlbj5Kpq4 -GmLdNCshzH5X7Ew3IYM8EHmsX8dmD6mhv7vpVwIDAQABAoIBABOdpb4qhcG+3twA -c/cGCKmaASLnljQ/UU6IFTjrsjXJVKTbRaPeVKX/05sgZQXZ0t3s2mV5AsQ2U1w8 -Cd+3w+qaemzQThW8hAOGCROzEDX29QWi/o2sX0ydgTMqaq0Wv3SlWv6I0mGfT45y -/BURIsrdTCvCmz2erLqa1dL4MWJXRFjT9UTs5twlecIOM2IHKoGGagFhymRK4kDe -wTRC9fpfoAgyfus3pCO/wi/F8yKGPDEwY+zgkhrJQ+kSeki7oKdGD1H540vB8gRt -EIqssE0Y6rEYf97WssQlxJgvoJBDSftOijS6mwvoasDUwfFqyyPiirawXWWhHXkc -DjIi/XECgYEA5xfjilw9YyM2UGQNESbNNunPcj7gDZbN347xJwmYmi9AUdPLt9xN -3XaMqqR22k1DUOxC/5hH0uiXir7mDfqmC+XS/ic/VOsa3CDWejkEnyGLiwSHY502 -wD/xWgHwUiGVAG9HY64vnDGm6L3KGXA2oqxanL4V0+0+Ht49pZ16i8sCgYEAw+Ox -CHGtpkzjCP/z8xr+1VTSdpc/4CP2HONnYopcn48KfQnf7Nale69/1kZpypJlvQSG -eeA3jMGigNJEkb8/kaVoRLCisXcwLc0XIfCTeiK6FS0Ka30D/84Qm8UsHxRdpGkM -kYITAa2r64tgRL8as4/ukeXBKE+oOhX43LeEfyUCgYBkf7IX2Ndlhsm3GlvIarxy -NipeP9PGdR/hKlPbq0OvQf9R1q7QrcE7H7Q6/b0mYNV2mtjkOQB7S2WkFDMOP0P5 -BqDEoKLdNkV/F9TOYH+PCNKbyYNrodJOt0Ap6Y/u1+Xpw3sjcXwJDFrO+sKqX2+T -PStG4S+y84jBedsLbDoAEwKBgQCTz7/KC11o2yOFqv09N+WKvBKDgeWlD/2qFr3w -UU9K5viXGVhqshz0k5z25vL09Drowf1nAZVpFMO2SPOMtq8VC6b+Dfr1xmYIaXVH -Gu1tf77CM9Zk/VSDNc66e7GrUgbHBK2DLo+A+Ld9aRIfTcSsMbNnS+LQtCrQibvb -cG7+MQKBgQCY11oMT2dUekoZEyW4no7W5D74lR8ztMjp/fWWTDo/AZGPBY6cZoZF -IICrzYtDT/5BzB0Jh1f4O9ZQkm5+OvlFbmoZoSbMzHL3oJCBOY5K0/kdGXL46WWh -IRJSYakNU6VIS7SjDpKgm9D8befQqZeoSggSjIIULIiAtYgS80vmGA== ------END RSA PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDgzCCAmugAwIBAgIDAxOUMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy -aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u -Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx -CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIzNTU1NFoXDTM5MDUyMjIzNTU1NFowaTEP -MA0GA1UEAxMGY2xpZW50MRAwDgYDVQQLEwdEcml2ZXJzMQwwCgYDVQQKEwNNREIx -FjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYD -VQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALDUvFBLop+/ -ytvY13yDi6SKIdYxMllasYontjp8e7iSSnKCqJIQN13lJ1TdJOl+TMGFnDedsXJ7 -Oy2LcrdOp0aUxtv3pqFeOMPbik7h7L1G4jsElZv06CkEOBVBWaKqK1x/mZibiKST -X6FvtwpBNbxAwiHnCKugTYOckrD7b0rkiRxo6ZnO/+WVA7xRNmifEGyVN1rNB14h -G+spotOPC5cxC9lSl98Opqqofu3XGRgMu6mJNuRWfZLSrwnkGru5k8lNYbD6yhYi -rTDfENmQCLV9d2rMOfyHkk6ti4CxR5W4+SqauBpi3TQrIcx+V+xMNyGDPBB5rF/H -Zg+pob+76VcCAwEAAaMkMCIwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUF -BwMCMA0GCSqGSIb3DQEBCwUAA4IBAQAqRcLAGvYMaGYOV4HJTzNotT2qE0I9THNQ -wOV1fBg69x6SrUQTQLjJEptpOA288Wue6Jt3H+p5qAGV5GbXjzN/yjCoItggSKxG -Xg7279nz6/C5faoIKRjpS9R+MsJGlttP9nUzdSxrHvvqm62OuSVFjjETxD39DupE -YPFQoHOxdFTtBQlc/zIKxVdd20rs1xJeeU2/L7jtRBSPuR/Sk8zot7G2/dQHX49y -kHrq8qz12kj1T6XDXf8KZawFywXaz0/Ur+fUYKmkVk1T0JZaNtF4sKqDeNE4zcns -p3xLVDSl1Q5Gwj7bgph9o4Hxs9izPwiqjmNaSjPimGYZ399zcurY ------END CERTIFICATE----- diff --git a/.evergreen/x509gen/commonName.pem b/.evergreen/x509gen/commonName.pem deleted file mode 100644 index e8ebd4953..000000000 --- a/.evergreen/x509gen/commonName.pem +++ /dev/null @@ -1,48 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEArS94Vnpx+C+LwiCIyfZH45uwDbiEqCKr0hfWPVlJdoOQgaDO -av9nbxRvx+CnsiZ1yE6Kj1NYIX3FqOzO9YizwKtPaxCqFMjDzl1HZCJ8LTZZzMic -01K38wGfacsBwno/sNZn9jgnT+9JOasD6854IAs5T7dRCFH/nxV+RuZ4ueTWIcfH -jXzAZv9+wtu0sVmQKHV0J3S6ZdPqqDaYRdhOCyShBTO4RbUW1myIjooIqqy/xceV -TmXGWycqZjyDDronT1kj/yx6znqudOeDzj1PaEdnsqdXxQlI7MVdRf3nXdDXTpw5 -gPhqxqYc47vL6RvMxqief0BJnlc6PoZWoyTRPwIDAQABAoIBAQCYNMYwSsDrfO35 -mRpfVYHs+iGKjYaZNo+Hv8dcd6Jm9E4Gf0urIfjH2VA8fKclnUOa3dxNBtTH6n/T -bPyfMpu4U1cjI6w3RBNCxRw/V0eHfOMDZbTezS459k0ib3aGc2aShn0sGkICsKzM -cA6sKfPNRdACzXv8MgTUzdEDgv7LcGwNUKYzz/XWZxOX+XpeAGNSdXxv6ASvZNJ7 -u3Ba6LbOSAjxnKK24qdBDwCfuxRvD6ovenvI3+qIDSZSrEs/ofGhEEdKlQiyUAgS -m40kWqtoq9sC4/6cGxCLw9scuwXhwE0NNP19QRjh6Hsmr6qmu8LJAKugJi+5WyLg -1oHLs91xAoGBAO4oy6cdc57UdL7A2UbFDWJkBlySw0ChCK4I49Sfq/IISpd3mOfH -SxpZoh5IEnKTEYSqMi/kUUt8J/kQhjdAhqyA33GuNekfGPumUxyB8nKtowNNevyv -Ou6Y9FmzwEektvTLoku/4GxVbrgE262YEu/U1bMA700YK88knCtRWrtFAoGBALoo -qdUpb9s0NK0K4pGo8NYdtqVraOkXPAhKCCOY+hnl0yJERU7LLM9pYCMmR9m/TPcA -pXZTETPWcB6SDJoH3nCmje1Bt3xTxnSvt9P8lXYfvgVpKz8zBrvvnZqUDbMUjWe+ -vz9/jRKrarKgzG6KLnLgFV9sNbuSoOER4/h7MmCzAoGARP2qaUHd4Y/4Nd4V0yt4 -Qh1pvl2BlHJR2mCW51xN6jI+sXwi3lncRsjabt1AAtLZy02mdjs01aIkzkDcMJtP -qB85G2x1D5BDo3q+Ls7yFgh45ZcHXrXAY6gJeQbaV6a+nVF0NW9jKt7g0QwPO02H -htRoB4/owrOS1VHsr5vEpeUCgYAsWg/MZ2js8s0yBQvh5Dws5ztiwepmzlBRMUIr -KQE9NlJNMbLJiQKOD+8FsNMhf8BYgODrBfNtREPGJMm30PQgJq5dvnB2wIbhuhOz -/9OkJv/gziOtlPyfvgDwmSGCbv0ZoIp0GHGF5y0ujbznASj72YN+DovmupJ1zQth -YgionQKBgDGtSfvf3VpJxoabJ52tC0vJFDzkqdbOT0imuLjRHmUH4pSKuMvanvVk -kYcHXeQcfLOPjH18UUqTIgK5vXXjJraduq2bGyvdLcbd3xmj5guzfim3FP83Lh/U -OMAbRgBdq3rlylRqcZh0NqV05L0kJ0Wt1XIaV/eknpuFz5nD7O+y ------END RSA PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDcTCCAlmgAwIBAgIDB5VBMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy -aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u -Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx -CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIxMDUxNVoXDTM5MDUyMjIxMDUxNVowfTEf -MB0GA1UEAxMWY29tbW9uTmFtZS5tb25nb2RiLm9yZzEQMA4GA1UECxMHRHJpdmVy -czEQMA4GA1UEChMHTW9uZ29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8G -A1UECBMITmV3IFlvcmsxCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEArS94Vnpx+C+LwiCIyfZH45uwDbiEqCKr0hfWPVlJdoOQgaDO -av9nbxRvx+CnsiZ1yE6Kj1NYIX3FqOzO9YizwKtPaxCqFMjDzl1HZCJ8LTZZzMic -01K38wGfacsBwno/sNZn9jgnT+9JOasD6854IAs5T7dRCFH/nxV+RuZ4ueTWIcfH -jXzAZv9+wtu0sVmQKHV0J3S6ZdPqqDaYRdhOCyShBTO4RbUW1myIjooIqqy/xceV -TmXGWycqZjyDDronT1kj/yx6znqudOeDzj1PaEdnsqdXxQlI7MVdRf3nXdDXTpw5 -gPhqxqYc47vL6RvMxqief0BJnlc6PoZWoyTRPwIDAQABMA0GCSqGSIb3DQEBCwUA -A4IBAQA34DUMfx0YaxsXnNlCmbkncwgb69VfwWTqtON2MabOlw9fQ0Z5YlwduBSD -DxkRosVURdqV+EcGxei6opnPkdoJ+1mkCDo360q+R/bJUFqjj7djB7GCwwK/Eud4 -Jjn//eLBChU+DlTjO1yL8haEQR70LyVz37sh28oIRqoTS3Nk2SZg7Gnor1qHwd6j -OljaM1WiTJfq6XCSZ9/3C5Ix0Vr7xZaP9Dn5lgQ86du6N6tmaKqVobCw3vjITmnr -eZTC7dKU4/O52d6lHZ1vv8GyvqrRCeiolTVzhW47GvO/n+snC0NMkXvoo7Rzv1S/ -FxHvlhiH5wCbaGnBx4uF5/boedV+ ------END CERTIFICATE----- diff --git a/.evergreen/x509gen/crl.pem b/.evergreen/x509gen/crl.pem deleted file mode 100644 index 733a0acdc..000000000 --- a/.evergreen/x509gen/crl.pem +++ /dev/null @@ -1,13 +0,0 @@ ------BEGIN X509 CRL----- -MIIB6jCB0wIBATANBgkqhkiG9w0BAQsFADB5MRswGQYDVQQDExJEcml2ZXJzIFRl -c3RpbmcgQ0ExEDAOBgNVBAsTB0RyaXZlcnMxEDAOBgNVBAoTB01vbmdvREIxFjAU -BgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYDVQQG -EwJVUxcNMTkwNTIyMjI0NTUzWhcNMTkwNjIxMjI0NTUzWjAVMBMCAncVFw0xOTA1 -MjIyMjQ1MzJaoA8wDTALBgNVHRQEBAICEAAwDQYJKoZIhvcNAQELBQADggEBACwQ -W9OF6ExJSzzYbpCRroznkfdLG7ghNSxIpBQUGtcnYbkP4em6TdtAj5K3yBjcKn4a -hnUoa5EJGr2Xgg0QascV/1GuWEJC9rsYYB9boVi95l1CrkS0pseaunM086iItZ4a -hRVza8qEMBc3rdsracA7hElYMKdFTRLpIGciJehXzv40yT5XFBHGy/HIT0CD50O7 -BDOHzA+rCFCvxX8UY9myDfb1r1zUW7Gzjn241VT7bcIJmhFE9oV0popzDyqr6GvP -qB2t5VmFpbnSwkuc4ie8Jizip1P8Hg73lut3oVAHACFGPpfaNIAp4GcSH61zJmff -9UBe3CJ1INwqyiuqGeA= ------END X509 CRL----- diff --git a/.evergreen/x509gen/expired.pem b/.evergreen/x509gen/expired.pem deleted file mode 100644 index 2d92be01a..000000000 --- a/.evergreen/x509gen/expired.pem +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAw06nN1BoINnY/WJXvi+r0taDMphWQNoyeM85A6NIYKo+vWtN -fvf6f2JTpg/Q4NJ3txiZE/F6yZaMFC78l1KMoz+zIEPLpSJoIezCaXyl2+AQih8A -WmAOFAoiWYTiNfWoVM7t0Qzy6yS+rXifuET5Dg1mtWpA6xRFHZEqQTdKX4QzbT5G -RenoFIBT9wG7xUV+FG1/9s5nx4f5gZDbwMKA7mNq/Jr+rZZQV4lReeGtoYNx1I/Z -4yd4xswI3RfuB7QDZMNHWZazFxW1N6EP5NJBDZJkYLEkMX36r4Orr/73z4EA5GBx -zqdSKH9qHLRiBwesfZf7u8xb120u1S1X1J8a5QIDAQABAoIBAQC+Y9swWerYM2WL -RKYCWZhndQP6e3SBzfMrv951hGQXD38Pyh2Gq5h/O0wN8xcNQz6+t3TqcxnekCrH -tjI4FZnRvlQRHOXVeeAHSjUO/hr1Z8zXyHbgowi2Ula/64FVVr+cxQgiJTxdK7nR -g2g4Csy6/SdlrEnSoDTsKMoHPy36Q0GaLDBnthpKIc1Prhntf6vBCgQAHXVfLk6E -NwddYloL+mfEZESa3Qf2ZYeX/Ovq9agbuQ3cRE7M5FunSo9E7eXt+D+Ntk0usTKV -BaUEHLRYXV827fMDGc1vBN6WFVfthhYviIEgDdkALwOw4lfIiA2WM3fhCF6Ow9hJ -as3dpEHBAoGBAO+l4PdUXypWBYQNZKggH79kAFuAOWtLsMqEBO0ZaXXdvFdwdzhR -jbL7mhRrghgGYpXWIUaNkcbX0XPlkWl2dRzYQqRNjUSEGFabVAxdGZPfiYoupXVl -Lz/FIG3P6BnEYmczh9MxRpJyk4wlUCKppYPiBrR0Ei/qcbGvciOwLq5VAoGBANCi -PWG2izO2HuBFgZTuVIvnl7ixQXgG/tvbiEmYvDNYy1E+w1MWY10Ve/JtIncBIVHk -fEgJPL3hvipAez5ir9Qa1D4PlWxsIrbjuNcLaj+IsRhWBDjMKwRWgmTvvsimcyF5 -39Vs4FujR8cgXy8UnZhYDVRC13PyxmYfJrp4QCpRAoGAKV8nsUsdir+DAEMXp3a0 -RGRNM361avKMOMoF17DVZgW7qBTAYDakEcwh03ij4uXnSxrGb9ms2vkTLcDqE5zh -pvMmvhqtUrDDSuBR6DiCW+bxZaub4OJw/79WU97aoOgoXMymnC0bk9i35C/k37cN -3fC9W5XWNfNxYU16lPKrfGkCgYA14hD0UY72Fg03YvwqmLshPvkCbFU6SKQ96B70 -0wuYP1CTdSBBL0EOY2QVonYKQjJ20gn/GNOlPs48X1b1L8u1fhBezuuKiwsULRAq -Cfqw2f7TCDQi7ygVALrAkuK1M7f8Z1uV5X60bCE3nna21B43oFYg8vpuKb9v1I/O -DQyVYQKBgQCH/Kxq+7Or/5ciq15Vy6z+PJdsGV9FV9S7zkQOZqJ4PXJn0wG9PXnp -ugjvmU1iLx0bXs5llByRx792Q/QmdWnwMCohs6bkWaBCf36JJfTkDTzzbez43cCK -HcYi6gtbiBznWiLWekudRkWdhIFEGU6cSjimy1i4yvwIw85PlEQt/Q== ------END RSA PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDljCCAn6gAwIBAgIDAYZJMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy -aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u -Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx -CzAJBgNVBAYTAlVTMB4XDTE5MDUyMDIyMzYzNVoXDTE5MDUyMTIyMzYzNVowcDES -MBAGA1UEAxMJbG9jYWxob3N0MRAwDgYDVQQLEwdEcml2ZXJzMRAwDgYDVQQKEwdN -b25nb0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREwDwYDVQQIEwhOZXcgWW9y -azELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDD -Tqc3UGgg2dj9Yle+L6vS1oMymFZA2jJ4zzkDo0hgqj69a01+9/p/YlOmD9Dg0ne3 -GJkT8XrJlowULvyXUoyjP7MgQ8ulImgh7MJpfKXb4BCKHwBaYA4UCiJZhOI19ahU -zu3RDPLrJL6teJ+4RPkODWa1akDrFEUdkSpBN0pfhDNtPkZF6egUgFP3AbvFRX4U -bX/2zmfHh/mBkNvAwoDuY2r8mv6tllBXiVF54a2hg3HUj9njJ3jGzAjdF+4HtANk -w0dZlrMXFbU3oQ/k0kENkmRgsSQxffqvg6uv/vfPgQDkYHHOp1Iof2octGIHB6x9 -l/u7zFvXbS7VLVfUnxrlAgMBAAGjMDAuMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcE -fwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEAgPh9C6Yi -6ykJYfaOETPEkggI9LlLQyQ0VhSJGrcw8DXGuPEkyd2xtczYoh0ijtYD3nlTQnh1 -u+5mEEP05nuMMURzG+v7WzZG8Qfz/SDBY1Lfvb/waI3w3RT/dcZ6jwz39jQhV+rU -o2F1vr37Hnh1Ehoa2igjKL1w1LmWdoFgHb0p09qQDAGtkP0gxl0t7iujDDRStLQn -OpWwfOpCaYhtzWwONJn/JIG+JCE/szcRbmc4XKw8t06ffS0mKR/yZBCoekZinnPD -XRVWAH/UF5XPs0mUlrvhFcT/vjgXSZvpi+UuVv3XL56xwPmXAgKsYUpqLlgbrVxv -jY93LTJ1azg+Sw== ------END CERTIFICATE----- diff --git a/.evergreen/x509gen/password_protected.pem b/.evergreen/x509gen/password_protected.pem deleted file mode 100644 index cc9e12470..000000000 --- a/.evergreen/x509gen/password_protected.pem +++ /dev/null @@ -1,51 +0,0 @@ ------BEGIN ENCRYPTED PRIVATE KEY----- -MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIC8as6PDVhwECAggA -MB0GCWCGSAFlAwQBAgQQTYOgCJcRqUI7dsgqNojv/ASCBNCG9fiu642V4AuFK34c -Q42lvy/cR0CIXLq/rDXN1L685kdeKex7AfDuRtnjY2+7CLJiJimgQNJXDJPHab/k -MBHbwbBs38fg6eSYX8V08/IyyTege5EJMhYxmieHDC3DXKt0gyHk6hA/r5+Mr49h -HeVGwqBLJEQ3gVIeHaOleZYspsXXWqOPHnFiqnk/biaJS0+LkDDEiQgTLEYSnOjP -lexxUc4BV/TN0Z920tZCMfwx7IXD/C+0AkV/Iqq4LALmT702EccB3indaIJ8biGR -radqDLR32Q+vT9uZHgT8EFiUsISMqhob2mnyTfFV/s9ghWwogjSz0HrRcq6fxdg7 -oeyT9K0ET53AGTGmV0206byPu6qCj1eNvtn+t1Ob+d5hecaTugRMVheWPlc5frsz -AcewDNa0pv4pZItjAGMqOPJHfzEDnzTJXpLqGYhg044H1+OCY8+1YK7U0u8dO+/3 -f5AoDMq18ipDVTFTooJURej4/Wjbrfad3ZFjp86nxfHPeWM1YjC9+IlLtK1wr0/U -V8TjGqCkw8yHayz01A86iA8X53YQBg+tyMGjxmivo6LgFGKa9mXGvDkN+B+0+OcA -PqldAuH/TJhnkqzja767e4n9kcr+TmV19Hn1hcJPTDrRU8+sSqQFsWN4pvHazAYB -UdWie+EXI0eU2Av9JFgrVcpRipXjB48BaPwuBw8hm+VStCH7ynF4lJy6/3esjYwk -Mx+NUf8+pp1DRzpzuJa2vAutzqia5r58+zloQMxkgTZtJkQU6OCRoUhHGVk7WNb1 -nxsibOSzyVSP9ZNbHIHAn43vICFGrPubRs200Kc4CdXsOSEWoP0XYebhiNJgGtQs -KoISsV4dFRLwhaJhIlayTBQz6w6Ph87WbtuiAqoLiuqdXhUGz/79j/6JZqCH8t/H -eZs4Dhu+HdD/wZKJDYAS+JBsiwYWnI3y/EowZYgLdOMI4u6xYDejhxwEw20LW445 -qjJ7pV/iX2uavazHgC91Bfd4zodfXIQ1IDyTmb51UFwx0ARzG6enntduO6xtcYU9 -MXwfrEpuZ/MkWTLkR0PHPbIPcR1MiVwPKdvrLk42Bzj/urtXYrAFUckMFMzEh+uv -0lix2hbq/Xwj4dXcY4w9hnC6QQDCJTf9S6MU6OisrZHKk0qZ2Vb4aU/eBcBsHBwo -X/QGcDHneHxlrrs2eLX26Vh8Odc5h8haeIxnfaa1t+Yv56OKHuAztPMnJOUL7KtQ -A556LxT0b5IGx0RcfUcbG8XbxEHseACptoDOoguh9923IBI0uXmpi8q0P815LPUu -0AsE47ATDMGPnXbopejRDicfgMGjykJn8vKO8r/Ia3Fpnomx4iJNCXGqomL+GMpZ -IhQbKNrRG6XZMlx5kVCT0Qr1nOWMiOTSDCQ5vrG3c1Viu+0bctvidEvs+LCm98tb -7ty8F0uOno0rYGNQz18OEE1Tj+E19Vauz1U35Z5SsgJJ/GfzhSJ79Srmdg2PsAzk -AUNTKXux1GLf1cMjTiiU5g+tCEtUL9Me7lsv3L6aFdrCyRbhXUQfJh4NAG8+3Pvh -EaprThBzKsVvbOfU81mOaH9YMmUgmxG86vxDiNtaWd4v6c1k+HGspJr/q49pcXZP -ltBMuS9AihstZ1sHJsyQCmNXkA== ------END ENCRYPTED PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDgzCCAmugAwIBAgIDBXUHMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy -aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u -Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx -CzAJBgNVBAYTAlVTMB4XDTE5MDUyMzAwMDEyOVoXDTM5MDUyMzAwMDEyOVowaTEP -MA0GA1UEAxMGY2xpZW50MRAwDgYDVQQLEwdEcml2ZXJzMQwwCgYDVQQKEwNNREIx -FjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYD -VQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOqCb0Lo4XsV -W327Wlnqc5rwWa5Elw0rFuehSfViRIcYfuFWAPXoOj3fIDsYz6d41G8hp6tkF88p -swlbzDF8Fc7mXDhauwwl2F/NrWYUXwCT8fKju4DtGd2JlDMi1TRDeofkYCGVPp70 -vNqd0H8iDWWs8OmiNrdBLJwNiGaf9y15ena4ImQGitXLFn+qNSXYJ1Rs8p7Y2PTr -L+dff5gJCVbANwGII1rjMAsrMACPVmr8c1Lxoq4fSdJiLweosrv2Lk0WWGsO0Seg -ZY71dNHEyNjItE+VtFEtslJ5L261i3BfF/FqNnH2UmKXzShwfwxyHT8o84gSAltQ -5/lVJ4QQKosCAwEAAaMkMCIwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUF -BwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBOAlKxIMFcTZ+4k8NJv97RSf+zOb5Wu2ct -uxSZxzgKTxLFUuEM8XQiEz1iHQ3XG+uV1fzA74YLQiKjjLrU0mx54eM1vaRtOXvF -sJlzZU8Z2+523FVPx4HBPyObQrfXmIoAiHoQ4VUeepkPRpXxpifgWd/OCWhLDr2/ -0Kgcb0ybaGVDpA0UD9uVIwgFjRu6id7wG+lVcdRxJYskTOOaN2o1hMdAKkrpFQbd -zNRfEoBPUYR3QAmAKP2HBjpgp4ktOHoOKMlfeAuuMCUocSnmPKc3xJaH/6O7rHcf -/Rm0X411RH8JfoXYsSiPsd601kZefhuWvJH0sJLibRDvT7zs8C1v ------END CERTIFICATE----- diff --git a/.evergreen/x509gen/server.pem b/.evergreen/x509gen/server.pem deleted file mode 100644 index 7480f9644..000000000 --- a/.evergreen/x509gen/server.pem +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAhNrB0E6GY/kFSd8/vNpu/t952tbnOsD5drV0XPvmuy7SgKDY -a/S+xb/jPnlZKKehdBnH7qP/gYbv34ZykzcDFZscjPLiGc2cRGP+NQCSFK0d2/7d -y15zSD3zhj14G8+MkpAejTU+0/qFNZMc5neDvGanTe0+8aWa0DXssM0MuTxIv7j6 -CtsMWeqLLofN7a1Kw2UvmieCHfHMuA/08pJwRnV/+5T9WONBPJja2ZQRrG1BjpI4 -81zSPUZesIqi8yDlExdvgNaRZIEHi/njREqwVgJOZomUY57zmKypiMzbz48dDTsV -gUStxrEqbaP+BEjQYPX5+QQk4GdMjkLf52LR6QIDAQABAoIBAHSs+hHLJNOf2zkp -S3y8CUblVMsQeTpsR6otaehPgi9Zy50TpX4KD5D0GMrBH8BIl86y5Zd7h+VlcDzK -gs0vPxI2izhuBovKuzaE6rf5rFFkSBjxGDCG3o/PeJOoYFdsS3RcBbjVzju0hFCs -xnDQ/Wz0anJRrTnjyraY5SnQqx/xuhLXkj/lwWoWjP2bUqDprnuLOj16soNu60Um -JziWbmWx9ty0wohkI/8DPBl9FjSniEEUi9pnZXPElFN6kwPkgdfT5rY/TkMH4lsu -ozOUc5xgwlkT6kVjXHcs3fleuT/mOfVXLPgNms85JKLucfd6KiV7jYZkT/bXIjQ+ -7CZEn0ECgYEA5QiKZgsfJjWvZpt21V/i7dPje2xdwHtZ8F9NjX7ZUFA7mUPxUlwe -GiXxmy6RGzNdnLOto4SF0/7ebuF3koO77oLup5a2etL+y/AnNAufbu4S5D72sbiz -wdLzr3d5JQ12xeaEH6kQNk2SD5/ShctdS6GmTgQPiJIgH0MIdi9F3v0CgYEAlH84 -hMWcC+5b4hHUEexeNkT8kCXwHVcUjGRaYFdSHgovvWllApZDHSWZ+vRcMBdlhNPu -09Btxo99cjOZwGYJyt20QQLGc/ZyiOF4ximQzabTeFgLkTH3Ox6Mh2Rx9yIruYoX -nE3UfMDkYELanEJUv0zenKpZHw7tTt5yXXSlEF0CgYBSsEOvVcKYO/eoluZPYQAA -F2jgzZ4HeUFebDoGpM52lZD+463Dq2hezmYtPaG77U6V3bUJ/TWH9VN/Or290vvN -v83ECcC2FWlSXdD5lFyqYx/E8gqE3YdgqfW62uqM+xBvoKsA9zvYLydVpsEN9v8m -6CSvs/2btA4O21e5u5WBTQKBgGtAb6vFpe0gHRDs24SOeYUs0lWycPhf+qFjobrP -lqnHpa9iPeheat7UV6BfeW3qmBIVl/s4IPE2ld4z0qqZiB0Tf6ssu/TpXNPsNXS6 -dLFz+myC+ufFdNEoQUtQitd5wKbjTCZCOGRaVRgJcSdG6Tq55Fa22mOKPm+mTmed -ZdKpAoGAFsTYBAHPxs8nzkCJCl7KLa4/zgbgywO6EcQgA7tfelB8bc8vcAMG5o+8 -YqAfwxrzhVSVbJx0fibTARXROmbh2pn010l2wj3+qUajM8NiskCPFbSjGy7HSUze -P8Kt1uMDJdj55gATzn44au31QBioZY2zXleorxF21cr+BZCJgfA= ------END RSA PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDlTCCAn2gAwIBAgICdxUwDQYJKoZIhvcNAQELBQAweTEbMBkGA1UEAxMSRHJp -dmVycyBUZXN0aW5nIENBMRAwDgYDVQQLEwdEcml2ZXJzMRAwDgYDVQQKEwdNb25n -b0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREwDwYDVQQIEwhOZXcgWW9yazEL -MAkGA1UEBhMCVVMwHhcNMTkwNTIyMjIzMjU2WhcNMzkwNTIyMjIzMjU2WjBwMRIw -EAYDVQQDEwlsb2NhbGhvc3QxEDAOBgNVBAsTB0RyaXZlcnMxEDAOBgNVBAoTB01v -bmdvREIxFjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3Jr -MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAITa -wdBOhmP5BUnfP7zabv7fedrW5zrA+Xa1dFz75rsu0oCg2Gv0vsW/4z55WSinoXQZ -x+6j/4GG79+GcpM3AxWbHIzy4hnNnERj/jUAkhStHdv+3ctec0g984Y9eBvPjJKQ -Ho01PtP6hTWTHOZ3g7xmp03tPvGlmtA17LDNDLk8SL+4+grbDFnqiy6Hze2tSsNl -L5ongh3xzLgP9PKScEZ1f/uU/VjjQTyY2tmUEaxtQY6SOPNc0j1GXrCKovMg5RMX -b4DWkWSBB4v540RKsFYCTmaJlGOe85isqYjM28+PHQ07FYFErcaxKm2j/gRI0GD1 -+fkEJOBnTI5C3+di0ekCAwEAAaMwMC4wLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/ -AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQBol8+YH7MA -HwnIh7KcJ8h87GkCWsjOJCDJWiYBJArQ0MmgDO0qdx+QEtvLMn3XNtP05ZfK0WyX -or4cWllAkMFYaFbyB2hYazlD1UAAG+22Rku0UP6pJMLbWe6pnqzx+RL68FYdbZhN -fCW2xiiKsdPoo2VEY7eeZKrNr/0RFE5EKXgzmobpTBQT1Dl3Ve4aWLoTy9INlQ/g -z40qS7oq1PjjPLgxINhf4ncJqfmRXugYTOnyFiVXLZTys5Pb9SMKdToGl3NTYWLL -2AZdjr6bKtT+WtXyHqO0cQ8CkAW0M6VOlMluACllcJxfrtdlQS2S4lUIj76QKBdZ -khBHXq/b8MFX ------END CERTIFICATE----- diff --git a/.evergreen/x509gen/wild.pem b/.evergreen/x509gen/wild.pem deleted file mode 100644 index d41800748..000000000 --- a/.evergreen/x509gen/wild.pem +++ /dev/null @@ -1,49 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAlenyliMpkLM9aR51iOO7hdLS66pwgafsJlIbtsKAy6WxlcKA -yecs0yCQfw5z5j3BFgv88dzAFEF+jFG6o/EmAzqmK5uRCQX1EJbl2p8detbzToj9 -Ys1Z1peWE8FkJtZMKUdLdlRQQ57v2VUr0kwtFEUGlSNyVwf4pJ5coyqpukmoUdko -zrKeclshjydDVo44Ln6WYvN6odz/CZT808fHZ0CXcIEKyDV8zXIcHGX2OUL/ajtZ -+C2pIbAx64nin1BLtHGvDT0Pan1xKDiMCkOdc7va0gLh0qtPjGLsI4vc8iByviGJ -Kw7hVaj7ym0r2DFzeqghfvNNNHisGXSf+6EcPQIDAQABAoIBAGq/PVefDhfVKaNS -ZwrkbkDqT/ozUQ1hzwuyZ72JXkCkaYFkEGS0Ufy8MWfnmKuXyYezXZezQqqpwDyW -bboTGqgt+OkQSwQL0+bOLDmyF0HDEVkYvqS96HyfT+QdTv1AltbFx3woqUadQ9iT -hzKlv2uxgvBrXx2NtYUypnAhDt5wQQ4n1w46Kl1USb983qWDWyFtHfIQo6vF1JK/ -s6I6oA2tmORPTD3A7E2xT98UMM8B1c/v1F+owAiD+KNmgAN4oWSWBfRGEKg59fZA -aGWjQrwoWmQQJnMnTsHZc+2hT7waKnyOwOFq1NPXyfCw+4cSeI3B3rPxPyShBM4O -ZKfajIECgYEAz555nPHhk5GmMpXprryCODy66ChHWulA3fM9r0k/ObBgKqTirAOA -y0PmA8OxR8acV0V2lZImdwF5Lvnj+c8+fTFSnPKSQHpcZ/lbxo+m2rYwv7+BxUP9 -GJAWzA6xqBde6hNPULml8cNOqT7jwRnLt/DkwY+94Oeh3H5CRYb90Y0CgYEAuNkR -EieGwCn+TjgatkhMLhYqr234544p3ofL82CFlCcsOXtWqCma6POOi038eBlZiHV9 -EPBq4qQHCZMAPeApTZbiZ+Z8ezC3IxjGSX0jP5QK+gBrkk7nbp24nRMlHOrwizsL -/Sxu4Y6puZk5aTUZVufPLXokY6Iez0Kd07vyUXECgYBqWHFQi7EQ5nzr0lAVOee1 -qJ3QRrlt/qZESdCh1XH2Obq4fSbCFzVEaK4L5ZQMANaZ+TGpoWfkczPAdS1qCtam -R7paPAHf1w04EMkKpxA/XS0ROqXdBltA1qVmtmwXfokWeveYkM9IS9Mh6927TlxE -BrcV0mvfJKaLC30koeWnDQKBgEn1oBzxb7sHklbdn+J7Pu/Zsq6Kg+KyQRJmpzXz -0r6ahdlh/iQ+sVqvyML4KyIqkmZFDAtxBnM0ShSMmrYnMJ941ZHY6Mmpjj0etofE -6AuSQmoRLPlXVMYvmSRP+rN9VU2ADKX510usd0BpjE0KD99z1LNPgavTvBwVfWyw -cJ4hAoGBALgyVPMBPv1d8irbM1WHFe/I3vxjb4DWOY9xclbRWjkW69oZmkouGP07 -52ehzfBtBC87VPLwTEr/ERZqfICBqZvXYFypd2ydGhbDKjDswiUd6nACNKAx5ZPo -OVwQjVfjGqkKNThoHhvE1YU//+WtCe0YVUGqMA9dyZe1QO3HcqI8 ------END RSA PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDkzCCAnugAwIBAgIDCRU4MA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy -aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u -Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx -CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIyMzgxOVoXDTM5MDUyMjIyMzgxOVowcDES -MBAGA1UEAxMJbG9jYWxob3N0MRAwDgYDVQQLEwdEcml2ZXJzMRAwDgYDVQQKEwdN -b25nb0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREwDwYDVQQIEwhOZXcgWW9y -azELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCV -6fKWIymQsz1pHnWI47uF0tLrqnCBp+wmUhu2woDLpbGVwoDJ5yzTIJB/DnPmPcEW -C/zx3MAUQX6MUbqj8SYDOqYrm5EJBfUQluXanx161vNOiP1izVnWl5YTwWQm1kwp -R0t2VFBDnu/ZVSvSTC0URQaVI3JXB/iknlyjKqm6SahR2SjOsp5yWyGPJ0NWjjgu -fpZi83qh3P8JlPzTx8dnQJdwgQrINXzNchwcZfY5Qv9qO1n4LakhsDHrieKfUEu0 -ca8NPQ9qfXEoOIwKQ51zu9rSAuHSq0+MYuwji9zyIHK+IYkrDuFVqPvKbSvYMXN6 -qCF+8000eKwZdJ/7oRw9AgMBAAGjLTArMCkGA1UdEQQiMCCCCWxvY2FsaG9zdIcE -fwAAAYINKi5tb25nb2RiLm9yZzANBgkqhkiG9w0BAQsFAAOCAQEAMCENVK+w+wP7 -T1XBytsScn7+Bh33sn+A+c7H/6BNOEdTxCQ67L3zBc0XrBFYtiHcAppNBKvvM8cV -ERWjXlU2nZ+A0WKOZE2nXYQL5lBnnXoIMwcdtJuTJuWw8r3MlVXDcP6bK8tNSQMG -WYK7PHQ3RNiWNABZejJV9GVP25nO6Wr2gt2xnEwYvUXTnCJtT+NsTE/fU4MlGuUL -a93Cec86Ij0XTMTcnj4nfZhct30nuqiU4wWBPHCN7BXxRQzIHu68aVHBpwDEAf6j -PAOKhucGY6DW+dyrW/1BjW6+ZOmJWxJ7GB+x0gjprQbGH67gIvRvTa9wW7NqWyS3 -Go/qT7H6FQ== ------END CERTIFICATE----- diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index c66c0fa8f..f85a72de2 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -14,6 +14,10 @@ inputs: working-directory: description: "The directory where composer.json is located, if it is not in the repository root." required: false + ignore-platform-req: + description: "Whether to ignore platform requirements when installing dependencies with Composer." + required: false + default: "false" runs: using: composite @@ -36,7 +40,7 @@ runs: - name: Install PHP uses: shivammathur/setup-php@v2 with: - coverage: none + coverage: xdebug extensions: "mongodb-${{ inputs.driver-version }}" php-version: "${{ inputs.php-version }}" tools: cs2pr @@ -49,5 +53,5 @@ runs: - name: Install dependencies with Composer uses: ramsey/composer-install@3.0.0 with: - composer-options: "--no-suggest" + composer-options: "--no-suggest ${{ inputs.ignore-platform-req == 'true' && '--ignore-platform-req=php+' || '' }}" working-directory: "${{ inputs.working-directory }}" diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 27bf72a6c..22e97cb1c 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -13,7 +13,9 @@ on: env: PHP_VERSION: "8.2" - DRIVER_VERSION: "mongodb/mongo-php-driver@v1.21" + # TODO: change to "stable" once 2.0.0 is released + # DRIVER_VERSION: "stable" + DRIVER_VERSION: "mongodb/mongo-php-driver@v2.x" jobs: phpcs: @@ -22,7 +24,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v4" + uses: "actions/checkout@v5" - name: "Setup" uses: "./.github/actions/setup" @@ -40,7 +42,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v4" + uses: "actions/checkout@v5" - name: "Setup" uses: "./.github/actions/setup" diff --git a/.github/workflows/generator.yml b/.github/workflows/generator.yml index ba60d56e3..f9650fc1f 100644 --- a/.github/workflows/generator.yml +++ b/.github/workflows/generator.yml @@ -13,7 +13,9 @@ on: env: PHP_VERSION: "8.2" - DRIVER_VERSION: "mongodb/mongo-php-driver@v1.21" + # TODO: change to "stable" once 2.0.0 is released + # DRIVER_VERSION: "stable" + DRIVER_VERSION: "mongodb/mongo-php-driver@v2.x" jobs: diff: @@ -22,7 +24,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v4" + uses: "actions/checkout@v5" - name: "Setup" uses: "./.github/actions/setup" diff --git a/.github/workflows/merge-up.yml b/.github/workflows/merge-up.yml index aee665ea2..f8de9e676 100644 --- a/.github/workflows/merge-up.yml +++ b/.github/workflows/merge-up.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Checkout id: checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: # fetch-depth 0 is required to fetch all branches, not just the branch being built fetch-depth: 0 @@ -24,7 +24,7 @@ jobs: - name: Create pull request id: create-pull-request - uses: alcaeus/automatic-merge-up-action@1.0.0 + uses: alcaeus/automatic-merge-up-action@1.0.1 with: ref: ${{ github.ref_name }} branchNamePattern: 'v.' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7d22c6c6c..37bb2ed3f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,7 +49,7 @@ jobs: run: echo '🎬 Release process for version ${{ inputs.version }} started by @${{ github.triggering_actor }}' >> $GITHUB_STEP_SUMMARY - name: "Generate token and checkout repository" - uses: mongodb-labs/drivers-github-tools/secure-checkout@v2 + uses: mongodb-labs/drivers-github-tools/secure-checkout@v3 with: app_id: ${{ vars.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} @@ -57,7 +57,9 @@ jobs: - name: "Store version numbers in env variables" run: | echo RELEASE_VERSION=${{ inputs.version }} >> $GITHUB_ENV + echo RELEASE_VERSION_WITHOUT_STABILITY=$(echo ${{ inputs.version }} | awk -F- '{print $1}') >> $GITHUB_ENV echo RELEASE_BRANCH=v$(echo ${{ inputs.version }} | cut -d '.' -f-2) >> $GITHUB_ENV + echo DEV_BRANCH=v$(echo ${{ inputs.version }} | cut -d '.' -f-1).x >> $GITHUB_ENV - name: "Ensure release tag does not already exist" run: | @@ -66,18 +68,37 @@ jobs: exit 1 fi - - name: "Fail if branch names don't match" - if: ${{ github.ref_name != env.RELEASE_BRANCH }} + # For patch releases (A.B.C where C != 0), we expect the release to be + # triggered from the A.B maintenance branch + - name: "Fail if patch release is created from wrong release branch" + if: ${{ !endsWith(env.RELEASE_VERSION_WITHOUT_STABILITY, '.0') && env.RELEASE_BRANCH != github.ref_name }} run: | echo '❌ Release failed due to branch mismatch: expected ${{ inputs.version }} to be released from ${{ env.RELEASE_BRANCH }}, got ${{ github.ref_name }}' >> $GITHUB_STEP_SUMMARY exit 1 + # For non-patch releases (A.B.C where C == 0), we expect the release to + # be triggered from the A.B maintenance branch or A.x development branch + - name: "Fail if non-patch release is created from wrong release branch" + if: ${{ endsWith(env.RELEASE_VERSION_WITHOUT_STABILITY, '.0') && env.RELEASE_BRANCH != github.ref_name && env.DEV_BRANCH != github.ref_name }} + run: | + echo '❌ Release failed due to branch mismatch: expected ${{ inputs.version }} to be released from ${{ env.RELEASE_BRANCH }} or ${{ env.DEV_BRANCH }}, got ${{ github.ref_name }}' >> $GITHUB_STEP_SUMMARY + exit 1 + + # If a non-patch release is created from its A.x development branch, + # create the A.B maintenance branch from the current one and push it + - name: "Create and push new release branch for non-patch release" + if: ${{ endsWith(env.RELEASE_VERSION_WITHOUT_STABILITY, '.0') && env.DEV_BRANCH == github.ref_name }} + run: | + echo '🆕 Creating new release branch ${{ env.RELEASE_BRANCH }} from ${{ github.ref_name }}' >> $GITHUB_STEP_SUMMARY + git checkout -b ${RELEASE_BRANCH} + git push origin ${RELEASE_BRANCH} + # # Preliminary checks done - commence the release process # - name: "Set up drivers-github-tools" - uses: mongodb-labs/drivers-github-tools/setup@v2 + uses: mongodb-labs/drivers-github-tools/setup@v3 with: aws_role_arn: ${{ secrets.AWS_ROLE_ARN }} aws_region_name: ${{ vars.AWS_REGION_NAME }} @@ -90,10 +111,10 @@ jobs: EOL - name: "Create draft release" - run: echo "RELEASE_URL=$(gh release create ${{ inputs.version }} --target ${{ github.ref_name }} --title "${{ inputs.version }}" --notes-file release-message --draft)" >> "$GITHUB_ENV" + run: echo "RELEASE_URL=$(gh release create ${{ inputs.version }} --target ${{ env.RELEASE_BRANCH }} --title "${{ inputs.version }}" --notes-file release-message --draft)" >> "$GITHUB_ENV" - name: "Create release tag" - uses: mongodb-labs/drivers-github-tools/tag-version@v2 + uses: mongodb-labs/drivers-github-tools/tag-version@v3 with: version: ${{ inputs.version }} tag_message_template: 'Release ${VERSION}' @@ -132,7 +153,7 @@ jobs: steps: - name: "Generate token and checkout repository" - uses: mongodb-labs/drivers-github-tools/secure-checkout@v2 + uses: mongodb-labs/drivers-github-tools/secure-checkout@v3 with: app_id: ${{ vars.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} @@ -140,14 +161,14 @@ jobs: # Sets the S3_ASSETS environment variable used later - name: "Set up drivers-github-tools" - uses: mongodb-labs/drivers-github-tools/setup@v2 + uses: mongodb-labs/drivers-github-tools/setup@v3 with: aws_role_arn: ${{ secrets.AWS_ROLE_ARN }} aws_region_name: ${{ vars.AWS_REGION_NAME }} aws_secret_id: ${{ secrets.AWS_SECRET_ID }} - name: "Generate SSDLC Reports" - uses: mongodb-labs/drivers-github-tools/full-report@v2 + uses: mongodb-labs/drivers-github-tools/full-report@v3 with: product_name: "MongoDB PHP Driver (library)" release_version: ${{ inputs.version }} @@ -158,7 +179,7 @@ jobs: continue-on-error: true - name: Upload S3 assets - uses: mongodb-labs/drivers-github-tools/upload-s3-assets@v2 + uses: mongodb-labs/drivers-github-tools/upload-s3-assets@v3 with: version: ${{ inputs.version }} product_name: mongo-php-library diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index e84d33d5b..440d1d6c4 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -19,7 +19,9 @@ on: env: PHP_VERSION: "8.2" - DRIVER_VERSION: "mongodb/mongo-php-driver@v1.21" + # TODO: change to "stable" once 2.0.0 is released + # DRIVER_VERSION: "stable" + DRIVER_VERSION: "mongodb/mongo-php-driver@v2.x" jobs: psalm: @@ -28,7 +30,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v4" + uses: "actions/checkout@v5" with: ref: ${{ github.event_name == 'workflow_dispatch' && inputs.ref || github.ref }} @@ -48,13 +50,13 @@ jobs: - name: "Upload SARIF report" if: ${{ github.event_name != 'workflow_dispatch' }} - uses: "github/codeql-action/upload-sarif@v3" + uses: "github/codeql-action/upload-sarif@v4" with: sarif_file: psalm.sarif - name: "Upload SARIF report" if: ${{ github.event_name == 'workflow_dispatch' }} - uses: "github/codeql-action/upload-sarif@v3" + uses: "github/codeql-action/upload-sarif@v4" with: sarif_file: psalm.sarif ref: ${{ inputs.ref }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 58bcd0c85..9b6ac37d9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,9 @@ on: - "feature/*" env: - DRIVER_VERSION: "mongodb/mongo-php-driver@v1.21" + # TODO: change to "stable" once 2.0.0 is released + # DRIVER_VERSION: "stable" + DRIVER_VERSION: "mongodb/mongo-php-driver@v2.x" jobs: phpunit: @@ -23,50 +25,53 @@ jobs: fail-fast: true matrix: os: - - "ubuntu-22.04" + - "ubuntu-24.04" php-version: - "8.1" - "8.2" - "8.3" - "8.4" + - "8.5" mongodb-version: - - "6.0" + - "8.0" topology: - - "server" + - "replica_set" include: + # Test additional topologies for MongoDB 8.0 + - os: "ubuntu-24.04" + php-version: "8.4" + mongodb-version: "8.0" + topology: "server" + - os: "ubuntu-24.04" + php-version: "8.4" + mongodb-version: "8.0" + topology: "sharded_cluster" + # Test lowest server/php versions - os: "ubuntu-22.04" php-version: "8.1" mongodb-version: "6.0" - topology: "replica_set" + topology: "server" - os: "ubuntu-22.04" php-version: "8.1" mongodb-version: "6.0" - topology: "sharded_cluster" - - os: "ubuntu-24.04" - php-version: "8.1" - mongodb-version: "8.0" - topology: "server" - - os: "ubuntu-24.04" - php-version: "8.1" - mongodb-version: "8.0" topology: "replica_set" - - os: "ubuntu-24.04" + - os: "ubuntu-22.04" php-version: "8.1" - mongodb-version: "8.0" + mongodb-version: "6.0" topology: "sharded_cluster" steps: - name: "Checkout" - uses: "actions/checkout@v4" + uses: "actions/checkout@v5" with: fetch-depth: 2 submodules: true - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: '3.13' - - name: "Setup MongoDB" + - name: "Set up MongoDB" id: setup-mongodb uses: ./tests/drivers-evergreen-tools with: @@ -79,8 +84,26 @@ jobs: php-version: ${{ matrix.php-version }} driver-version: ${{ env.DRIVER_VERSION }} php-ini-values: "zend.assertions=1" + ignore-platform-req: ${{ matrix.php-version == '8.5' && 'true' || 'false' }} - name: "Run PHPUnit" - run: "vendor/bin/phpunit" + run: "vendor/bin/phpunit --configuration phpunit.evergreen.xml --coverage-clover coverage.xml" env: + XDEBUG_MODE: "coverage" MONGODB_URI: ${{ steps.setup-mongodb.outputs.cluster-uri }} + + - name: "Upload coverage report" + uses: codecov/codecov-action@v5 + with: + disable_search: true + files: coverage.xml + flags: "${{ matrix.mongodb-version }}-${{ matrix.topology }}" + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload test results to Codecov + uses: codecov/test-results-action@v1 + with: + disable_search: true + files: test-results.xml + flags: "${{ matrix.mongodb-version }}-${{ matrix.topology }}" + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 40f94eef6..1aa330118 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,21 +58,21 @@ The test suite references the following environment variables: `username` URI option for clients constructed by the test suite, which will override any credentials in the connection string itself. -The following environment variable is used for [stable API testing](https://github.com/mongodb/specifications/blob/master/source/versioned-api/tests/README.rst): +The following environment variable is used for [stable API testing](https://github.com/mongodb/specifications/blob/master/source/versioned-api/tests/README.md): * `API_VERSION`: If defined, this value will be used to construct a [`MongoDB\Driver\ServerApi`](https://www.php.net/manual/en/mongodb-driver-serverapi.construct.php), which will then be specified as the `serverApi` driver option for clients created by the test suite. -The following environment variables are used for [load balancer testing](https://github.com/mongodb/specifications/blob/master/source/load-balancers/tests/README.rst): +The following environment variables are used for [load balancer testing](https://github.com/mongodb/specifications/blob/master/source/load-balancers/tests/README.md): * `MONGODB_SINGLE_MONGOS_LB_URI`: Connection string to a load balancer backed by a single mongos host. * `MONGODB_MULTI_MONGOS_LB_URI`: Connection string to a load balancer backed by multiple mongos hosts. -The following environment variables are used for [CSFLE testing](https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst): +The following environment variables are used for [CSFLE testing](https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md): * `AWS_ACCESS_KEY_ID` * `AWS_SECRET_ACCESS_KEY` @@ -123,6 +123,15 @@ The goal is that the library passes tests with the latest spec version at all times, either by implementing small changes quickly, or by skipping tests as necessary. +## Backward compatibility + +When submitting a PR, be mindful of our backward compatibility guarantees. Our +BC policy follows [Symfony's Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html). + +In short, this means we use semantic versioning and guarantee backward +compatibility on all minor releases. For a more detailed definition, refer to +the Symfony docs linked above. + ## Code quality Before submitting a pull request, please ensure that your code adheres to the diff --git a/README.md b/README.md index b7620c72c..643e539e2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MongoDB PHP Library -![Tests](https://github.com/mongodb/mongo-php-library/workflows/Tests/badge.svg) -![Coding Standards](https://github.com/mongodb/mongo-php-library/workflows/Coding%20Standards/badge.svg) +![Tests](https://github.com/mongodb/mongo-php-library/actions/workflows/tests.yml/badge.svg) +![Coding Standards](https://github.com/mongodb/mongo-php-library/actions/workflows/coding-standards.yml/badge.svg) This library provides a high-level abstraction around the lower-level [PHP driver](https://github.com/mongodb/mongo-php-driver) (`mongodb` extension). diff --git a/UPGRADE-2.0.md b/UPGRADE-2.0.md new file mode 100644 index 000000000..516b57929 --- /dev/null +++ b/UPGRADE-2.0.md @@ -0,0 +1,83 @@ +UPGRADE FROM 1.x to 2.0 +======================== + + * Classes in the namespace `MongoDB\Operation\` are `final`. + * All methods in interfaces and classes now define a return type. + * The `MongoDB\ChangeStream::CURSOR_NOT_FOUND` constant is now private. + * The `MongoDB\Operation\Watch::FULL_DOCUMENT_DEFAULT` constant has been + removed. + * The `getNamespace` and `isGeoHaystack` methods have been removed from the + `MongoDB\Model\IndexInfo` class. + * The `maxScan`, `modifiers`, `oplogReplay`, and `snapshot` options for `find` + and `findOne` operations have been removed. + * The `MongoDB\Collection::mapReduce` method has been removed. Use + [aggregation pipeline](https://www.mongodb.com/docs/manual/reference/map-reduce-to-aggregation-pipeline/) + instead. + * The following classes and interfaces have been removed without replacement: + * `MongoDB\MapReduceResult` + * `MongoDB\Model\CollectionInfoCommandIterator` + * `MongoDB\Model\CollectionInfoIterator` + * `MongoDB\Model\DatabaseInfoIterator` + * `MongoDB\Model\DatabaseInfoLegacyIterator` + * `MongoDB\Model\IndexInfoIterator` + * `MongoDB\Model\IndexInfoIteratorIterator` + * `MongoDB\Operation\Executable` + * The `flags` and `autoIndexId` options for + `MongoDB\Database::createCollection()` have been removed. Additionally, the + `USE_POWER_OF_2_SIZES` and `NO_PADDING` constants in + `MongoDB\Operation\CreateCollection` have been removed. + +Operations with no result +------------------------- + +The following operations no longer return the raw command result. The return +type changed to `void`. In case of an error, an exception is thrown. + + * `MongoDB\Client`: `dropDatabase` + * `MongoDB\Collection`: `drop`, `dropIndex`, `dropIndexes`, `dropSearchIndex`, `rename` + * `MongoDB\Database`: `createCollection`, `drop`, `dropCollection`, `renameCollection` + * `MongoDB\Database::createEncryptedCollection()` returns the list of encrypted fields + +If you still need to access the raw command result, you can use a +[`CommandSubscriber`](https://www.php.net/manual/en/class.mongodb-driver-monitoring-commandsubscriber.php). + +GridFS +------ + + * The `md5` is no longer calculated when a file is uploaded to GridFS. + Applications that require a file digest should implement it outside GridFS + and store in metadata. + + ```php + $hash = hash_file('sha256', $filename); + $bucket->openUploadStream($fileId, ['metadata' => ['hash' => $hash]]); + ``` + + * The fields `contentType` and `aliases` are no longer stored in the `files` + collection. Applications that require this information should store it in + metadata. + + **Before:** + ```php + $bucket->openUploadStream($fileId, ['contentType' => 'image/png']); + ``` + + **After:** + ```php + $bucket->openUploadStream($fileId, ['metadata' => ['contentType' => 'image/png']]); + ``` + +UnsupportedException method removals +------------------------------------ + +The following methods have been removed from the +`MongoDB\Exception\UnsupportedException` class: + * `allowDiskUseNotSupported` + * `arrayFiltersNotSupported` + * `collationNotSupported` + * `explainNotSupported` + * `readConcernNotSupported` + * `writeConcernNotSupported` + +The remaining methods have been marked as internal and may be removed in a +future minor version. Only the class itself is covered by the BC promise. diff --git a/benchmark/src/DriverBench/MultiDocBench.php b/benchmark/src/DriverBench/MultiDocBench.php index 22c6edba6..b569f9ca5 100644 --- a/benchmark/src/DriverBench/MultiDocBench.php +++ b/benchmark/src/DriverBench/MultiDocBench.php @@ -20,7 +20,7 @@ final class MultiDocBench { /** - * @see https://github.com/mongodb/specifications/blob/master/source/benchmarking/benchmarking.rst#find-many-and-empty-the-cursor + * @see https://github.com/mongodb/specifications/blob/master/source/benchmarking/benchmarking.md#find-many-and-empty-the-cursor * @param array{options: array} $params */ #[BeforeMethods('beforeFindMany')] diff --git a/composer.json b/composer.json index d4c327360..285255e48 100644 --- a/composer.json +++ b/composer.json @@ -11,9 +11,10 @@ ], "require": { "php": "^8.1", - "ext-mongodb": "^1.21.0", + "ext-mongodb": "^2.1", "composer-runtime-api": "^2.0", - "psr/log": "^1.1.4|^2|^3" + "psr/log": "^1.1.4|^2|^3", + "symfony/polyfill-php85": "^1.32" }, "require-dev": { "doctrine/coding-standard": "^12.0", diff --git a/examples/command_logger.php b/examples/command_logger.php index eab47882a..41ead8496 100644 --- a/examples/command_logger.php +++ b/examples/command_logger.php @@ -3,6 +3,8 @@ namespace MongoDB\Examples\CommandLogger; +use Closure; +use Exception; use MongoDB\BSON\Document; use MongoDB\Client; use MongoDB\Driver\Monitoring\CommandFailedEvent; @@ -24,37 +26,56 @@ function toJSON(object $document): string class CommandLogger implements CommandSubscriber { - public function commandStarted(CommandStartedEvent $event): void + /** @param Closure(object):void $handleOutput */ + public function __construct(private readonly Closure $handleOutput) { - printf("%s command started\n", $event->getCommandName()); + } - printf("command: %s\n", toJson($event->getCommand())); - echo "\n"; + public function commandStarted(CommandStartedEvent $event): void + { + $this->handleOutput->__invoke($event); } public function commandSucceeded(CommandSucceededEvent $event): void { - printf("%s command succeeded\n", $event->getCommandName()); - printf("reply: %s\n", toJson($event->getReply())); - echo "\n"; + $this->handleOutput->__invoke($event); } public function commandFailed(CommandFailedEvent $event): void { - printf("%s command failed\n", $event->getCommandName()); - printf("reply: %s\n", toJson($event->getReply())); - - $exception = $event->getError(); - printf("exception: %s\n", $exception::class); - printf("exception.code: %d\n", $exception->getCode()); - printf("exception.message: %s\n", $exception->getMessage()); - echo "\n"; + $this->handleOutput->__invoke($event); } } $client = new Client(getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1/'); -$client->addSubscriber(new CommandLogger()); +$handleOutput = function (object $event): void { + switch ($event::class) { + case CommandStartedEvent::class: + printf("%s command started\n", $event->getCommandName()); + printf("command: %s\n", toJson($event->getCommand())); + break; + case CommandSucceededEvent::class: + printf("%s command succeeded\n", $event->getCommandName()); + printf("reply: %s\n", toJson($event->getReply())); + break; + case CommandFailedEvent::class: + printf("%s command failed\n", $event->getCommandName()); + printf("reply: %s\n", toJson($event->getReply())); + + $exception = $event->getError(); + printf("exception: %s\n", $exception::class); + printf("exception.code: %d\n", $exception->getCode()); + printf("exception.message: %s\n", $exception->getMessage()); + break; + default: + throw new Exception('Event type not supported'); + } + + echo "\n"; +}; + +$client->addSubscriber(new CommandLogger($handleOutput)); $collection = $client->test->command_logger; $collection->drop(); diff --git a/examples/sdam_logger.php b/examples/sdam_logger.php index 2edf82302..fe4d02ea6 100644 --- a/examples/sdam_logger.php +++ b/examples/sdam_logger.php @@ -3,6 +3,8 @@ namespace MongoDB\Examples; +use Closure; +use Exception; use MongoDB\BSON\Document; use MongoDB\Client; use MongoDB\Driver\Monitoring\SDAMSubscriber; @@ -28,102 +30,54 @@ function toJSON(array|object $document): string class SDAMLogger implements SDAMSubscriber { + /** @param Closure(object):void $handleOutput */ + public function __construct(private readonly Closure $handleOutput) + { + } + public function serverChanged(ServerChangedEvent $event): void { - printf( - "serverChanged: %s:%d changed from %s to %s\n", - $event->getHost(), - $event->getPort(), - $event->getPreviousDescription()->getType(), - $event->getNewDescription()->getType(), - ); - - printf("previous hello response: %s\n", toJson($event->getPreviousDescription()->getHelloResponse())); - printf("new hello response: %s\n", toJson($event->getNewDescription()->getHelloResponse())); - echo "\n"; + $this->handleOutput->__invoke($event); } public function serverClosed(ServerClosedEvent $event): void { - printf( - "serverClosed: %s:%d was removed from topology %s\n", - $event->getHost(), - $event->getPort(), - (string) $event->getTopologyId(), - ); - echo "\n"; + $this->handleOutput->__invoke($event); } public function serverHeartbeatFailed(ServerHeartbeatFailedEvent $event): void { - printf( - "serverHeartbeatFailed: %s:%d heartbeat failed after %dµs\n", - $event->getHost(), - $event->getPort(), - $event->getDurationMicros(), - ); - - $error = $event->getError(); - - printf("error: %s(%d): %s\n", $error::class, $error->getCode(), $error->getMessage()); - echo "\n"; + $this->handleOutput->__invoke($event); } public function serverHeartbeatStarted(ServerHeartbeatStartedEvent $event): void { - printf( - "serverHeartbeatStarted: %s:%d heartbeat started\n", - $event->getHost(), - $event->getPort(), - ); - echo "\n"; + $this->handleOutput->__invoke($event); } public function serverHeartbeatSucceeded(ServerHeartbeatSucceededEvent $event): void { - printf( - "serverHeartbeatSucceeded: %s:%d heartbeat succeeded after %dµs\n", - $event->getHost(), - $event->getPort(), - $event->getDurationMicros(), - ); - - printf("reply: %s\n", toJson($event->getReply())); - echo "\n"; + $this->handleOutput->__invoke($event); } public function serverOpening(ServerOpeningEvent $event): void { - printf( - "serverOpening: %s:%d was added to topology %s\n", - $event->getHost(), - $event->getPort(), - (string) $event->getTopologyId(), - ); - echo "\n"; + $this->handleOutput->__invoke($event); } public function topologyChanged(TopologyChangedEvent $event): void { - printf( - "topologyChanged: %s changed from %s to %s\n", - (string) $event->getTopologyId(), - $event->getPreviousDescription()->getType(), - $event->getNewDescription()->getType(), - ); - echo "\n"; + $this->handleOutput->__invoke($event); } public function topologyClosed(TopologyClosedEvent $event): void { - printf("topologyClosed: %s was closed\n", (string) $event->getTopologyId()); - echo "\n"; + $this->handleOutput->__invoke($event); } public function topologyOpening(TopologyOpeningEvent $event): void { - printf("topologyOpening: %s was opened\n", (string) $event->getTopologyId()); - echo "\n"; + $this->handleOutput->__invoke($event); } } @@ -132,7 +86,87 @@ public function topologyOpening(TopologyOpeningEvent $event): void * (including subscribers) are freed. */ $client = new Client(getenv('MONGODB_URI') ?: 'mongodb://127.0.0.1/', [], ['disableClientPersistence' => true]); -$client->getManager()->addSubscriber(new SDAMLogger()); +$handleOutput = function (object $event): void { + switch ($event::class) { + case ServerChangedEvent::class: + printf( + "serverChanged: %s:%d changed from %s to %s\n", + $event->getHost(), + $event->getPort(), + $event->getPreviousDescription()->getType(), + $event->getNewDescription()->getType(), + ); + + printf("previous hello response: %s\n", toJson($event->getPreviousDescription()->getHelloResponse())); + printf("new hello response: %s\n", toJson($event->getNewDescription()->getHelloResponse())); + break; + case ServerClosedEvent::class: + printf( + "serverClosed: %s:%d was removed from topology %s\n", + $event->getHost(), + $event->getPort(), + $event->getTopologyId()->__toString(), + ); + break; + case ServerHeartbeatFailedEvent::class: + printf( + "serverHeartbeatFailed: %s:%d heartbeat failed after %dµs\n", + $event->getHost(), + $event->getPort(), + $event->getDurationMicros(), + ); + + $error = $event->getError(); + + printf("error: %s(%d): %s\n", $error::class, $error->getCode(), $error->getMessage()); + break; + case ServerHeartbeatStartedEvent::class: + printf( + "serverHeartbeatStarted: %s:%d heartbeat started\n", + $event->getHost(), + $event->getPort(), + ); + break; + case ServerHeartbeatSucceededEvent::class: + printf( + "serverHeartbeatSucceeded: %s:%d heartbeat succeeded after %dµs\n", + $event->getHost(), + $event->getPort(), + $event->getDurationMicros(), + ); + + printf("reply: %s\n", toJson($event->getReply())); + break; + case ServerOpeningEvent::class: + printf( + "serverOpening: %s:%d was added to topology %s\n", + $event->getHost(), + $event->getPort(), + $event->getTopologyId()->__toString(), + ); + break; + case TopologyChangedEvent::class: + printf( + "topologyChanged: %s changed from %s to %s\n", + $event->getTopologyId()->__toString(), + $event->getPreviousDescription()->getType(), + $event->getNewDescription()->getType(), + ); + break; + case TopologyClosedEvent::class: + printf("topologyClosed: %s was closed\n", $event->getTopologyId()->__toString()); + break; + case TopologyOpeningEvent::class: + printf("topologyOpening: %s was opened\n", $event->getTopologyId()->__toString()); + break; + default: + throw new Exception('Event type not supported'); + } + + echo "\n"; +}; + +$client->getManager()->addSubscriber(new SDAMLogger($handleOutput)); $client->test->command(['ping' => 1]); diff --git a/generator/config/accumulator/concatArrays.yaml b/generator/config/accumulator/concatArrays.yaml new file mode 100644 index 000000000..baf61b5e3 --- /dev/null +++ b/generator/config/accumulator/concatArrays.yaml @@ -0,0 +1,30 @@ +# $schema: ../schema.json +name: $concatArrays +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/concatArrays/' +type: + - accumulator + - window + - resolvesToArray +encode: single +description: | + Concatenates arrays to return the concatenated array. +arguments: + - + name: array + type: + - resolvesToArray # of arrays + variadic: array + description: | + An array of expressions that resolve to an array. + If any argument resolves to a value of null or refers to a field that is missing, `$concatArrays` returns `null`. +tests: + - + name: 'Warehouse collection' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/concatArrays/#example' + pipeline: + - + $project: + items: + $concatArrays: + - '$instock' + - '$ordered' diff --git a/generator/config/accumulator/setUnion.yaml b/generator/config/accumulator/setUnion.yaml new file mode 100644 index 000000000..8c8419f9e --- /dev/null +++ b/generator/config/accumulator/setUnion.yaml @@ -0,0 +1,32 @@ +# $schema: ../schema.json +name: $setUnion +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/' +type: + - accumulator + - window + - resolvesToArray +encode: single +description: | + Takes two or more arrays and returns an array containing the elements that appear in any input array. +arguments: + - + name: array + type: + - resolvesToArray # of arrays + variadic: array + description: | + An array of expressions that resolve to an array. +tests: + - + name: 'Flowers collection' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/#example' + pipeline: + - + $project: + flowerFieldA: 1 + flowerFieldB: 1 + allValues: + $setUnion: + - "$flowerFieldA" + - "$flowerFieldB" + _id: 0 diff --git a/generator/config/expression/createObjectId.yaml b/generator/config/expression/createObjectId.yaml new file mode 100644 index 000000000..bab85d7f7 --- /dev/null +++ b/generator/config/expression/createObjectId.yaml @@ -0,0 +1,17 @@ +# $schema: ../schema.json +name: $createObjectId +link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/createObjectId/' +type: + - resolvesToObjectId +encode: object +description: | + Returns a random object ID +tests: + - + name: 'Example' + link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/createObjectId/#example' + pipeline: + - + $project: + objectId: + $createObjectId: {} diff --git a/generator/config/expression/replaceAll.yaml b/generator/config/expression/replaceAll.yaml index 74d479cb7..b13c58884 100644 --- a/generator/config/expression/replaceAll.yaml +++ b/generator/config/expression/replaceAll.yaml @@ -21,6 +21,7 @@ arguments: type: - resolvesToString - resolvesToNull + - resolvesToRegex description: | The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. - @@ -42,3 +43,13 @@ tests: input: '$item' find: 'blue paint' replacement: 'red paint' + - + name: 'Support regex search string' + pipeline: + - + $project: + item: + $replaceAll: + input: '123-456-7890' + find: !bson_regex '\d{3}' + replacement: 'xxx' diff --git a/generator/config/expression/split.yaml b/generator/config/expression/split.yaml index 1c6169910..98739c4ec 100644 --- a/generator/config/expression/split.yaml +++ b/generator/config/expression/split.yaml @@ -17,6 +17,7 @@ arguments: name: delimiter type: - resolvesToString + - resolvesToRegex description: | The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. tests: @@ -46,3 +47,12 @@ tests: - $sort: total_qty: -1 + - + name: 'Support regex delimiter' + pipeline: + - + $project: + split: + $split: + - 'abc' + - !bson_regex 'b' diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 7b0912775..fe0dc3819 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -133,14 +133,6 @@ - - - - - src - - - diff --git a/phpunit.evergreen.xml b/phpunit.evergreen.xml index b2e71bda3..42a1bfb41 100644 --- a/phpunit.evergreen.xml +++ b/phpunit.evergreen.xml @@ -25,6 +25,12 @@ + + + src + + + diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 5816fc227..4d99d5c0c 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -5,11 +5,7 @@ name]]> - - - - @@ -23,6 +19,9 @@ + + + @@ -218,14 +217,6 @@ - - - - - - - - @@ -248,6 +239,12 @@ + + + + + + @@ -328,20 +325,9 @@ - - - - - - - filesCollection->updateMany( - ['filename' => $filename], - ['$set' => ['filename' => $newFilename]], - )->getMatchedCount()]]> - @@ -379,6 +365,7 @@ getArrayCopy()]]> + getArrayCopy()]]> @@ -386,6 +373,7 @@ + @@ -394,6 +382,15 @@ + + + + + ]]> + + + ]]> + cursor->nextBatch]]> @@ -409,34 +406,6 @@ current()]]> - - - - - - - - - - - - - - - - - databases)]]> - - - - databases)]]> - - - - - - - index]]> @@ -586,6 +555,14 @@ + + + + + + executeBulkWriteCommand($this->bulkWriteCommand, $options)]]> + + @@ -600,9 +577,6 @@ - - options['typeMap']]]> - @@ -674,9 +648,6 @@ - - options['typeMap']]]> - @@ -687,9 +658,6 @@ - - options['typeMap']]]> - @@ -702,9 +670,6 @@ - - options['typeMap']]]> - @@ -734,11 +699,7 @@ options['codec']]]> options['typeMap']]]> - - - - @@ -771,15 +732,6 @@ value ?? null) : null]]> - - - - - - - - - @@ -829,44 +781,12 @@ - - - listCollections->execute($server), $this->databaseName)]]> - - - - - listDatabases->execute($server))]]> - - - - - databaseName . '.' . $this->collectionName)]]> - - - - - result->collection]]> - result->db]]> - options['typeMap']]]> - - - - - - - - - - - - options['typeMap']]]> @@ -878,9 +798,6 @@ - - options['typeMap']]]> - diff --git a/psalm.xml.dist b/psalm.xml.dist index ba737bd6a..28efbec86 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -19,6 +19,15 @@ + + + + + + + + + diff --git a/rector.php b/rector.php index f184fd9c0..61fe76b6d 100644 --- a/rector.php +++ b/rector.php @@ -7,6 +7,8 @@ use Rector\DeadCode\Rector\ClassLike\RemoveAnnotationRector; use Rector\Php70\Rector\StmtsAwareInterface\IfIssetToCoalescingRector; use Rector\Php71\Rector\FuncCall\RemoveExtraParametersRector; +use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector; +use Rector\Php80\Rector\Class_\StringableForToStringRector; use Rector\Php80\Rector\Switch_\ChangeSwitchToMatchRector; use Rector\Renaming\Rector\Cast\RenameCastRector; use Rector\Renaming\ValueObject\RenameCast; @@ -18,11 +20,8 @@ __DIR__ . '/tests', __DIR__ . '/tools', ]) - ->withPhpSets(php74: true) + ->withPhpSets(php80: true) ->withComposerBased(phpunit: true) - ->withRules([ - ChangeSwitchToMatchRector::class, - ]) // All classes are public API by default, unless marked with @internal. ->withConfiguredRule(RemoveAnnotationRector::class, ['api']) // Fix PHP 8.5 deprecations @@ -42,6 +41,10 @@ ChangeSwitchToMatchRector::class => [ __DIR__ . '/tests/SpecTests/Operation.php', ], + ClassPropertyAssignToConstructorPromotionRector::class, + StringableForToStringRector::class => [ + __DIR__ . '/src/Model/IndexInput.php', + ], ]) // phpcs:enable ->withImportNames(importNames: false, removeUnusedImports: true); diff --git a/src/Builder/Accumulator/ConcatArraysAccumulator.php b/src/Builder/Accumulator/ConcatArraysAccumulator.php new file mode 100644 index 000000000..90ab96fa9 --- /dev/null +++ b/src/Builder/Accumulator/ConcatArraysAccumulator.php @@ -0,0 +1,57 @@ + 'array']; + + /** + * @var list $array An array of expressions that resolve to an array. + * If any argument resolves to a value of null or refers to a field that is missing, `$concatArrays` returns `null`. + */ + public readonly array $array; + + /** + * @param BSONArray|PackedArray|ResolvesToArray|array|string ...$array An array of expressions that resolve to an array. + * If any argument resolves to a value of null or refers to a field that is missing, `$concatArrays` returns `null`. + * @no-named-arguments + */ + public function __construct(PackedArray|ResolvesToArray|BSONArray|array|string ...$array) + { + if (\count($array) < 1) { + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $array, got %d.', 1, \count($array))); + } + + if (! array_is_list($array)) { + throw new InvalidArgumentException('Expected $array arguments to be a list (array), named arguments are not supported'); + } + + $this->array = $array; + } +} diff --git a/src/Builder/Accumulator/FactoryTrait.php b/src/Builder/Accumulator/FactoryTrait.php index 9e9257549..486eb43a5 100644 --- a/src/Builder/Accumulator/FactoryTrait.php +++ b/src/Builder/Accumulator/FactoryTrait.php @@ -117,6 +117,20 @@ public static function bottomN( return new BottomNAccumulator($n, $sortBy, $output); } + /** + * Concatenates arrays to return the concatenated array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/concatArrays/ + * @no-named-arguments + * @param BSONArray|PackedArray|ResolvesToArray|array|string ...$array An array of expressions that resolve to an array. + * If any argument resolves to a value of null or refers to a field that is missing, `$concatArrays` returns `null`. + */ + public static function concatArrays( + PackedArray|ResolvesToArray|BSONArray|array|string ...$array, + ): ConcatArraysAccumulator { + return new ConcatArraysAccumulator(...$array); + } + /** * Returns the number of documents in the group or window. * Distinct from the $count pipeline stage. @@ -453,6 +467,18 @@ public static function rank(): RankAccumulator return new RankAccumulator(); } + /** + * Takes two or more arrays and returns an array containing the elements that appear in any input array. + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/ + * @no-named-arguments + * @param BSONArray|PackedArray|ResolvesToArray|array|string ...$array An array of expressions that resolve to an array. + */ + public static function setUnion(PackedArray|ResolvesToArray|BSONArray|array|string ...$array): SetUnionAccumulator + { + return new SetUnionAccumulator(...$array); + } + /** * Returns the value from an expression applied to a document in a specified position relative to the current document in the $setWindowFields stage partition. * New in MongoDB 5.0. diff --git a/src/Builder/Accumulator/SetUnionAccumulator.php b/src/Builder/Accumulator/SetUnionAccumulator.php new file mode 100644 index 000000000..e8ee446cc --- /dev/null +++ b/src/Builder/Accumulator/SetUnionAccumulator.php @@ -0,0 +1,53 @@ + 'array']; + + /** @var list $array An array of expressions that resolve to an array. */ + public readonly array $array; + + /** + * @param BSONArray|PackedArray|ResolvesToArray|array|string ...$array An array of expressions that resolve to an array. + * @no-named-arguments + */ + public function __construct(PackedArray|ResolvesToArray|BSONArray|array|string ...$array) + { + if (\count($array) < 1) { + throw new InvalidArgumentException(\sprintf('Expected at least %d values for $array, got %d.', 1, \count($array))); + } + + if (! array_is_list($array)) { + throw new InvalidArgumentException('Expected $array arguments to be a list (array), named arguments are not supported'); + } + + $this->array = $array; + } +} diff --git a/src/Builder/BuilderEncoder.php b/src/Builder/BuilderEncoder.php index 833409b07..e9121a7d0 100644 --- a/src/Builder/BuilderEncoder.php +++ b/src/Builder/BuilderEncoder.php @@ -32,7 +32,9 @@ use WeakReference; use function array_key_exists; +use function is_array; use function is_object; +use function iterator_to_array; /** @template-implements Encoder */ final class BuilderEncoder implements Encoder @@ -46,11 +48,15 @@ final class BuilderEncoder implements Encoder /** @var array */ private array $cachedEncoders = []; - /** @param array $encoders */ - public function __construct(array $encoders = []) + /** @param iterable $encoders */ + public function __construct(iterable $encoders = []) { $self = WeakReference::create($this); + if (! is_array($encoders)) { + $encoders = iterator_to_array($encoders); + } + $this->encoders = $encoders + [ Pipeline::class => new PipelineEncoder($self), Variable::class => new VariableEncoder(), diff --git a/src/Builder/Expression/CreateObjectIdOperator.php b/src/Builder/Expression/CreateObjectIdOperator.php new file mode 100644 index 000000000..eef57b3ca --- /dev/null +++ b/src/Builder/Expression/CreateObjectIdOperator.php @@ -0,0 +1,28 @@ +input = $input; diff --git a/src/Builder/Expression/SplitOperator.php b/src/Builder/Expression/SplitOperator.php index 2e54ef5a5..30d306259 100644 --- a/src/Builder/Expression/SplitOperator.php +++ b/src/Builder/Expression/SplitOperator.php @@ -8,6 +8,7 @@ namespace MongoDB\Builder\Expression; +use MongoDB\BSON\Regex; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; @@ -26,15 +27,17 @@ final class SplitOperator implements ResolvesToArray, OperatorInterface /** @var ResolvesToString|string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. */ public readonly ResolvesToString|string $string; - /** @var ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ - public readonly ResolvesToString|string $delimiter; + /** @var Regex|ResolvesToRegex|ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ + public readonly Regex|ResolvesToRegex|ResolvesToString|string $delimiter; /** * @param ResolvesToString|string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. - * @param ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. + * @param Regex|ResolvesToRegex|ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ - public function __construct(ResolvesToString|string $string, ResolvesToString|string $delimiter) - { + public function __construct( + ResolvesToString|string $string, + Regex|ResolvesToRegex|ResolvesToString|string $delimiter, + ) { $this->string = $string; $this->delimiter = $delimiter; } diff --git a/src/BulkWriteResult.php b/src/BulkWriteResult.php index 4fb556e0b..43c60ea2d 100644 --- a/src/BulkWriteResult.php +++ b/src/BulkWriteResult.php @@ -17,19 +17,16 @@ namespace MongoDB; +use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\WriteResult; -use MongoDB\Exception\BadMethodCallException; /** * Result class for a bulk write operation. */ class BulkWriteResult { - private bool $isAcknowledged; - public function __construct(private WriteResult $writeResult, private array $insertedIds) { - $this->isAcknowledged = $writeResult->isAcknowledged(); } /** @@ -38,16 +35,11 @@ public function __construct(private WriteResult $writeResult, private array $ins * This method should only be called if the write was acknowledged. * * @see BulkWriteResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getDeletedCount() + public function getDeletedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getDeletedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getDeletedCount(); } /** @@ -56,16 +48,11 @@ public function getDeletedCount() * This method should only be called if the write was acknowledged. * * @see BulkWriteResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getInsertedCount() + public function getInsertedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getInsertedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getInsertedCount(); } /** @@ -76,10 +63,8 @@ public function getInsertedCount() * the driver did not generate an ID), the index will contain its "_id" * field value. Any driver-generated ID will be a MongoDB\BSON\ObjectId * instance. - * - * @return array */ - public function getInsertedIds() + public function getInsertedIds(): array { return $this->insertedIds; } @@ -90,16 +75,11 @@ public function getInsertedIds() * This method should only be called if the write was acknowledged. * * @see BulkWriteResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getMatchedCount() + public function getMatchedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getMatchedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getMatchedCount(); } /** @@ -111,16 +91,11 @@ public function getMatchedCount() * This method should only be called if the write was acknowledged. * * @see BulkWriteResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getModifiedCount() + public function getModifiedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getModifiedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getModifiedCount(); } /** @@ -129,16 +104,11 @@ public function getModifiedCount() * This method should only be called if the write was acknowledged. * * @see BulkWriteResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getUpsertedCount() + public function getUpsertedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getUpsertedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getUpsertedCount(); } /** @@ -152,16 +122,11 @@ public function getUpsertedCount() * This method should only be called if the write was acknowledged. * * @see BulkWriteResult::isAcknowledged() - * @return array - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getUpsertedIds() + public function getUpsertedIds(): array { - if ($this->isAcknowledged) { - return $this->writeResult->getUpsertedIds(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getUpsertedIds(); } /** @@ -169,11 +134,9 @@ public function getUpsertedIds() * * If the update was not acknowledged, other fields from the WriteResult * (e.g. matchedCount) will be undefined. - * - * @return boolean */ - public function isAcknowledged() + public function isAcknowledged(): bool { - return $this->isAcknowledged; + return $this->writeResult->isAcknowledged(); } } diff --git a/src/ChangeStream.php b/src/ChangeStream.php index 55f25b031..b9f8aed1c 100644 --- a/src/ChangeStream.php +++ b/src/ChangeStream.php @@ -21,22 +21,16 @@ use MongoDB\BSON\Document; use MongoDB\BSON\Int64; use MongoDB\Codec\DocumentCodec; -use MongoDB\Driver\CursorId; use MongoDB\Driver\Exception\ConnectionException; use MongoDB\Driver\Exception\RuntimeException; use MongoDB\Driver\Exception\ServerException; use MongoDB\Exception\BadMethodCallException; use MongoDB\Exception\ResumeTokenException; use MongoDB\Model\ChangeStreamIterator; -use ReturnTypeWillChange; use function assert; use function call_user_func; use function in_array; -use function sprintf; -use function trigger_error; - -use const E_USER_DEPRECATED; /** * Iterator for a change stream. @@ -49,11 +43,7 @@ */ class ChangeStream implements Iterator { - /** - * @deprecated 1.4 - * @todo make this constant private in 2.0 (see: PHPLIB-360) - */ - public const CURSOR_NOT_FOUND = 43; + private const CURSOR_NOT_FOUND = 43; private const RESUMABLE_ERROR_CODES = [ 6, // HostUnreachable @@ -88,12 +78,8 @@ class ChangeStream implements Iterator */ private bool $hasAdvanced = false; - /** - * @see https://php.net/iterator.current - * @return array|object|null - */ - #[ReturnTypeWillChange] - public function current() + /** @see https://php.net/iterator.current */ + public function current(): array|object|null { $value = $this->iterator->current(); @@ -106,26 +92,9 @@ public function current() return $this->codec->decode($value); } - /** - * @return CursorId|Int64 - * @psalm-return ($asInt64 is true ? Int64 : CursorId) - */ - #[ReturnTypeWillChange] - public function getCursorId(bool $asInt64 = false) + public function getCursorId(): Int64 { - if (! $asInt64) { - @trigger_error( - sprintf( - 'The method "%s" will no longer return a "%s" instance in the future. Pass "true" as argument to change to the new behavior and receive a "%s" instance instead.', - __METHOD__, - CursorId::class, - Int64::class, - ), - E_USER_DEPRECATED, - ); - } - - return $this->iterator->getInnerIterator()->getId($asInt64); + return $this->iterator->getInnerIterator()->getId(); } /** @@ -134,20 +103,14 @@ public function getCursorId(bool $asInt64 = false) * Null may be returned if no change documents have been iterated and the * server did not include a postBatchResumeToken in its aggregate or getMore * command response. - * - * @return array|object|null */ - public function getResumeToken() + public function getResumeToken(): array|object|null { return $this->iterator->getResumeToken(); } - /** - * @see https://php.net/iterator.key - * @return int|null - */ - #[ReturnTypeWillChange] - public function key() + /** @see https://php.net/iterator.key */ + public function key(): ?int { if ($this->valid()) { return $this->key; @@ -158,11 +121,9 @@ public function key() /** * @see https://php.net/iterator.next - * @return void * @throws ResumeTokenException */ - #[ReturnTypeWillChange] - public function next() + public function next(): void { try { $this->iterator->next(); @@ -174,11 +135,9 @@ public function next() /** * @see https://php.net/iterator.rewind - * @return void * @throws ResumeTokenException */ - #[ReturnTypeWillChange] - public function rewind() + public function rewind(): void { try { $this->iterator->rewind(); @@ -191,12 +150,8 @@ public function rewind() } } - /** - * @see https://php.net/iterator.valid - * @return boolean - */ - #[ReturnTypeWillChange] - public function valid() + /** @see https://php.net/iterator.valid */ + public function valid(): bool { return $this->iterator->valid(); } @@ -218,7 +173,7 @@ public function __construct(private ChangeStreamIterator $iterator, callable $re /** * Determines if an exception is a resumable error. * - * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resumable-error + * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.md#resumable-error */ private function isResumableError(RuntimeException $exception): bool { @@ -255,7 +210,8 @@ private function onIteration(bool $incrementKey): void * have been received in the last response. Therefore, we can unset the * resumeCallable. This will free any reference to Watch as well as the * only reference to any implicit session created therein. */ - if ((string) $this->getCursorId(true) === '0') { + // Use a type-unsafe comparison to compare with Int64 instances + if ($this->getCursorId() == 0) { $this->resumeCallable = null; } diff --git a/src/Client.php b/src/Client.php index c9d36a6c8..938da2f1a 100644 --- a/src/Client.php +++ b/src/Client.php @@ -24,6 +24,8 @@ use MongoDB\Builder\BuilderEncoder; use MongoDB\Builder\Pipeline; use MongoDB\Codec\Encoder; +use MongoDB\Driver\BulkWriteCommand; +use MongoDB\Driver\BulkWriteCommandResult; use MongoDB\Driver\ClientEncryption; use MongoDB\Driver\Exception\InvalidArgumentException as DriverInvalidArgumentException; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; @@ -39,22 +41,20 @@ use MongoDB\Model\BSONArray; use MongoDB\Model\BSONDocument; use MongoDB\Model\DatabaseInfo; +use MongoDB\Operation\ClientBulkWriteCommand; use MongoDB\Operation\DropDatabase; use MongoDB\Operation\ListDatabaseNames; use MongoDB\Operation\ListDatabases; use MongoDB\Operation\Watch; use stdClass; +use Stringable; use Throwable; use function array_diff_key; use function is_array; use function is_string; -use function sprintf; -use function trigger_error; -use const E_USER_DEPRECATED; - -class Client +class Client implements Stringable { public const DEFAULT_URI = 'mongodb://127.0.0.1/'; @@ -150,9 +150,8 @@ public function __construct(?string $uri = null, array $uriOptions = [], array $ * Return internal properties for debugging purposes. * * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo - * @return array */ - public function __debugInfo() + public function __debugInfo(): array { return [ 'manager' => $this->manager, @@ -173,19 +172,16 @@ public function __debugInfo() * @see https://php.net/oop5.overloading#object.get * @see https://php.net/types.string#language.types.string.parsing.complex * @param string $databaseName Name of the database to select - * @return Database */ - public function __get(string $databaseName) + public function __get(string $databaseName): Database { return $this->getDatabase($databaseName); } /** * Return the connection string (i.e. URI). - * - * @return string */ - public function __toString() + public function __toString(): string { return $this->uri; } @@ -200,14 +196,38 @@ final public function addSubscriber(Subscriber $subscriber): void $this->manager->addSubscriber($subscriber); } + /** + * Executes multiple write operations across multiple namespaces. + * + * @param BulkWriteCommand|ClientBulkWrite $bulk Assembled bulk write command or builder + * @param array $options Additional options + * @throws UnsupportedException if options are unsupported on the selected server + * @throws InvalidArgumentException for parameter/option parsing errors + * @throws DriverRuntimeException for other driver errors (e.g. connection errors) + * @see ClientBulkWriteCommand::__construct() for supported options + */ + public function bulkWrite(BulkWriteCommand|ClientBulkWrite $bulk, array $options = []): BulkWriteCommandResult + { + if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { + $options['writeConcern'] = $this->writeConcern; + } + + if ($bulk instanceof ClientBulkWrite) { + $bulk = $bulk->bulkWriteCommand; + } + + $operation = new ClientBulkWriteCommand($bulk, $options); + $server = select_server_for_write($this->manager, $options); + + return $operation->execute($server); + } + /** * Returns a ClientEncryption instance for explicit encryption and decryption * * @param array $options Encryption options - * - * @return ClientEncryption */ - public function createClientEncryption(array $options) + public function createClientEncryption(array $options): ClientEncryption { $options = $this->prepareEncryptionOptions($options); @@ -220,19 +240,12 @@ public function createClientEncryption(array $options) * @see DropDatabase::__construct() for supported options * @param string $databaseName Database name * @param array $options Additional options - * @return array|object Command result document * @throws UnsupportedException if options are unsupported on the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function dropDatabase(string $databaseName, array $options = []) + public function dropDatabase(string $databaseName, array $options = []): void { - if (! isset($options['typeMap'])) { - $options['typeMap'] = $this->typeMap; - } else { - @trigger_error(sprintf('The function %s() will return nothing in mongodb/mongodb v2.0, the "typeMap" option is deprecated', __FUNCTION__), E_USER_DEPRECATED); - } - $server = select_server_for_write($this->manager, $options); if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { @@ -241,7 +254,7 @@ public function dropDatabase(string $databaseName, array $options = []) $operation = new DropDatabase($databaseName, $options); - return $operation->execute($server); + $operation->execute($server); } /** @@ -277,10 +290,8 @@ public function getDatabase(string $databaseName, array $options = []): Database /** * Return the Manager. - * - * @return Manager */ - public function getManager() + public function getManager(): Manager { return $this->manager; } @@ -289,29 +300,24 @@ public function getManager() * Return the read concern for this client. * * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php - * @return ReadConcern */ - public function getReadConcern() + public function getReadConcern(): ReadConcern { return $this->readConcern; } /** * Return the read preference for this client. - * - * @return ReadPreference */ - public function getReadPreference() + public function getReadPreference(): ReadPreference { return $this->readPreference; } /** * Return the type map for this client. - * - * @return array */ - public function getTypeMap() + public function getTypeMap(): array { return $this->typeMap; } @@ -320,9 +326,8 @@ public function getTypeMap() * Return the write concern for this client. * * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php - * @return WriteConcern */ - public function getWriteConcern() + public function getWriteConcern(): WriteConcern { return $this->writeConcern; } @@ -331,6 +336,7 @@ public function getWriteConcern() * List database names. * * @see ListDatabaseNames::__construct() for supported options + * @return Iterator * @throws UnexpectedValueException if the command response was malformed * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) @@ -352,7 +358,7 @@ public function listDatabaseNames(array $options = []): Iterator * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function listDatabases(array $options = []) + public function listDatabases(array $options = []): Iterator { $operation = new ListDatabases($options); $server = select_server($this->manager, $options); @@ -377,10 +383,9 @@ final public function removeSubscriber(Subscriber $subscriber): void * @param string $databaseName Name of the database containing the collection * @param string $collectionName Name of the collection to select * @param array $options Collection constructor options - * @return Collection * @throws InvalidArgumentException for parameter/option parsing errors */ - public function selectCollection(string $databaseName, string $collectionName, array $options = []) + public function selectCollection(string $databaseName, string $collectionName, array $options = []): Collection { return $this->getCollection($databaseName, $collectionName, $options); } @@ -391,10 +396,9 @@ public function selectCollection(string $databaseName, string $collectionName, a * @see Database::__construct() for supported options * @param string $databaseName Name of the database to select * @param array $options Database constructor options - * @return Database * @throws InvalidArgumentException for parameter/option parsing errors */ - public function selectDatabase(string $databaseName, array $options = []) + public function selectDatabase(string $databaseName, array $options = []): Database { return $this->getDatabase($databaseName, $options); } @@ -404,9 +408,8 @@ public function selectDatabase(string $databaseName, array $options = []) * * @see https://php.net/manual/en/mongodb-driver-manager.startsession.php * @param array $options Session options - * @return Session */ - public function startSession(array $options = []) + public function startSession(array $options = []): Session { return $this->manager->startSession($options); } @@ -417,10 +420,9 @@ public function startSession(array $options = []) * @see Watch::__construct() for supported options * @param array $pipeline Aggregation pipeline * @param array $options Command options - * @return ChangeStream * @throws InvalidArgumentException for parameter/option parsing errors */ - public function watch(array $pipeline = [], array $options = []) + public function watch(array $pipeline = [], array $options = []): ChangeStream { if (is_builder_pipeline($pipeline)) { $pipeline = new Pipeline(...$pipeline); diff --git a/src/ClientBulkWrite.php b/src/ClientBulkWrite.php new file mode 100644 index 000000000..f11452d3a --- /dev/null +++ b/src/ClientBulkWrite.php @@ -0,0 +1,249 @@ + */ + private readonly Encoder $builderEncoder, + private readonly ?DocumentCodec $codec, + ) { + } + + #[NoDiscard] + public static function createWithCollection(Collection $collection, array $options = []): self + { + $options += ['ordered' => true]; + + if (isset($options['bypassDocumentValidation']) && ! is_bool($options['bypassDocumentValidation'])) { + throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $options['bypassDocumentValidation'], 'boolean'); + } + + if (isset($options['let']) && ! is_document($options['let'])) { + throw InvalidArgumentException::expectedDocumentType('"let" option', $options['let']); + } + + if (! is_bool($options['ordered'])) { + throw InvalidArgumentException::invalidType('"ordered" option', $options['ordered'], 'boolean'); + } + + if (isset($options['verboseResults']) && ! is_bool($options['verboseResults'])) { + throw InvalidArgumentException::invalidType('"verboseResults" option', $options['verboseResults'], 'boolean'); + } + + return new self( + new BulkWriteCommand($options), + $collection->getManager(), + $collection->getNamespace(), + $collection->getBuilderEncoder(), + $collection->getCodec(), + ); + } + + public function deleteMany(array|object $filter, array $options = []): self + { + $filter = $this->builderEncoder->encodeIfSupported($filter); + + if (isset($options['collation']) && ! is_document($options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + } + + if (isset($options['hint']) && ! is_string($options['hint']) && ! is_document($options['hint'])) { + throw InvalidArgumentException::expectedDocumentOrStringType('"hint" option', $options['hint']); + } + + $this->bulkWriteCommand->deleteMany($this->namespace, $filter, $options); + + return $this; + } + + public function deleteOne(array|object $filter, array $options = []): self + { + $filter = $this->builderEncoder->encodeIfSupported($filter); + + if (isset($options['collation']) && ! is_document($options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + } + + if (isset($options['hint']) && ! is_string($options['hint']) && ! is_document($options['hint'])) { + throw InvalidArgumentException::expectedDocumentOrStringType('"hint" option', $options['hint']); + } + + $this->bulkWriteCommand->deleteOne($this->namespace, $filter, $options); + + return $this; + } + + public function insertOne(array|object $document, mixed &$id = null): self + { + if ($this->codec) { + $document = $this->codec->encode($document); + } + + // Capture the document's _id, which may have been generated, in an optional output variable + /** @var mixed $id */ + $id = $this->bulkWriteCommand->insertOne($this->namespace, $document); + + return $this; + } + + public function replaceOne(array|object $filter, array|object $replacement, array $options = []): self + { + $filter = $this->builderEncoder->encodeIfSupported($filter); + + if ($this->codec) { + $replacement = $this->codec->encode($replacement); + } + + // Treat empty arrays as replacement documents for BC + if ($replacement === []) { + $replacement = (object) $replacement; + } + + if (is_first_key_operator($replacement)) { + throw new InvalidArgumentException('First key in $replacement is an update operator'); + } + + if (is_pipeline($replacement, true)) { + throw new InvalidArgumentException('$replacement is an update pipeline'); + } + + if (isset($options['collation']) && ! is_document($options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + } + + if (isset($options['hint']) && ! is_string($options['hint']) && ! is_document($options['hint'])) { + throw InvalidArgumentException::expectedDocumentOrStringType('"hint" option', $options['hint']); + } + + if (isset($options['sort']) && ! is_document($options['sort'])) { + throw InvalidArgumentException::expectedDocumentType('"sort" option', $options['sort']); + } + + if (isset($options['upsert']) && ! is_bool($options['upsert'])) { + throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean'); + } + + $this->bulkWriteCommand->replaceOne($this->namespace, $filter, $replacement, $options); + + return $this; + } + + public function updateMany(array|object $filter, array|object $update, array $options = []): self + { + $filter = $this->builderEncoder->encodeIfSupported($filter); + $update = $this->builderEncoder->encodeIfSupported($update); + + if (! is_first_key_operator($update) && ! is_pipeline($update)) { + throw new InvalidArgumentException('Expected update operator(s) or non-empty pipeline for $update'); + } + + if (isset($options['arrayFilters']) && ! is_array($options['arrayFilters'])) { + throw InvalidArgumentException::invalidType('"arrayFilters" option', $options['arrayFilters'], 'array'); + } + + if (isset($options['collation']) && ! is_document($options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + } + + if (isset($options['hint']) && ! is_string($options['hint']) && ! is_document($options['hint'])) { + throw InvalidArgumentException::expectedDocumentOrStringType('"hint" option', $options['hint']); + } + + if (isset($options['upsert']) && ! is_bool($options['upsert'])) { + throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean'); + } + + $this->bulkWriteCommand->updateMany($this->namespace, $filter, $update, $options); + + return $this; + } + + public function updateOne(array|object $filter, array|object $update, array $options = []): self + { + $filter = $this->builderEncoder->encodeIfSupported($filter); + $update = $this->builderEncoder->encodeIfSupported($update); + + if (! is_first_key_operator($update) && ! is_pipeline($update)) { + throw new InvalidArgumentException('Expected update operator(s) or non-empty pipeline for $update'); + } + + if (isset($options['arrayFilters']) && ! is_array($options['arrayFilters'])) { + throw InvalidArgumentException::invalidType('"arrayFilters" option', $options['arrayFilters'], 'array'); + } + + if (isset($options['collation']) && ! is_document($options['collation'])) { + throw InvalidArgumentException::expectedDocumentType('"collation" option', $options['collation']); + } + + if (isset($options['hint']) && ! is_string($options['hint']) && ! is_document($options['hint'])) { + throw InvalidArgumentException::expectedDocumentOrStringType('"hint" option', $options['hint']); + } + + if (isset($options['sort']) && ! is_document($options['sort'])) { + throw InvalidArgumentException::expectedDocumentType('"sort" option', $options['sort']); + } + + if (isset($options['upsert']) && ! is_bool($options['upsert'])) { + throw InvalidArgumentException::invalidType('"upsert" option', $options['upsert'], 'boolean'); + } + + $this->bulkWriteCommand->updateOne($this->namespace, $filter, $update, $options); + + return $this; + } + + #[NoDiscard] + public function withCollection(Collection $collection): self + { + /* Prohibit mixing Collections associated with different Manager + * objects. This is not technically necessary, since the Collection is + * only used to derive a namespace and encoding options; however, it + * may prevent a user from inadvertently mixing writes destined for + * different deployments. */ + if ($this->manager !== $collection->getManager()) { + throw new InvalidArgumentException('$collection is associated with a different MongoDB\Driver\Manager'); + } + + return new self( + $this->bulkWriteCommand, + $this->manager, + $collection->getNamespace(), + $collection->getBuilderEncoder(), + $collection->getCodec(), + ); + } +} diff --git a/src/Codec/DecodeIfSupported.php b/src/Codec/DecodeIfSupported.php index 7fd768011..ea8dde15c 100644 --- a/src/Codec/DecodeIfSupported.php +++ b/src/Codec/DecodeIfSupported.php @@ -30,17 +30,13 @@ abstract public function canDecode(mixed $value): bool; /** * @psalm-param BSONType $value - * @return mixed * @psalm-return NativeType * @throws UnsupportedValueException if the decoder does not support the value */ - abstract public function decode(mixed $value); + abstract public function decode(mixed $value): mixed; - /** - * @return mixed - * @psalm-return ($value is BSONType ? NativeType : $value) - */ - public function decodeIfSupported(mixed $value) + /** @psalm-return ($value is BSONType ? NativeType : $value) */ + public function decodeIfSupported(mixed $value): mixed { return $this->canDecode($value) ? $this->decode($value) : $value; } diff --git a/src/Codec/Decoder.php b/src/Codec/Decoder.php index 432fb2dd7..37ff9b263 100644 --- a/src/Codec/Decoder.php +++ b/src/Codec/Decoder.php @@ -37,11 +37,10 @@ public function canDecode(mixed $value): bool; * should throw an exception. * * @psalm-param BSONType $value - * @return mixed * @psalm-return NativeType * @throws UnsupportedValueException if the decoder does not support the value */ - public function decode(mixed $value); + public function decode(mixed $value): mixed; /** * Decodes a given value if supported, otherwise returns the value as-is. @@ -49,8 +48,7 @@ public function decode(mixed $value); * The DecodeIfSupported trait provides a default implementation of this * method. * - * @return mixed * @psalm-return ($value is BSONType ? NativeType : $value) */ - public function decodeIfSupported(mixed $value); + public function decodeIfSupported(mixed $value): mixed; } diff --git a/src/Codec/EncodeIfSupported.php b/src/Codec/EncodeIfSupported.php index 33823cfd6..2ce1fcf53 100644 --- a/src/Codec/EncodeIfSupported.php +++ b/src/Codec/EncodeIfSupported.php @@ -30,17 +30,13 @@ abstract public function canEncode(mixed $value): bool; /** * @psalm-param NativeType $value - * @return mixed * @psalm-return BSONType * @throws UnsupportedValueException if the encoder does not support the value */ - abstract public function encode(mixed $value); + abstract public function encode(mixed $value): mixed; - /** - * @return mixed - * @psalm-return ($value is NativeType ? BSONType : $value) - */ - public function encodeIfSupported(mixed $value) + /** @psalm-return ($value is NativeType ? BSONType : $value) */ + public function encodeIfSupported(mixed $value): mixed { return $this->canEncode($value) ? $this->encode($value) : $value; } diff --git a/src/Codec/Encoder.php b/src/Codec/Encoder.php index 0cd0d58cb..c8ee0917b 100644 --- a/src/Codec/Encoder.php +++ b/src/Codec/Encoder.php @@ -37,11 +37,10 @@ public function canEncode(mixed $value): bool; * should throw an exception. * * @psalm-param NativeType $value - * @return mixed * @psalm-return BSONType * @throws UnsupportedValueException if the encoder does not support the value */ - public function encode(mixed $value); + public function encode(mixed $value): mixed; /** * Encodes a given value if supported, otherwise returns the value as-is. @@ -49,8 +48,7 @@ public function encode(mixed $value); * The EncodeIfSupported trait provides a default implementation of this * method. * - * @return mixed * @psalm-return ($value is NativeType ? BSONType : $value) */ - public function encodeIfSupported(mixed $value); + public function encodeIfSupported(mixed $value): mixed; } diff --git a/src/Collection.php b/src/Collection.php index f9bfba63c..8cc513e9a 100644 --- a/src/Collection.php +++ b/src/Collection.php @@ -20,7 +20,6 @@ use Countable; use Iterator; use MongoDB\BSON\Document; -use MongoDB\BSON\JavascriptInterface; use MongoDB\BSON\PackedArray; use MongoDB\Builder\BuilderEncoder; use MongoDB\Builder\Pipeline; @@ -63,7 +62,6 @@ use MongoDB\Operation\InsertOne; use MongoDB\Operation\ListIndexes; use MongoDB\Operation\ListSearchIndexes; -use MongoDB\Operation\MapReduce; use MongoDB\Operation\RenameCollection; use MongoDB\Operation\ReplaceOne; use MongoDB\Operation\UpdateMany; @@ -71,6 +69,7 @@ use MongoDB\Operation\UpdateSearchIndex; use MongoDB\Operation\Watch; use stdClass; +use Stringable; use function array_diff_key; use function array_intersect_key; @@ -78,13 +77,9 @@ use function current; use function is_array; use function is_bool; -use function sprintf; use function strlen; -use function trigger_error; -use const E_USER_DEPRECATED; - -class Collection +class Collection implements Stringable { private const DEFAULT_TYPE_MAP = [ 'array' => BSONArray::class, @@ -193,9 +188,8 @@ public function __construct(private Manager $manager, private string $databaseNa * Return internal properties for debugging purposes. * * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo - * @return array */ - public function __debugInfo() + public function __debugInfo(): array { return [ 'builderEncoder' => $this->builderEncoder, @@ -214,9 +208,8 @@ public function __debugInfo() * Return the collection namespace (e.g. "db.collection"). * * @see https://mongodb.com/docs/manual/core/databases-and-collections/ - * @return string */ - public function __toString() + public function __toString(): string { return $this->databaseName . '.' . $this->collectionName; } @@ -225,17 +218,16 @@ public function __toString() * Executes an aggregation framework pipeline on the collection. * * @see Aggregate::__construct() for supported options - * @param array $pipeline Aggregation pipeline - * @param array $options Command options - * @return CursorInterface&Iterator + * @param array|Pipeline $pipeline Aggregation pipeline + * @param array $options Command options * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function aggregate(array $pipeline, array $options = []) + public function aggregate(array|Pipeline $pipeline, array $options = []): CursorInterface { - if (is_builder_pipeline($pipeline)) { + if (is_array($pipeline) && is_builder_pipeline($pipeline)) { $pipeline = new Pipeline(...$pipeline); } @@ -273,12 +265,11 @@ public function aggregate(array $pipeline, array $options = []) * @see BulkWrite::__construct() for supported options * @param array[] $operations List of write operations * @param array $options Command options - * @return BulkWriteResult * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function bulkWrite(array $operations, array $options = []) + public function bulkWrite(array $operations, array $options = []): BulkWriteResult { $options = $this->inheritBuilderEncoder($options); $options = $this->inheritWriteOptions($options); @@ -295,7 +286,6 @@ public function bulkWrite(array $operations, array $options = []) * @see Count::__construct() for supported options * @param array|object $filter Query by which to filter documents * @param array $options Command options - * @return integer * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors @@ -303,7 +293,7 @@ public function bulkWrite(array $operations, array $options = []) * * @deprecated 1.4 */ - public function count(array|object $filter = [], array $options = []) + public function count(array|object $filter = [], array $options = []): int { $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritReadOptions($options); @@ -319,13 +309,12 @@ public function count(array|object $filter = [], array $options = []) * @see CountDocuments::__construct() for supported options * @param array|object $filter Query by which to filter documents * @param array $options Command options - * @return integer * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function countDocuments(array|object $filter = [], array $options = []) + public function countDocuments(array|object $filter = [], array $options = []): int { $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritReadOptions($options); @@ -348,7 +337,7 @@ public function countDocuments(array|object $filter = [], array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function createIndex(array|object $key, array $options = []) + public function createIndex(array|object $key, array $options = []): string { $operationOptionKeys = ['comment' => 1, 'commitQuorum' => 1, 'maxTimeMS' => 1, 'session' => 1, 'writeConcern' => 1]; $indexOptions = array_diff_key($options, $operationOptionKeys); @@ -384,7 +373,7 @@ public function createIndex(array|object $key, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function createIndexes(array $indexes, array $options = []) + public function createIndexes(array $indexes, array $options = []): array { $options = $this->inheritWriteOptions($options); @@ -458,12 +447,11 @@ public function createSearchIndexes(array $indexes, array $options = []): array * @see https://mongodb.com/docs/manual/reference/command/delete/ * @param array|object $filter Query by which to delete documents * @param array $options Command options - * @return DeleteResult * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function deleteMany(array|object $filter, array $options = []) + public function deleteMany(array|object $filter, array $options = []): DeleteResult { $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); @@ -480,12 +468,11 @@ public function deleteMany(array|object $filter, array $options = []) * @see https://mongodb.com/docs/manual/reference/command/delete/ * @param array|object $filter Query by which to delete documents * @param array $options Command options - * @return DeleteResult * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function deleteOne(array|object $filter, array $options = []) + public function deleteOne(array|object $filter, array $options = []): DeleteResult { $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); @@ -502,13 +489,12 @@ public function deleteOne(array|object $filter, array $options = []) * @param string $fieldName Field for which to return distinct values * @param array|object $filter Query by which to filter documents * @param array $options Command options - * @return array * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function distinct(string $fieldName, array|object $filter = [], array $options = []) + public function distinct(string $fieldName, array|object $filter = [], array $options = []): array { $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritReadOptions($options); @@ -524,15 +510,13 @@ public function distinct(string $fieldName, array|object $filter = [], array $op * * @see DropCollection::__construct() for supported options * @param array $options Additional options - * @return array|object Command result document * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function drop(array $options = []) + public function drop(array $options = []): void { $options = $this->inheritWriteOptions($options); - $options = $this->inheritTypeMap($options, __FUNCTION__); $server = select_server_for_write($this->manager, $options); @@ -545,7 +529,7 @@ public function drop(array $options = []) ? new DropEncryptedCollection($this->databaseName, $this->collectionName, $options) : new DropCollection($this->databaseName, $this->collectionName, $options); - return $operation->execute($server); + $operation->execute($server); } /** @@ -554,12 +538,11 @@ public function drop(array $options = []) * @see DropIndexes::__construct() for supported options * @param string|IndexInfo $indexName Index name or model object * @param array $options Additional options - * @return array|object Command result document * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function dropIndex(string|IndexInfo $indexName, array $options = []) + public function dropIndex(string|IndexInfo $indexName, array $options = []): void { $indexName = (string) $indexName; @@ -568,11 +551,10 @@ public function dropIndex(string|IndexInfo $indexName, array $options = []) } $options = $this->inheritWriteOptions($options); - $options = $this->inheritTypeMap($options, __FUNCTION__); $operation = new DropIndexes($this->databaseName, $this->collectionName, $indexName, $options); - return $operation->execute(select_server_for_write($this->manager, $options)); + $operation->execute(select_server_for_write($this->manager, $options)); } /** @@ -580,19 +562,17 @@ public function dropIndex(string|IndexInfo $indexName, array $options = []) * * @see DropIndexes::__construct() for supported options * @param array $options Additional options - * @return array|object Command result document * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function dropIndexes(array $options = []) + public function dropIndexes(array $options = []): void { $options = $this->inheritWriteOptions($options); - $options = $this->inheritTypeMap($options, __FUNCTION__); $operation = new DropIndexes($this->databaseName, $this->collectionName, '*', $options); - return $operation->execute(select_server_for_write($this->manager, $options)); + $operation->execute(select_server_for_write($this->manager, $options)); } /** @@ -618,13 +598,12 @@ public function dropSearchIndex(string $name, array $options = []): void * * @see EstimatedDocumentCount::__construct() for supported options * @param array $options Command options - * @return integer * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function estimatedDocumentCount(array $options = []) + public function estimatedDocumentCount(array $options = []): int { $options = $this->inheritReadOptions($options); @@ -640,12 +619,11 @@ public function estimatedDocumentCount(array $options = []) * @see https://mongodb.com/docs/manual/reference/command/explain/ * @param Explainable $explainable Command on which to run explain * @param array $options Additional options - * @return array|object * @throws UnsupportedException if explainable or options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function explain(Explainable $explainable, array $options = []) + public function explain(Explainable $explainable, array $options = []): array|object { $options = $this->inheritReadPreference($options); $options = $this->inheritTypeMap($options); @@ -662,12 +640,11 @@ public function explain(Explainable $explainable, array $options = []) * @see https://mongodb.com/docs/manual/crud/#read-operations * @param array|object $filter Query by which to filter documents * @param array $options Additional options - * @return CursorInterface&Iterator * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function find(array|object $filter = [], array $options = []) + public function find(array|object $filter = [], array $options = []): CursorInterface { $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritReadOptions($options); @@ -685,12 +662,11 @@ public function find(array|object $filter = [], array $options = []) * @see https://mongodb.com/docs/manual/crud/#read-operations * @param array|object $filter Query by which to filter documents * @param array $options Additional options - * @return array|object|null * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function findOne(array|object $filter = [], array $options = []) + public function findOne(array|object $filter = [], array $options = []): array|object|null { $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritReadOptions($options); @@ -710,13 +686,12 @@ public function findOne(array|object $filter = [], array $options = []) * @see https://mongodb.com/docs/manual/reference/command/findAndModify/ * @param array|object $filter Query by which to filter documents * @param array $options Command options - * @return array|object|null * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function findOneAndDelete(array|object $filter, array $options = []) + public function findOneAndDelete(array|object $filter, array $options = []): array|object|null { $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); @@ -741,13 +716,12 @@ public function findOneAndDelete(array|object $filter, array $options = []) * @param array|object $filter Query by which to filter documents * @param array|object $replacement Replacement document * @param array $options Command options - * @return array|object|null * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function findOneAndReplace(array|object $filter, array|object $replacement, array $options = []) + public function findOneAndReplace(array|object $filter, array|object $replacement, array $options = []): array|object|null { $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); @@ -772,15 +746,15 @@ public function findOneAndReplace(array|object $filter, array|object $replacemen * @param array|object $filter Query by which to filter documents * @param array|object $update Update to apply to the matched document * @param array $options Command options - * @return array|object|null * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function findOneAndUpdate(array|object $filter, array|object $update, array $options = []) + public function findOneAndUpdate(array|object $filter, array|object $update, array $options = []): array|object|null { $filter = $this->builderEncoder->encodeIfSupported($filter); + $update = $this->builderEncoder->encodeIfSupported($update); $options = $this->inheritWriteOptions($options); $options = $this->inheritCodecOrTypeMap($options); @@ -789,32 +763,37 @@ public function findOneAndUpdate(array|object $filter, array|object $update, arr return $operation->execute(select_server_for_write($this->manager, $options)); } + /** @psalm-return Encoder */ + public function getBuilderEncoder(): Encoder + { + return $this->builderEncoder; + } + + public function getCodec(): ?DocumentCodec + { + return $this->codec; + } + /** * Return the collection name. - * - * @return string */ - public function getCollectionName() + public function getCollectionName(): string { return $this->collectionName; } /** * Return the database name. - * - * @return string */ - public function getDatabaseName() + public function getDatabaseName(): string { return $this->databaseName; } /** * Return the Manager. - * - * @return Manager */ - public function getManager() + public function getManager(): Manager { return $this->manager; } @@ -823,9 +802,8 @@ public function getManager() * Return the collection namespace. * * @see https://mongodb.com/docs/manual/reference/glossary/#term-namespace - * @return string */ - public function getNamespace() + public function getNamespace(): string { return $this->databaseName . '.' . $this->collectionName; } @@ -834,29 +812,24 @@ public function getNamespace() * Return the read concern for this collection. * * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php - * @return ReadConcern */ - public function getReadConcern() + public function getReadConcern(): ReadConcern { return $this->readConcern; } /** * Return the read preference for this collection. - * - * @return ReadPreference */ - public function getReadPreference() + public function getReadPreference(): ReadPreference { return $this->readPreference; } /** * Return the type map for this collection. - * - * @return array */ - public function getTypeMap() + public function getTypeMap(): array { return $this->typeMap; } @@ -865,9 +838,8 @@ public function getTypeMap() * Return the write concern for this collection. * * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php - * @return WriteConcern */ - public function getWriteConcern() + public function getWriteConcern(): WriteConcern { return $this->writeConcern; } @@ -879,11 +851,10 @@ public function getWriteConcern() * @see https://mongodb.com/docs/manual/reference/command/insert/ * @param list $documents The documents to insert * @param array $options Command options - * @return InsertManyResult * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function insertMany(array $documents, array $options = []) + public function insertMany(array $documents, array $options = []): InsertManyResult { $options = $this->inheritWriteOptions($options); $options = $this->inheritCodec($options); @@ -900,11 +871,10 @@ public function insertMany(array $documents, array $options = []) * @see https://mongodb.com/docs/manual/reference/command/insert/ * @param array|object $document The document to insert * @param array $options Command options - * @return InsertOneResult * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function insertOne(array|object $document, array $options = []) + public function insertOne(array|object $document, array $options = []): InsertOneResult { $options = $this->inheritWriteOptions($options); $options = $this->inheritCodec($options); @@ -922,7 +892,7 @@ public function insertOne(array|object $document, array $options = []) * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function listIndexes(array $options = []) + public function listIndexes(array $options = []): Iterator { $operation = new ListIndexes($this->databaseName, $this->collectionName, $options); @@ -949,49 +919,6 @@ public function listSearchIndexes(array $options = []): Iterator return $operation->execute($server); } - /** - * Executes a map-reduce aggregation on the collection. - * - * @see MapReduce::__construct() for supported options - * @see https://mongodb.com/docs/manual/reference/command/mapReduce/ - * @param JavascriptInterface $map Map function - * @param JavascriptInterface $reduce Reduce function - * @param string|array|object $out Output specification - * @param array $options Command options - * @return MapReduceResult - * @throws UnsupportedException if options are not supported by the selected server - * @throws InvalidArgumentException for parameter/option parsing errors - * @throws DriverRuntimeException for other driver errors (e.g. connection errors) - * @throws UnexpectedValueException if the command response was malformed - */ - public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, string|array|object $out, array $options = []) - { - @trigger_error(sprintf('The %s method is deprecated and will be removed in a version 2.0.', __METHOD__), E_USER_DEPRECATED); - - $hasOutputCollection = ! is_mapreduce_output_inline($out); - - // Check if the out option is inline because we will want to coerce a primary read preference if not - if ($hasOutputCollection) { - $options['readPreference'] = new ReadPreference(ReadPreference::PRIMARY); - } else { - $options = $this->inheritReadPreference($options); - } - - /* A "majority" read concern is not compatible with inline output, so - * avoid providing the Collection's read concern if it would conflict. - */ - if (! $hasOutputCollection || $this->readConcern->getLevel() !== ReadConcern::MAJORITY) { - $options = $this->inheritReadConcern($options); - } - - $options = $this->inheritWriteOptions($options); - $options = $this->inheritTypeMap($options); - - $operation = new MapReduce($this->databaseName, $this->collectionName, $map, $reduce, $out, $options); - - return $operation->execute(select_server_for_write($this->manager, $options)); - } - /** * Renames the collection. * @@ -999,23 +926,21 @@ public function mapReduce(JavascriptInterface $map, JavascriptInterface $reduce, * @param string $toCollectionName New name of the collection * @param string|null $toDatabaseName New database name of the collection. Defaults to the original database. * @param array $options Additional options - * @return array|object Command result document * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function rename(string $toCollectionName, ?string $toDatabaseName = null, array $options = []) + public function rename(string $toCollectionName, ?string $toDatabaseName = null, array $options = []): void { if (! isset($toDatabaseName)) { $toDatabaseName = $this->databaseName; } $options = $this->inheritWriteOptions($options); - $options = $this->inheritTypeMap($options); $operation = new RenameCollection($this->databaseName, $this->collectionName, $toDatabaseName, $toCollectionName, $options); - return $operation->execute(select_server_for_write($this->manager, $options)); + $operation->execute(select_server_for_write($this->manager, $options)); } /** @@ -1026,12 +951,11 @@ public function rename(string $toCollectionName, ?string $toDatabaseName = null, * @param array|object $filter Query by which to filter documents * @param array|object $replacement Replacement document * @param array $options Command options - * @return UpdateResult * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function replaceOne(array|object $filter, array|object $replacement, array $options = []) + public function replaceOne(array|object $filter, array|object $replacement, array $options = []): UpdateResult { $filter = $this->builderEncoder->encodeIfSupported($filter); $options = $this->inheritWriteOptions($options); @@ -1050,12 +974,11 @@ public function replaceOne(array|object $filter, array|object $replacement, arra * @param array|object $filter Query by which to filter documents * @param array|object $update Update to apply to the matched documents * @param array $options Command options - * @return UpdateResult * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function updateMany(array|object $filter, array|object $update, array $options = []) + public function updateMany(array|object $filter, array|object $update, array $options = []): UpdateResult { $filter = $this->builderEncoder->encodeIfSupported($filter); $update = $this->builderEncoder->encodeIfSupported($update); @@ -1074,12 +997,11 @@ public function updateMany(array|object $filter, array|object $update, array $op * @param array|object $filter Query by which to filter documents * @param array|object $update Update to apply to the matched document * @param array $options Command options - * @return UpdateResult * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function updateOne(array|object $filter, array|object $update, array $options = []) + public function updateOne(array|object $filter, array|object $update, array $options = []): UpdateResult { $filter = $this->builderEncoder->encodeIfSupported($filter); $update = $this->builderEncoder->encodeIfSupported($update); @@ -1113,14 +1035,13 @@ public function updateSearchIndex(string $name, array|object $definition, array * Create a change stream for watching changes to the collection. * * @see Watch::__construct() for supported options - * @param array $pipeline Aggregation pipeline - * @param array $options Command options - * @return ChangeStream + * @param array|Pipeline $pipeline Aggregation pipeline + * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function watch(array $pipeline = [], array $options = []) + public function watch(array|Pipeline $pipeline = [], array $options = []): ChangeStream { - if (is_builder_pipeline($pipeline)) { + if (is_array($pipeline) && is_builder_pipeline($pipeline)) { $pipeline = new Pipeline(...$pipeline); } @@ -1139,10 +1060,9 @@ public function watch(array $pipeline = [], array $options = []) * * @see Collection::__construct() for supported options * @param array $options Collection constructor options - * @return Collection * @throws InvalidArgumentException for parameter/option parsing errors */ - public function withOptions(array $options = []) + public function withOptions(array $options = []): Collection { $options += [ 'autoEncryptionEnabled' => $this->autoEncryptionEnabled, @@ -1223,12 +1143,8 @@ private function inheritReadPreference(array $options): array return $options; } - private function inheritTypeMap(array $options, ?string $deprecatedFunction = null): array + private function inheritTypeMap(array $options): array { - if ($deprecatedFunction !== null && isset($options['typeMap'])) { - @trigger_error(sprintf('The function %s() will return nothing in mongodb/mongodb v2.0, the "typeMap" option is deprecated', $deprecatedFunction), E_USER_DEPRECATED); - } - // Only inherit the type map if no codec is used if (! isset($options['typeMap']) && ! isset($options['codec'])) { $options['typeMap'] = $this->typeMap; diff --git a/src/Command/ListCollections.php b/src/Command/ListCollections.php index 454e145f3..d42250cd3 100644 --- a/src/Command/ListCollections.php +++ b/src/Command/ListCollections.php @@ -18,13 +18,11 @@ namespace MongoDB\Command; use MongoDB\Driver\Command; -use MongoDB\Driver\Cursor; +use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Server; use MongoDB\Driver\Session; use MongoDB\Exception\InvalidArgumentException; -use MongoDB\Model\CachingIterator; -use MongoDB\Operation\Executable; use function is_bool; use function is_integer; @@ -36,7 +34,7 @@ * @internal * @see https://mongodb.com/docs/manual/reference/command/listCollections/ */ -class ListCollections implements Executable +final class ListCollections { /** * Constructs a listCollections command. @@ -93,17 +91,16 @@ public function __construct(private string $databaseName, private array $options /** * Execute the operation. * - * @return CachingIterator - * @see Executable::execute() + * @return CursorInterface * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server): CachingIterator + public function execute(Server $server): CursorInterface { - /** @var Cursor $cursor */ + /** @var CursorInterface $cursor */ $cursor = $server->executeReadCommand($this->databaseName, $this->createCommand(), $this->createOptions()); $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); - return new CachingIterator($cursor); + return $cursor; } /** diff --git a/src/Command/ListDatabases.php b/src/Command/ListDatabases.php index 5386559db..31ca5f741 100644 --- a/src/Command/ListDatabases.php +++ b/src/Command/ListDatabases.php @@ -23,7 +23,6 @@ use MongoDB\Driver\Session; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnexpectedValueException; -use MongoDB\Operation\Executable; use function current; use function is_array; @@ -37,7 +36,7 @@ * @internal * @see https://mongodb.com/docs/manual/reference/command/listDatabases/ */ -class ListDatabases implements Executable +final class ListDatabases { /** * Constructs a listDatabases command. @@ -93,7 +92,6 @@ public function __construct(private array $options = []) /** * Execute the operation. * - * @see Executable::execute() * @return array An array of database info structures * @throws UnexpectedValueException if the command response was malformed * @throws DriverRuntimeException for other driver errors (e.g. connection errors) @@ -102,6 +100,7 @@ public function execute(Server $server): array { $cursor = $server->executeReadCommand('admin', $this->createCommand(), $this->createOptions()); $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); + $result = current($cursor->toArray()); if (! isset($result['databases']) || ! is_array($result['databases'])) { diff --git a/src/Database.php b/src/Database.php index fbba0661c..be5803e34 100644 --- a/src/Database.php +++ b/src/Database.php @@ -24,7 +24,7 @@ use MongoDB\Builder\Pipeline; use MongoDB\Codec\Encoder; use MongoDB\Driver\ClientEncryption; -use MongoDB\Driver\Cursor; +use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Manager; use MongoDB\Driver\ReadConcern; @@ -51,18 +51,14 @@ use MongoDB\Operation\RenameCollection; use MongoDB\Operation\Watch; use stdClass; +use Stringable; use Throwable; -use Traversable; use function is_array; use function is_bool; -use function sprintf; use function strlen; -use function trigger_error; -use const E_USER_DEPRECATED; - -class Database +class Database implements Stringable { private const DEFAULT_TYPE_MAP = [ 'array' => BSONArray::class, @@ -157,9 +153,8 @@ public function __construct(private Manager $manager, private string $databaseNa * Return internal properties for debugging purposes. * * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo - * @return array */ - public function __debugInfo() + public function __debugInfo(): array { return [ 'builderEncoder' => $this->builderEncoder, @@ -182,19 +177,16 @@ public function __debugInfo() * @see https://php.net/oop5.overloading#object.get * @see https://php.net/types.string#language.types.string.parsing.complex * @param string $collectionName Name of the collection to select - * @return Collection */ - public function __get(string $collectionName) + public function __get(string $collectionName): Collection { return $this->getCollection($collectionName); } /** * Return the database name. - * - * @return string */ - public function __toString() + public function __toString(): string { return $this->databaseName; } @@ -205,17 +197,16 @@ public function __toString() * and $listLocalSessions. Requires MongoDB >= 3.6 * * @see Aggregate::__construct() for supported options - * @param array $pipeline Aggregation pipeline - * @param array $options Command options - * @return Traversable + * @param array|Pipeline $pipeline Aggregation pipeline + * @param array $options Command options * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function aggregate(array $pipeline, array $options = []) + public function aggregate(array|Pipeline $pipeline, array $options = []): CursorInterface { - if (is_builder_pipeline($pipeline)) { + if (is_array($pipeline) && is_builder_pipeline($pipeline)) { $pipeline = new Pipeline(...$pipeline); } @@ -263,11 +254,10 @@ public function aggregate(array $pipeline, array $options = []) * @see DatabaseCommand::__construct() for supported options * @param array|object $command Command document * @param array $options Options for command execution - * @return Cursor * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function command(array|object $command, array $options = []) + public function command(array|object $command, array $options = []): CursorInterface { if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; @@ -287,21 +277,14 @@ public function command(array|object $command, array $options = []) * collection. * * @see CreateCollection::__construct() for supported options - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#create-collection-helper + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.md#create-collection-helper * @see https://www.mongodb.com/docs/manual/core/queryable-encryption/fundamentals/manage-collections/ - * @return array|object Command result document * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function createCollection(string $collectionName, array $options = []) + public function createCollection(string $collectionName, array $options = []): void { - if (! isset($options['typeMap'])) { - $options['typeMap'] = $this->typeMap; - } else { - @trigger_error(sprintf('The function %s() will return nothing in mongodb/mongodb v2.0, the "typeMap" option is deprecated', __FUNCTION__), E_USER_DEPRECATED); - } - if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -316,7 +299,7 @@ public function createCollection(string $collectionName, array $options = []) $server = select_server_for_write($this->manager, $options); - return $operation->execute($server); + $operation->execute($server); } /** @@ -334,19 +317,13 @@ public function createCollection(string $collectionName, array $options = []) * getPrevious() and getEncryptedFields() methods, respectively. * * @see CreateCollection::__construct() for supported options - * @return array A tuple containing the command result document from creating the collection and the modified "encryptedFields" option + * @return array The modified "encryptedFields" option * @throws InvalidArgumentException for parameter/option parsing errors * @throws CreateEncryptedCollectionException for any errors creating data keys or creating the collection * @throws UnsupportedException if Queryable Encryption is not supported by the selected server */ public function createEncryptedCollection(string $collectionName, ClientEncryption $clientEncryption, string $kmsProvider, ?array $masterKey, array $options): array { - if (! isset($options['typeMap'])) { - $options['typeMap'] = $this->typeMap; - } else { - @trigger_error(sprintf('The function %s() will return nothing in mongodb/mongodb v2.0, the "typeMap" option is deprecated', __FUNCTION__), E_USER_DEPRECATED); - } - if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { $options['writeConcern'] = $this->writeConcern; } @@ -355,10 +332,10 @@ public function createEncryptedCollection(string $collectionName, ClientEncrypti $server = select_server_for_write($this->manager, $options); try { - $operation->createDataKeys($clientEncryption, $kmsProvider, $masterKey, $encryptedFields); - $result = $operation->execute($server); + $encryptedFields = $operation->createDataKeys($clientEncryption, $kmsProvider, $masterKey); + $operation->execute($server); - return [$result, $encryptedFields]; + return $encryptedFields; } catch (Throwable $e) { throw new CreateEncryptedCollectionException($e, $encryptedFields ?? []); } @@ -369,19 +346,12 @@ public function createEncryptedCollection(string $collectionName, ClientEncrypti * * @see DropDatabase::__construct() for supported options * @param array $options Additional options - * @return array|object Command result document * @throws UnsupportedException if options are unsupported on the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function drop(array $options = []) + public function drop(array $options = []): void { - if (! isset($options['typeMap'])) { - $options['typeMap'] = $this->typeMap; - } else { - @trigger_error(sprintf('The function %s() will return nothing in mongodb/mongodb v2.0, the "typeMap" option is deprecated', __FUNCTION__), E_USER_DEPRECATED); - } - $server = select_server_for_write($this->manager, $options); if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { @@ -390,7 +360,7 @@ public function drop(array $options = []) $operation = new DropDatabase($this->databaseName, $options); - return $operation->execute($server); + $operation->execute($server); } /** @@ -399,19 +369,12 @@ public function drop(array $options = []) * @see DropCollection::__construct() for supported options * @param string $collectionName Collection name * @param array $options Additional options - * @return array|object Command result document * @throws UnsupportedException if options are unsupported on the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function dropCollection(string $collectionName, array $options = []) + public function dropCollection(string $collectionName, array $options = []): void { - if (! isset($options['typeMap'])) { - $options['typeMap'] = $this->typeMap; - } else { - @trigger_error(sprintf('The function %s() will return nothing in mongodb/mongodb v2.0, the "typeMap" option is deprecated', __FUNCTION__), E_USER_DEPRECATED); - } - $server = select_server_for_write($this->manager, $options); if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { @@ -427,7 +390,7 @@ public function dropCollection(string $collectionName, array $options = []) ? new DropEncryptedCollection($this->databaseName, $collectionName, $options) : new DropCollection($this->databaseName, $collectionName, $options); - return $operation->execute($server); + $operation->execute($server); } /** @@ -455,20 +418,16 @@ public function getCollection(string $collectionName, array $options = []): Coll /** * Returns the database name. - * - * @return string */ - public function getDatabaseName() + public function getDatabaseName(): string { return $this->databaseName; } /** * Return the Manager. - * - * @return Manager */ - public function getManager() + public function getManager(): Manager { return $this->manager; } @@ -477,29 +436,24 @@ public function getManager() * Return the read concern for this database. * * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php - * @return ReadConcern */ - public function getReadConcern() + public function getReadConcern(): ReadConcern { return $this->readConcern; } /** * Return the read preference for this database. - * - * @return ReadPreference */ - public function getReadPreference() + public function getReadPreference(): ReadPreference { return $this->readPreference; } /** * Return the type map for this database. - * - * @return array */ - public function getTypeMap() + public function getTypeMap(): array { return $this->typeMap; } @@ -508,9 +462,8 @@ public function getTypeMap() * Return the write concern for this database. * * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php - * @return WriteConcern */ - public function getWriteConcern() + public function getWriteConcern(): WriteConcern { return $this->writeConcern; } @@ -519,6 +472,7 @@ public function getWriteConcern() * Returns the names of all collections in this database * * @see ListCollectionNames::__construct() for supported options + * @return Iterator * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ @@ -538,7 +492,7 @@ public function listCollectionNames(array $options = []): Iterator * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function listCollections(array $options = []) + public function listCollections(array $options = []): Iterator { $operation = new ListCollections($this->databaseName, $options); $server = select_server($this->manager, $options); @@ -553,11 +507,10 @@ public function listCollections(array $options = []) * @param string $collectionName Collection or view to modify * @param array $collectionOptions Collection or view options to assign * @param array $options Command options - * @return array|object * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function modifyCollection(string $collectionName, array $collectionOptions, array $options = []) + public function modifyCollection(string $collectionName, array $collectionOptions, array $options = []): array|object { if (! isset($options['typeMap'])) { $options['typeMap'] = $this->typeMap; @@ -582,21 +535,16 @@ public function modifyCollection(string $collectionName, array $collectionOption * @param string $toCollectionName New name of the collection * @param string|null $toDatabaseName New database name of the collection. Defaults to the original database. * @param array $options Additional options - * @return array|object Command result document * @throws UnsupportedException if options are unsupported on the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function renameCollection(string $fromCollectionName, string $toCollectionName, ?string $toDatabaseName = null, array $options = []) + public function renameCollection(string $fromCollectionName, string $toCollectionName, ?string $toDatabaseName = null, array $options = []): void { if (! isset($toDatabaseName)) { $toDatabaseName = $this->databaseName; } - if (! isset($options['typeMap'])) { - $options['typeMap'] = $this->typeMap; - } - $server = select_server_for_write($this->manager, $options); if (! isset($options['writeConcern']) && ! is_in_transaction($options)) { @@ -605,7 +553,7 @@ public function renameCollection(string $fromCollectionName, string $toCollectio $operation = new RenameCollection($this->databaseName, $fromCollectionName, $toDatabaseName, $toCollectionName, $options); - return $operation->execute($server); + $operation->execute($server); } /** @@ -614,10 +562,9 @@ public function renameCollection(string $fromCollectionName, string $toCollectio * @see Collection::__construct() for supported options * @param string $collectionName Name of the collection to select * @param array $options Collection constructor options - * @return Collection * @throws InvalidArgumentException for parameter/option parsing errors */ - public function selectCollection(string $collectionName, array $options = []) + public function selectCollection(string $collectionName, array $options = []): Collection { return $this->getCollection($collectionName, $options); } @@ -627,10 +574,9 @@ public function selectCollection(string $collectionName, array $options = []) * * @see Bucket::__construct() for supported options * @param array $options Bucket constructor options - * @return Bucket * @throws InvalidArgumentException for parameter/option parsing errors */ - public function selectGridFSBucket(array $options = []) + public function selectGridFSBucket(array $options = []): Bucket { $options += [ 'readConcern' => $this->readConcern, @@ -646,14 +592,13 @@ public function selectGridFSBucket(array $options = []) * Create a change stream for watching changes to the database. * * @see Watch::__construct() for supported options - * @param array $pipeline Aggregation pipeline - * @param array $options Command options - * @return ChangeStream + * @param array|Pipeline $pipeline Aggregation pipeline + * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function watch(array $pipeline = [], array $options = []) + public function watch(array|Pipeline $pipeline = [], array $options = []): ChangeStream { - if (is_builder_pipeline($pipeline)) { + if (is_array($pipeline) && is_builder_pipeline($pipeline)) { $pipeline = new Pipeline(...$pipeline); } @@ -683,10 +628,9 @@ public function watch(array $pipeline = [], array $options = []) * * @see Database::__construct() for supported options * @param array $options Database constructor options - * @return Database * @throws InvalidArgumentException for parameter/option parsing errors */ - public function withOptions(array $options = []) + public function withOptions(array $options = []): Database { $options += [ 'autoEncryptionEnabled' => $this->autoEncryptionEnabled, diff --git a/src/DeleteResult.php b/src/DeleteResult.php index 56f3f072d..23b416d0a 100644 --- a/src/DeleteResult.php +++ b/src/DeleteResult.php @@ -17,19 +17,16 @@ namespace MongoDB; +use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\WriteResult; -use MongoDB\Exception\BadMethodCallException; /** * Result class for a delete operation. */ class DeleteResult { - private bool $isAcknowledged; - public function __construct(private WriteResult $writeResult) { - $this->isAcknowledged = $writeResult->isAcknowledged(); } /** @@ -38,16 +35,11 @@ public function __construct(private WriteResult $writeResult) * This method should only be called if the write was acknowledged. * * @see DeleteResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getDeletedCount() + public function getDeletedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getDeletedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getDeletedCount(); } /** @@ -55,11 +47,9 @@ public function getDeletedCount() * * If the delete was not acknowledged, other fields from the WriteResult * (e.g. deletedCount) will be undefined. - * - * @return boolean */ - public function isAcknowledged() + public function isAcknowledged(): bool { - return $this->isAcknowledged; + return $this->writeResult->isAcknowledged(); } } diff --git a/src/Exception/BadMethodCallException.php b/src/Exception/BadMethodCallException.php index e1070982a..00cece346 100644 --- a/src/Exception/BadMethodCallException.php +++ b/src/Exception/BadMethodCallException.php @@ -27,21 +27,10 @@ class BadMethodCallException extends BaseBadMethodCallException implements Excep * Thrown when a mutable method is invoked on an immutable object. * * @param string $class Class name - * @return self + * @internal */ - public static function classIsImmutable(string $class) + public static function classIsImmutable(string $class): self { return new self(sprintf('%s is immutable', $class)); } - - /** - * Thrown when accessing a result field on an unacknowledged write result. - * - * @param string $method Method name - * @return self - */ - public static function unacknowledgedWriteResultAccess(string $method) - { - return new self(sprintf('%s should not be called for an unacknowledged write result', $method)); - } } diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php index 964b259c1..c92734a0c 100644 --- a/src/Exception/InvalidArgumentException.php +++ b/src/Exception/InvalidArgumentException.php @@ -29,6 +29,7 @@ class InvalidArgumentException extends DriverInvalidArgumentException implements Exception { + /** @internal */ public static function cannotCombineCodecAndTypeMap(): self { return new self('Cannot provide both "codec" and "typeMap" options'); @@ -37,6 +38,8 @@ public static function cannotCombineCodecAndTypeMap(): self /** * Thrown when an argument or option is expected to be a string or a document. * + * @internal + * * @param string $name Name of the argument or option * @param mixed $value Actual value (used to derive the type) */ @@ -50,6 +53,7 @@ public static function expectedDocumentOrStringType(string $name, mixed $value): * * @param string $name Name of the argument or option * @param mixed $value Actual value (used to derive the type) + * @internal */ public static function expectedDocumentType(string $name, mixed $value): self { @@ -62,9 +66,9 @@ public static function expectedDocumentType(string $name, mixed $value): self * @param string $name Name of the argument or option * @param mixed $value Actual value (used to derive the type) * @param string|list $expectedType Expected type as a string or an array containing one or more strings - * @return self + * @internal */ - public static function invalidType(string $name, mixed $value, string|array $expectedType) + public static function invalidType(string $name, mixed $value, string|array $expectedType): self { if (is_array($expectedType)) { $expectedType = self::expectedTypesToString($expectedType); diff --git a/src/Exception/ResumeTokenException.php b/src/Exception/ResumeTokenException.php index fc8808271..9117f700d 100644 --- a/src/Exception/ResumeTokenException.php +++ b/src/Exception/ResumeTokenException.php @@ -26,9 +26,9 @@ class ResumeTokenException extends RuntimeException * Thrown when a resume token has an invalid type. * * @param mixed $value Actual value (used to derive the type) - * @return self + * @internal */ - public static function invalidType(mixed $value) + public static function invalidType(mixed $value): self { return new self(sprintf('Expected resume token to have type "array or object" but found "%s"', get_debug_type($value))); } @@ -36,9 +36,9 @@ public static function invalidType(mixed $value) /** * Thrown when a resume token is not found in a change document. * - * @return self + * @internal */ - public static function notFound() + public static function notFound(): self { return new self('Resume token not found in change document'); } diff --git a/src/Exception/UnsupportedException.php b/src/Exception/UnsupportedException.php index 2bf910a09..a2204b886 100644 --- a/src/Exception/UnsupportedException.php +++ b/src/Exception/UnsupportedException.php @@ -19,110 +19,44 @@ class UnsupportedException extends RuntimeException { - /** - * Thrown when a command's allowDiskUse option is not supported by a server. - * - * @return self - */ - public static function allowDiskUseNotSupported() - { - return new self('The "allowDiskUse" option is not supported by the server executing this operation'); - } - - /** - * Thrown when array filters are not supported by a server. - * - * @deprecated 1.12 - * @todo Remove this in 2.0 (see: PHPLIB-797) - * - * @return self - */ - public static function arrayFiltersNotSupported() - { - return new self('Array filters are not supported by the server executing this operation'); - } - - /** - * Thrown when collations are not supported by a server. - * - * @deprecated 1.12 - * @todo Remove this in 2.0 (see: PHPLIB-797) - * - * @return self - */ - public static function collationNotSupported() - { - return new self('Collations are not supported by the server executing this operation'); - } - /** * Thrown when the commitQuorum option for createIndexes is not supported * by a server. * - * @return self + * @internal */ - public static function commitQuorumNotSupported() + public static function commitQuorumNotSupported(): self { return new self('The "commitQuorum" option is not supported by the server executing this operation'); } - /** - * Thrown when explain is not supported by a server. - * - * @return self - */ - public static function explainNotSupported() - { - return new self('Explain is not supported by the server executing this operation'); - } - /** * Thrown when a command's hint option is not supported by a server. * - * @return self + * @internal */ - public static function hintNotSupported() + public static function hintNotSupported(): self { return new self('Hint is not supported by the server executing this operation'); } - /** - * Thrown when a command's readConcern option is not supported by a server. - * - * @return self - */ - public static function readConcernNotSupported() - { - return new self('Read concern is not supported by the server executing this command'); - } - /** * Thrown when a readConcern is used with a read operation in a transaction. * - * @return self - */ - public static function readConcernNotSupportedInTransaction() - { - return new self('The "readConcern" option cannot be specified within a transaction. Instead, specify it when starting the transaction.'); - } - - /** - * Thrown when a command's writeConcern option is not supported by a server. - * - * @return self + * @internal */ - public static function writeConcernNotSupported() + public static function readConcernNotSupportedInTransaction(): self { - return new self('Write concern is not supported by the server executing this command'); + return new self('Cannot set read concern after starting a transaction. Instead, specify the "readConcern" option when starting the transaction.'); } /** * Thrown when a writeConcern is used with a write operation in a transaction. * - * @return self + * @internal */ - public static function writeConcernNotSupportedInTransaction() + public static function writeConcernNotSupportedInTransaction(): self { - return new self('The "writeConcern" option cannot be specified within a transaction. Instead, specify it when starting the transaction.'); + return new self('Cannot set write concern after starting a transaction. Instead, specify the "writeConcern" option when starting the transaction.'); } } diff --git a/src/Exception/UnsupportedValueException.php b/src/Exception/UnsupportedValueException.php index d744baa13..62e089e5e 100644 --- a/src/Exception/UnsupportedValueException.php +++ b/src/Exception/UnsupportedValueException.php @@ -24,8 +24,7 @@ class UnsupportedValueException extends InvalidArgumentException implements Exception { - /** @return mixed */ - public function getValue() + public function getValue(): mixed { return $this->value; } diff --git a/src/GridFS/Bucket.php b/src/GridFS/Bucket.php index 84f09c7b6..3210f8b42 100644 --- a/src/GridFS/Bucket.php +++ b/src/GridFS/Bucket.php @@ -17,7 +17,6 @@ namespace MongoDB\GridFS; -use Iterator; use MongoDB\BSON\Document; use MongoDB\Codec\DocumentCodec; use MongoDB\Collection; @@ -44,7 +43,6 @@ use function get_resource_type; use function in_array; use function is_array; -use function is_bool; use function is_integer; use function is_object; use function is_resource; @@ -58,11 +56,8 @@ use function stream_copy_to_stream; use function stream_get_meta_data; use function stream_get_wrappers; -use function trigger_error; use function urlencode; -use const E_USER_DEPRECATED; - /** * Bucket provides a public API for interacting with the GridFS files and chunks * collections. @@ -87,8 +82,6 @@ class Bucket private string $bucketName; - private bool $disableMD5; - private int $chunkSizeBytes; private ReadConcern $readConcern; @@ -110,9 +103,6 @@ class Bucket * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to * 261120 (i.e. 255 KiB). * - * * disableMD5 (boolean): When true, no MD5 sum will be generated for - * each stored file. Defaults to "false". - * * * readConcern (MongoDB\Driver\ReadConcern): Read concern. * * * readPreference (MongoDB\Driver\ReadPreference): Read preference. @@ -128,14 +118,9 @@ class Bucket */ public function __construct(private Manager $manager, private string $databaseName, array $options = []) { - if (isset($options['disableMD5']) && $options['disableMD5'] === false) { - @trigger_error('Setting GridFS "disableMD5" option to "false" is deprecated since mongodb/mongodb 1.18 and will not be supported in version 2.0.', E_USER_DEPRECATED); - } - $options += [ 'bucketName' => self::DEFAULT_BUCKET_NAME, 'chunkSizeBytes' => self::DEFAULT_CHUNK_SIZE_BYTES, - 'disableMD5' => false, ]; if (! is_string($options['bucketName'])) { @@ -154,10 +139,6 @@ public function __construct(private Manager $manager, private string $databaseNa throw InvalidArgumentException::invalidType('"codec" option', $options['codec'], DocumentCodec::class); } - if (! is_bool($options['disableMD5'])) { - throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean'); - } - if (isset($options['readConcern']) && ! $options['readConcern'] instanceof ReadConcern) { throw InvalidArgumentException::invalidType('"readConcern" option', $options['readConcern'], ReadConcern::class); } @@ -181,7 +162,6 @@ public function __construct(private Manager $manager, private string $databaseNa $this->bucketName = $options['bucketName']; $this->chunkSizeBytes = $options['chunkSizeBytes']; $this->codec = $options['codec'] ?? null; - $this->disableMD5 = $options['disableMD5']; $this->readConcern = $options['readConcern'] ?? $this->manager->getReadConcern(); $this->readPreference = $options['readPreference'] ?? $this->manager->getReadPreference(); $this->typeMap = $options['typeMap'] ?? self::DEFAULT_TYPE_MAP; @@ -202,15 +182,13 @@ public function __construct(private Manager $manager, private string $databaseNa * Return internal properties for debugging purposes. * * @see https://php.net/manual/en/language.oop5.magic.php#language.oop5.magic.debuginfo - * @return array */ - public function __debugInfo() + public function __debugInfo(): array { return [ 'bucketName' => $this->bucketName, 'codec' => $this->codec, 'databaseName' => $this->databaseName, - 'disableMD5' => $this->disableMD5, 'manager' => $this->manager, 'chunkSizeBytes' => $this->chunkSizeBytes, 'readConcern' => $this->readConcern, @@ -227,11 +205,10 @@ public function __debugInfo() * attempt to delete orphaned chunks. * * @param mixed $id File ID - * @return void * @throws FileNotFoundException if no file could be selected * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function delete(mixed $id) + public function delete(mixed $id): void { $file = $this->collectionWrapper->findFileById($id); $this->collectionWrapper->deleteFileAndChunksById($id); @@ -263,13 +240,12 @@ public function deleteByName(string $filename): void * * @param mixed $id File ID * @param resource $destination Writable Stream - * @return void * @throws FileNotFoundException if no file could be selected * @throws InvalidArgumentException if $destination is not a stream * @throws StreamException if the file could not be uploaded * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function downloadToStream(mixed $id, $destination) + public function downloadToStream(mixed $id, $destination): void { if (! is_resource($destination) || get_resource_type($destination) != 'stream') { throw InvalidArgumentException::invalidType('$destination', $destination, 'resource'); @@ -303,13 +279,12 @@ public function downloadToStream(mixed $id, $destination) * @param string $filename Filename * @param resource $destination Writable Stream * @param array $options Download options - * @return void * @throws FileNotFoundException if no file could be selected * @throws InvalidArgumentException if $destination is not a stream * @throws StreamException if the file could not be uploaded * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function downloadToStreamByName(string $filename, $destination, array $options = []) + public function downloadToStreamByName(string $filename, $destination, array $options = []): void { if (! is_resource($destination) || get_resource_type($destination) != 'stream') { throw InvalidArgumentException::invalidType('$destination', $destination, 'resource'); @@ -325,10 +300,9 @@ public function downloadToStreamByName(string $filename, $destination, array $op * Drops the files and chunks collections associated with this GridFS * bucket. * - * @return void * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function drop() + public function drop(): void { $this->collectionWrapper->dropCollections(); } @@ -340,12 +314,11 @@ public function drop() * @see Find::__construct() for supported options * @param array|object $filter Query by which to filter documents * @param array $options Additional options - * @return CursorInterface&Iterator * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function find(array|object $filter = [], array $options = []) + public function find(array|object $filter = [], array $options = []): CursorInterface { if ($this->codec && ! array_key_exists('codec', $options)) { $options['codec'] = $this->codec; @@ -361,12 +334,11 @@ public function find(array|object $filter = [], array $options = []) * @see FindOne::__construct() for supported options * @param array|object $filter Query by which to filter documents * @param array $options Additional options - * @return array|object|null * @throws UnsupportedException if options are not supported by the selected server * @throws InvalidArgumentException for parameter/option parsing errors * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function findOne(array|object $filter = [], array $options = []) + public function findOne(array|object $filter = [], array $options = []): array|object|null { if ($this->codec && ! array_key_exists('codec', $options)) { $options['codec'] = $this->codec; @@ -377,40 +349,32 @@ public function findOne(array|object $filter = [], array $options = []) /** * Return the bucket name. - * - * @return string */ - public function getBucketName() + public function getBucketName(): string { return $this->bucketName; } /** * Return the chunks collection. - * - * @return Collection */ - public function getChunksCollection() + public function getChunksCollection(): Collection { return $this->collectionWrapper->getChunksCollection(); } /** * Return the chunk size in bytes. - * - * @return integer */ - public function getChunkSizeBytes() + public function getChunkSizeBytes(): int { return $this->chunkSizeBytes; } /** * Return the database name. - * - * @return string */ - public function getDatabaseName() + public function getDatabaseName(): string { return $this->databaseName; } @@ -419,11 +383,10 @@ public function getDatabaseName() * Gets the file document of the GridFS file associated with a stream. * * @param resource $stream GridFS stream - * @return array|object * @throws InvalidArgumentException if $stream is not a GridFS stream * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function getFileDocumentForStream($stream) + public function getFileDocumentForStream($stream): array|object { $file = $this->getRawFileDocumentForStream($stream); @@ -439,12 +402,11 @@ public function getFileDocumentForStream($stream) * Gets the file document's ID of the GridFS file associated with a stream. * * @param resource $stream GridFS stream - * @return mixed * @throws CorruptFileException if the file "_id" field does not exist * @throws InvalidArgumentException if $stream is not a GridFS stream * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function getFileIdForStream($stream) + public function getFileIdForStream($stream): mixed { $file = $this->getRawFileDocumentForStream($stream); @@ -464,10 +426,8 @@ public function getFileIdForStream($stream) /** * Return the files collection. - * - * @return Collection */ - public function getFilesCollection() + public function getFilesCollection(): Collection { return $this->collectionWrapper->getFilesCollection(); } @@ -476,29 +436,24 @@ public function getFilesCollection() * Return the read concern for this GridFS bucket. * * @see https://php.net/manual/en/mongodb-driver-readconcern.isdefault.php - * @return ReadConcern */ - public function getReadConcern() + public function getReadConcern(): ReadConcern { return $this->readConcern; } /** * Return the read preference for this GridFS bucket. - * - * @return ReadPreference */ - public function getReadPreference() + public function getReadPreference(): ReadPreference { return $this->readPreference; } /** * Return the type map for this GridFS bucket. - * - * @return array */ - public function getTypeMap() + public function getTypeMap(): array { return $this->typeMap; } @@ -507,9 +462,8 @@ public function getTypeMap() * Return the write concern for this GridFS bucket. * * @see https://php.net/manual/en/mongodb-driver-writeconcern.isdefault.php - * @return WriteConcern */ - public function getWriteConcern() + public function getWriteConcern(): WriteConcern { return $this->writeConcern; } @@ -581,9 +535,6 @@ public function openDownloadStreamByName(string $filename, array $options = []) * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the * bucket's chunk size. * - * * disableMD5 (boolean): When true, no MD5 sum will be generated for - * the stored file. Defaults to "false". - * * * metadata (document): User data for the "metadata" field of the files * collection document. * @@ -595,7 +546,6 @@ public function openUploadStream(string $filename, array $options = []) { $options += [ 'chunkSizeBytes' => $this->chunkSizeBytes, - 'disableMD5' => $this->disableMD5, ]; $path = $this->createPathForUpload(); @@ -638,11 +588,10 @@ public function registerGlobalStreamWrapperAlias(string $alias): void * * @param mixed $id File ID * @param string $newFilename New filename - * @return void * @throws FileNotFoundException if no file could be selected * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function rename(mixed $id, string $newFilename) + public function rename(mixed $id, string $newFilename): void { $updateResult = $this->collectionWrapper->updateFilenameForId($id, $newFilename); @@ -650,16 +599,9 @@ public function rename(mixed $id, string $newFilename) return; } - /* If the update resulted in no modification, it's possible that the - * file did not exist, in which case we must raise an error. Checking - * the write result's matched count will be most efficient, but fall - * back to a findOne operation if necessary (i.e. legacy writes). - */ - $found = $updateResult->getMatchedCount() !== null - ? $updateResult->getMatchedCount() === 1 - : $this->collectionWrapper->findFileById($id) !== null; - - if (! $found) { + // If the update resulted in no modification, it's possible that the + // file did not exist, in which case we must raise an error. + if ($updateResult->getMatchedCount() !== 1) { throw FileNotFoundException::byId($id, $this->getFilesNamespace()); } } @@ -692,9 +634,6 @@ public function renameByName(string $filename, string $newFilename): void * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to the * bucket's chunk size. * - * * disableMD5 (boolean): When true, no MD5 sum will be generated for - * the stored file. Defaults to "false". - * * * metadata (document): User data for the "metadata" field of the files * collection document. * @@ -706,7 +645,7 @@ public function renameByName(string $filename, string $newFilename): void * @throws StreamException if the file could not be uploaded * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function uploadFromStream(string $filename, $source, array $options = []) + public function uploadFromStream(string $filename, $source, array $options = []): mixed { if (! is_resource($source) || get_resource_type($source) != 'stream') { throw InvalidArgumentException::invalidType('$source', $source, 'resource'); @@ -826,9 +765,9 @@ private function registerStreamWrapper(): void * * @see StreamWrapper::setContextResolver() * - * @param string $path The full url provided to fopen(). It contains the filename. - * gridfs://database_name/collection_name.files/file_name - * @param array{revision?: int, chunkSizeBytes?: int, disableMD5?: bool} $context The options provided to fopen() + * @param string $path The full url provided to fopen(). It contains the filename. + * gridfs://database_name/collection_name.files/file_name + * @param array{revision?: int, chunkSizeBytes?: int} $context The options provided to fopen() * * @return array{collectionWrapper: CollectionWrapper, file: object}|array{collectionWrapper: CollectionWrapper, filename: string, options: array} * @@ -859,7 +798,6 @@ private function resolveStreamContext(string $path, string $mode, array $context 'filename' => $filename, 'options' => $context + [ 'chunkSizeBytes' => $this->chunkSizeBytes, - 'disableMD5' => $this->disableMD5, ], ]; } diff --git a/src/GridFS/CollectionWrapper.php b/src/GridFS/CollectionWrapper.php index 09b06ba23..62be77fc0 100644 --- a/src/GridFS/CollectionWrapper.php +++ b/src/GridFS/CollectionWrapper.php @@ -18,7 +18,6 @@ namespace MongoDB\GridFS; use ArrayIterator; -use Iterator; use MongoDB\Collection; use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Manager; @@ -39,7 +38,7 @@ * * @internal */ -class CollectionWrapper +final class CollectionWrapper { private Collection $chunksCollection; @@ -117,9 +116,8 @@ public function dropCollections(): void * * @param mixed $id File ID * @param integer $fromChunk Starting chunk (inclusive) - * @return CursorInterface&Iterator */ - public function findChunksByFileId(mixed $id, int $fromChunk = 0) + public function findChunksByFileId(mixed $id, int $fromChunk = 0): CursorInterface { return $this->chunksCollection->find( [ @@ -191,9 +189,8 @@ public function findFileById(mixed $id): ?object * @see Find::__construct() for supported options * @param array|object $filter Query by which to filter documents * @param array $options Additional options - * @return CursorInterface&Iterator */ - public function findFiles(array|object $filter, array $options = []) + public function findFiles(array|object $filter, array $options = []): CursorInterface { return $this->filesCollection->find($filter, $options); } @@ -203,9 +200,8 @@ public function findFiles(array|object $filter, array $options = []) * * @param array|object $filter Query by which to filter documents * @param array $options Additional options - * @return array|object|null */ - public function findOneFile(array|object $filter, array $options = []) + public function findOneFile(array|object $filter, array $options = []): array|object|null { return $this->filesCollection->findOne($filter, $options); } diff --git a/src/GridFS/Exception/CorruptFileException.php b/src/GridFS/Exception/CorruptFileException.php index db1622908..b9feef87d 100644 --- a/src/GridFS/Exception/CorruptFileException.php +++ b/src/GridFS/Exception/CorruptFileException.php @@ -25,6 +25,8 @@ class CorruptFileException extends RuntimeException { /** * Thrown when a chunk doesn't contain valid data. + * + * @internal */ public static function invalidChunkData(int $chunkIndex): self { @@ -35,9 +37,9 @@ public static function invalidChunkData(int $chunkIndex): self * Thrown when a chunk is not found for an expected index. * * @param integer $expectedIndex Expected index number - * @return self + * @internal */ - public static function missingChunk(int $expectedIndex) + public static function missingChunk(int $expectedIndex): self { return new self(sprintf('Chunk not found for index "%d"', $expectedIndex)); } @@ -47,9 +49,9 @@ public static function missingChunk(int $expectedIndex) * * @param integer $index Actual index number (i.e. "n" field) * @param integer $expectedIndex Expected index number - * @return self + * @internal */ - public static function unexpectedIndex(int $index, int $expectedIndex) + public static function unexpectedIndex(int $index, int $expectedIndex): self { return new self(sprintf('Expected chunk to have index "%d" but found "%d"', $expectedIndex, $index)); } @@ -59,9 +61,9 @@ public static function unexpectedIndex(int $index, int $expectedIndex) * * @param integer $size Actual size (i.e. "data" field length) * @param integer $expectedSize Expected size - * @return self + * @internal */ - public static function unexpectedSize(int $size, int $expectedSize) + public static function unexpectedSize(int $size, int $expectedSize): self { return new self(sprintf('Expected chunk to have size "%d" but found "%d"', $expectedSize, $size)); } diff --git a/src/GridFS/Exception/FileNotFoundException.php b/src/GridFS/Exception/FileNotFoundException.php index 99c6d854a..4cf153c08 100644 --- a/src/GridFS/Exception/FileNotFoundException.php +++ b/src/GridFS/Exception/FileNotFoundException.php @@ -28,9 +28,9 @@ class FileNotFoundException extends RuntimeException * Thrown when a file cannot be found by its filename. * * @param string $filename Filename - * @return self + * @internal */ - public static function byFilename(string $filename) + public static function byFilename(string $filename): self { return new self(sprintf('File with name "%s" not found', $filename)); } @@ -41,9 +41,9 @@ public static function byFilename(string $filename) * @param string $filename Filename * @param integer $revision Revision * @param string $namespace Namespace for the files collection - * @return self + * @internal */ - public static function byFilenameAndRevision(string $filename, int $revision, string $namespace) + public static function byFilenameAndRevision(string $filename, int $revision, string $namespace): self { return new self(sprintf('File with name "%s" and revision "%d" not found in "%s"', $filename, $revision, $namespace)); } @@ -53,9 +53,9 @@ public static function byFilenameAndRevision(string $filename, int $revision, st * * @param mixed $id File ID * @param string $namespace Namespace for the files collection - * @return self + * @internal */ - public static function byId(mixed $id, string $namespace) + public static function byId(mixed $id, string $namespace): self { $json = Document::fromPHP(['_id' => $id])->toRelaxedExtendedJSON(); diff --git a/src/GridFS/Exception/StreamException.php b/src/GridFS/Exception/StreamException.php index 880182588..3fd75ca8c 100644 --- a/src/GridFS/Exception/StreamException.php +++ b/src/GridFS/Exception/StreamException.php @@ -13,6 +13,7 @@ class StreamException extends RuntimeException /** * @param resource $source * @param resource $destination + * @internal */ public static function downloadFromFilenameFailed(string $filename, $source, $destination): self { @@ -25,6 +26,7 @@ public static function downloadFromFilenameFailed(string $filename, $source, $de /** * @param resource $source * @param resource $destination + * @internal */ public static function downloadFromIdFailed(mixed $id, $source, $destination): self { @@ -35,7 +37,10 @@ public static function downloadFromIdFailed(mixed $id, $source, $destination): s return new self(sprintf('Downloading file from "%s" to "%s" failed. GridFS identifier: "%s"', $sourceMetadata['uri'], $destinationMetadata['uri'], $idString)); } - /** @param resource $source */ + /** + * @param resource $source + * @internal + */ public static function uploadFailed(string $filename, $source, string $destinationUri): self { $sourceMetadata = stream_get_meta_data($source); diff --git a/src/GridFS/ReadableStream.php b/src/GridFS/ReadableStream.php index 94275b9b7..cdc5d2d8d 100644 --- a/src/GridFS/ReadableStream.php +++ b/src/GridFS/ReadableStream.php @@ -17,7 +17,6 @@ namespace MongoDB\GridFS; -use Iterator; use MongoDB\BSON\Binary; use MongoDB\Driver\CursorInterface; use MongoDB\Exception\InvalidArgumentException; @@ -38,7 +37,7 @@ * * @internal */ -class ReadableStream +final class ReadableStream { private ?string $buffer = null; @@ -48,8 +47,7 @@ class ReadableStream private int $chunkOffset = 0; - /** @var (CursorInterface&Iterator)|null */ - private ?Iterator $chunksIterator = null; + private ?CursorInterface $chunksIterator = null; private int $expectedLastChunkSize = 0; diff --git a/src/GridFS/StreamWrapper.php b/src/GridFS/StreamWrapper.php index cc1d89fc9..e4f57fa85 100644 --- a/src/GridFS/StreamWrapper.php +++ b/src/GridFS/StreamWrapper.php @@ -49,7 +49,7 @@ * @see Bucket::openDownloadStream() * @psalm-type ContextOptions = array{collectionWrapper: CollectionWrapper, file: object}|array{collectionWrapper: CollectionWrapper, filename: string, options: array} */ -class StreamWrapper +final class StreamWrapper { /** @var resource|null Stream context (set by PHP) */ public $context; @@ -89,7 +89,7 @@ public static function register(string $protocol = 'gridfs'): void stream_wrapper_unregister($protocol); } - stream_wrapper_register($protocol, static::class, STREAM_IS_URL); + stream_wrapper_register($protocol, self::class, STREAM_IS_URL); } /** @@ -315,8 +315,7 @@ public function unlink(string $path): bool return true; } - /** @return false|array */ - public function url_stat(string $path, int $flags) + public function url_stat(string $path, int $flags): false|array { assert($this->stream === null); diff --git a/src/GridFS/WritableStream.php b/src/GridFS/WritableStream.php index 65b35de90..e7a4e0e63 100644 --- a/src/GridFS/WritableStream.php +++ b/src/GridFS/WritableStream.php @@ -17,7 +17,6 @@ namespace MongoDB\GridFS; -use HashContext; use MongoDB\BSON\Binary; use MongoDB\BSON\ObjectId; use MongoDB\BSON\UTCDateTime; @@ -25,14 +24,8 @@ use MongoDB\Exception\InvalidArgumentException; use function array_intersect_key; -use function hash_final; -use function hash_init; -use function hash_update; -use function is_bool; use function is_integer; -use function is_string; use function MongoDB\is_document; -use function MongoDB\is_string_array; use function sprintf; use function strlen; use function substr; @@ -42,7 +35,7 @@ * * @internal */ -class WritableStream +final class WritableStream { private const DEFAULT_CHUNK_SIZE_BYTES = 261120; @@ -52,12 +45,8 @@ class WritableStream private int $chunkSize; - private bool $disableMD5; - private array $file; - private ?HashContext $hashCtx = null; - private bool $isClosed = false; private int $length = 0; @@ -69,19 +58,9 @@ class WritableStream * * * _id (mixed): File document identifier. Defaults to a new ObjectId. * - * * aliases (array of strings): DEPRECATED An array of aliases. - * Applications wishing to store aliases should add an aliases field to - * the metadata document instead. - * * * chunkSizeBytes (integer): The chunk size in bytes. Defaults to * 261120 (i.e. 255 KiB). * - * * disableMD5 (boolean): When true, no MD5 sum will be generated. - * Defaults to "false". - * - * * contentType (string): DEPRECATED content type to be stored with the - * file. This information should now be added to the metadata. - * * * metadata (document): User data for the "metadata" field of the files * collection document. * @@ -95,13 +74,8 @@ public function __construct(private CollectionWrapper $collectionWrapper, string $options += [ '_id' => new ObjectId(), 'chunkSizeBytes' => self::DEFAULT_CHUNK_SIZE_BYTES, - 'disableMD5' => false, ]; - if (isset($options['aliases']) && ! is_string_array($options['aliases'])) { - throw InvalidArgumentException::invalidType('"aliases" option', $options['aliases'], 'array of strings'); - } - if (! is_integer($options['chunkSizeBytes'])) { throw InvalidArgumentException::invalidType('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer'); } @@ -110,32 +84,18 @@ public function __construct(private CollectionWrapper $collectionWrapper, string throw new InvalidArgumentException(sprintf('Expected "chunkSizeBytes" option to be >= 1, %d given', $options['chunkSizeBytes'])); } - if (! is_bool($options['disableMD5'])) { - throw InvalidArgumentException::invalidType('"disableMD5" option', $options['disableMD5'], 'boolean'); - } - - if (isset($options['contentType']) && ! is_string($options['contentType'])) { - throw InvalidArgumentException::invalidType('"contentType" option', $options['contentType'], 'string'); - } - if (isset($options['metadata']) && ! is_document($options['metadata'])) { throw InvalidArgumentException::expectedDocumentType('"metadata" option', $options['metadata']); } $this->chunkSize = $options['chunkSizeBytes']; - $this->disableMD5 = $options['disableMD5']; - - if (! $this->disableMD5) { - $this->hashCtx = hash_init('md5'); - } - $this->file = [ '_id' => $options['_id'], 'chunkSize' => $this->chunkSize, 'filename' => $filename, 'length' => null, 'uploadDate' => null, - ] + array_intersect_key($options, ['aliases' => 1, 'contentType' => 1, 'metadata' => 1]); + ] + array_intersect_key($options, ['metadata' => 1]); } /** @@ -248,10 +208,6 @@ private function fileCollectionInsert(): void $this->file['length'] = $this->length; $this->file['uploadDate'] = new UTCDateTime(); - if (! $this->disableMD5 && $this->hashCtx) { - $this->file['md5'] = hash_final($this->hashCtx); - } - try { $this->collectionWrapper->insertFile($this->file); } catch (DriverRuntimeException $e) { @@ -276,10 +232,6 @@ private function insertChunkFromBuffer(): void 'data' => new Binary($data), ]; - if (! $this->disableMD5 && $this->hashCtx) { - hash_update($this->hashCtx, $data); - } - try { $this->collectionWrapper->insertChunk($chunk); } catch (DriverRuntimeException $e) { diff --git a/src/InsertManyResult.php b/src/InsertManyResult.php index 6ad2a9595..2f27bd24e 100644 --- a/src/InsertManyResult.php +++ b/src/InsertManyResult.php @@ -17,19 +17,16 @@ namespace MongoDB; +use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\WriteResult; -use MongoDB\Exception\BadMethodCallException; /** * Result class for a multi-document insert operation. */ class InsertManyResult { - private bool $isAcknowledged; - public function __construct(private WriteResult $writeResult, private array $insertedIds) { - $this->isAcknowledged = $writeResult->isAcknowledged(); } /** @@ -38,16 +35,11 @@ public function __construct(private WriteResult $writeResult, private array $ins * This method should only be called if the write was acknowledged. * * @see InsertManyResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getInsertedCount() + public function getInsertedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getInsertedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getInsertedCount(); } /** @@ -58,10 +50,8 @@ public function getInsertedCount() * the driver did not generate an ID), the index will contain its "_id" * field value. Any driver-generated ID will be a MongoDB\BSON\ObjectId * instance. - * - * @return array */ - public function getInsertedIds() + public function getInsertedIds(): array { return $this->insertedIds; } @@ -71,10 +61,8 @@ public function getInsertedIds() * * If the insert was not acknowledged, other fields from the WriteResult * (e.g. insertedCount) will be undefined. - * - * @return boolean */ - public function isAcknowledged() + public function isAcknowledged(): bool { return $this->writeResult->isAcknowledged(); } diff --git a/src/InsertOneResult.php b/src/InsertOneResult.php index 9c72775c8..47e1d26bc 100644 --- a/src/InsertOneResult.php +++ b/src/InsertOneResult.php @@ -17,19 +17,16 @@ namespace MongoDB; +use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\WriteResult; -use MongoDB\Exception\BadMethodCallException; /** * Result class for a single-document insert operation. */ class InsertOneResult { - private bool $isAcknowledged; - public function __construct(private WriteResult $writeResult, private mixed $insertedId) { - $this->isAcknowledged = $writeResult->isAcknowledged(); } /** @@ -38,16 +35,11 @@ public function __construct(private WriteResult $writeResult, private mixed $ins * This method should only be called if the write was acknowledged. * * @see InsertOneResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getInsertedCount() + public function getInsertedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getInsertedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getInsertedCount(); } /** @@ -56,10 +48,8 @@ public function getInsertedCount() * If the document had an ID prior to inserting (i.e. the driver did not * need to generate an ID), this will contain its "_id". Any * driver-generated ID will be a MongoDB\BSON\ObjectId instance. - * - * @return mixed */ - public function getInsertedId() + public function getInsertedId(): mixed { return $this->insertedId; } @@ -73,10 +63,8 @@ public function getInsertedId() * If the insert was not acknowledged, other fields from the WriteResult * (e.g. insertedCount) will be undefined and their getter methods should * not be invoked. - * - * @return boolean */ - public function isAcknowledged() + public function isAcknowledged(): bool { return $this->writeResult->isAcknowledged(); } diff --git a/src/MapReduceResult.php b/src/MapReduceResult.php deleted file mode 100644 index 62e21d0e5..000000000 --- a/src/MapReduceResult.php +++ /dev/null @@ -1,111 +0,0 @@ - - * @psalm-type MapReduceCallable = callable(): Traversable - */ -class MapReduceResult implements IteratorAggregate -{ - /** - * @var callable - * @psalm-var MapReduceCallable - */ - private $getIterator; - - private int $executionTimeMS; - - private array $counts; - - private array $timing; - - /** - * Returns various count statistics from the mapReduce command. - * - * @return array - */ - public function getCounts() - { - return $this->counts; - } - - /** - * Return the command execution time in milliseconds. - * - * @return integer - */ - public function getExecutionTimeMS() - { - return $this->executionTimeMS; - } - - /** - * Return the mapReduce results as a Traversable. - * - * @see https://php.net/iteratoraggregate.getiterator - * @return Traversable - */ - #[ReturnTypeWillChange] - public function getIterator() - { - return call_user_func($this->getIterator); - } - - /** - * Returns various timing statistics from the mapReduce command. - * - * Note: timing statistics are only available if the mapReduce command's - * "verbose" option was true; otherwise, an empty array will be returned. - * - * @return array - */ - public function getTiming() - { - return $this->timing; - } - - /** - * @internal - * @param callable $getIterator Callback that returns a Traversable for mapReduce results - * @param stdClass $result Result document from the mapReduce command - * @psalm-param MapReduceCallable $getIterator - */ - public function __construct(callable $getIterator, stdClass $result) - { - $this->getIterator = $getIterator; - $this->executionTimeMS = isset($result->timeMillis) ? (int) $result->timeMillis : 0; - $this->counts = isset($result->counts) ? (array) $result->counts : []; - $this->timing = isset($result->timing) ? (array) $result->timing : []; - } -} diff --git a/src/Model/BSONArray.php b/src/Model/BSONArray.php index f055f4cb7..040350ce4 100644 --- a/src/Model/BSONArray.php +++ b/src/Model/BSONArray.php @@ -21,7 +21,6 @@ use JsonSerializable; use MongoDB\BSON\Serializable; use MongoDB\BSON\Unserializable; -use ReturnTypeWillChange; use function array_values; use function MongoDB\recursive_copy; @@ -51,9 +50,8 @@ public function __clone() * * @see https://php.net/oop5.magic#object.set-state * @see https://php.net/var-export - * @return self */ - public static function __set_state(array $properties) + public static function __set_state(array $properties): self { $array = new self(); $array->exchangeArray($properties); @@ -68,10 +66,8 @@ public static function __set_state(array $properties) * as a BSON array. * * @see https://php.net/mongodb-bson-serializable.bsonserialize - * @return array */ - #[ReturnTypeWillChange] - public function bsonSerialize() + public function bsonSerialize(): array { return array_values($this->getArrayCopy()); } @@ -82,8 +78,7 @@ public function bsonSerialize() * @see https://php.net/mongodb-bson-unserializable.bsonunserialize * @param array $data Array data */ - #[ReturnTypeWillChange] - public function bsonUnserialize(array $data) + public function bsonUnserialize(array $data): void { parent::__construct($data); } @@ -95,10 +90,8 @@ public function bsonUnserialize(array $data) * as a JSON array. * * @see https://php.net/jsonserializable.jsonserialize - * @return array */ - #[ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): array { return array_values($this->getArrayCopy()); } diff --git a/src/Model/BSONDocument.php b/src/Model/BSONDocument.php index 1ff8bc1e6..545f08dc0 100644 --- a/src/Model/BSONDocument.php +++ b/src/Model/BSONDocument.php @@ -22,7 +22,6 @@ use JsonSerializable; use MongoDB\BSON\Serializable; use MongoDB\BSON\Unserializable; -use ReturnTypeWillChange; use stdClass; use function MongoDB\recursive_copy; @@ -65,9 +64,8 @@ public function __construct(array|stdClass $input = [], int $flags = ArrayObject * * @see https://php.net/oop5.magic#object.set-state * @see https://php.net/var-export - * @return self */ - public static function __set_state(array $properties) + public static function __set_state(array $properties): self { $document = new self(); $document->exchangeArray($properties); @@ -79,10 +77,8 @@ public static function __set_state(array $properties) * Serialize the document to BSON. * * @see https://php.net/mongodb-bson-serializable.bsonserialize - * @return stdClass */ - #[ReturnTypeWillChange] - public function bsonSerialize() + public function bsonSerialize(): stdClass { return (object) $this->getArrayCopy(); } @@ -93,8 +89,7 @@ public function bsonSerialize() * @see https://php.net/mongodb-bson-unserializable.bsonunserialize * @param array $data Array data */ - #[ReturnTypeWillChange] - public function bsonUnserialize(array $data) + public function bsonUnserialize(array $data): void { parent::__construct($data, ArrayObject::ARRAY_AS_PROPS); } @@ -103,10 +98,8 @@ public function bsonUnserialize(array $data) * Serialize the array to JSON. * * @see https://php.net/jsonserializable.jsonserialize - * @return object */ - #[ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): stdClass { return (object) $this->getArrayCopy(); } diff --git a/src/Model/BSONIterator.php b/src/Model/BSONIterator.php index 2ca257c7f..e90901173 100644 --- a/src/Model/BSONIterator.php +++ b/src/Model/BSONIterator.php @@ -21,7 +21,6 @@ use MongoDB\BSON\Document; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnexpectedValueException; -use ReturnTypeWillChange; use function assert; use function is_array; @@ -50,44 +49,28 @@ class BSONIterator implements Iterator private int $position = 0; - /** - * @see https://php.net/iterator.current - * @return mixed - */ - #[ReturnTypeWillChange] - public function current() + /** @see https://php.net/iterator.current */ + public function current(): mixed { return $this->current; } - /** - * @see https://php.net/iterator.key - * @return int - */ - #[ReturnTypeWillChange] - public function key() + /** @see https://php.net/iterator.key */ + public function key(): int { return $this->key; } - /** - * @see https://php.net/iterator.next - * @return void - */ - #[ReturnTypeWillChange] - public function next() + /** @see https://php.net/iterator.next */ + public function next(): void { $this->key++; $this->current = null; $this->advance(); } - /** - * @see https://php.net/iterator.rewind - * @return void - */ - #[ReturnTypeWillChange] - public function rewind() + /** @see https://php.net/iterator.rewind */ + public function rewind(): void { $this->key = 0; $this->position = 0; @@ -96,7 +79,6 @@ public function rewind() } /** @see https://php.net/iterator.valid */ - #[ReturnTypeWillChange] public function valid(): bool { return $this->current !== null; diff --git a/src/Model/CachingIterator.php b/src/Model/CachingIterator.php index 15ba7ca1a..64c61708f 100644 --- a/src/Model/CachingIterator.php +++ b/src/Model/CachingIterator.php @@ -20,7 +20,6 @@ use Countable; use Iterator; use IteratorIterator; -use ReturnTypeWillChange; use Traversable; use function count; @@ -40,7 +39,7 @@ * @template TValue * @template-implements Iterator */ -class CachingIterator implements Countable, Iterator +final class CachingIterator implements Countable, Iterator { private const FIELD_KEY = 0; private const FIELD_VALUE = 1; @@ -79,12 +78,8 @@ public function count(): int return count($this->items); } - /** - * @see https://php.net/iterator.current - * @return mixed - */ - #[ReturnTypeWillChange] - public function current() + /** @see https://php.net/iterator.current */ + public function current(): mixed { $currentItem = current($this->items); @@ -93,11 +88,9 @@ public function current() /** * @see https://php.net/iterator.key - * @return mixed * @psalm-return TKey|null */ - #[ReturnTypeWillChange] - public function key() + public function key(): mixed { $currentItem = current($this->items); diff --git a/src/Model/CallbackIterator.php b/src/Model/CallbackIterator.php index ea429ccbc..4a8e5dda9 100644 --- a/src/Model/CallbackIterator.php +++ b/src/Model/CallbackIterator.php @@ -19,7 +19,6 @@ use Iterator; use IteratorIterator; -use ReturnTypeWillChange; use Traversable; use function call_user_func; @@ -29,12 +28,12 @@ * * @internal * - * @template TKey + * @template TKey of array-key * @template TValue * @template TCallbackValue * @template-implements Iterator */ -class CallbackIterator implements Iterator +final class CallbackIterator implements Iterator { /** @var callable(TValue, TKey): TCallbackValue */ private $callback; @@ -56,8 +55,7 @@ public function __construct(Traversable $traversable, callable $callback) * @see https://php.net/iterator.current * @return TCallbackValue */ - #[ReturnTypeWillChange] - public function current() + public function current(): mixed { return call_user_func($this->callback, $this->iterator->current(), $this->iterator->key()); } @@ -66,8 +64,7 @@ public function current() * @see https://php.net/iterator.key * @return TKey */ - #[ReturnTypeWillChange] - public function key() + public function key(): mixed { return $this->iterator->key(); } diff --git a/src/Model/ChangeStreamIterator.php b/src/Model/ChangeStreamIterator.php index aa635dda9..4cd7a57af 100644 --- a/src/Model/ChangeStreamIterator.php +++ b/src/Model/ChangeStreamIterator.php @@ -17,7 +17,6 @@ namespace MongoDB\Model; -use Iterator; use IteratorIterator; use MongoDB\BSON\Document; use MongoDB\BSON\Serializable; @@ -30,7 +29,6 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\ResumeTokenException; use MongoDB\Exception\UnexpectedValueException; -use ReturnTypeWillChange; use function assert; use function count; @@ -49,9 +47,9 @@ * * @internal * @template TValue of array|object - * @template-extends IteratorIterator&Iterator> + * @template-extends IteratorIterator> */ -class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber +final class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber { private int $batchPosition = 0; @@ -67,28 +65,25 @@ class ChangeStreamIterator extends IteratorIterator implements CommandSubscriber /** * @see https://php.net/iteratoriterator.current - * @return array|object|null * @psalm-return TValue|null */ - #[ReturnTypeWillChange] - public function current() + public function current(): array|object|null { return $this->valid() ? parent::current() : null; } /** - * Necessary to let psalm know that we're always expecting a cursor as inner - * iterator. This could be side-stepped due to the class not being final, - * but it's very much an invalid use-case. This method can be dropped in 2.0 - * once the class is final. + * This method is necessary as psalm does not properly set the return type + * of IteratorIterator::getInnerIterator to the templated iterator * - * @return CursorInterface&Iterator + * @see https://github.com/vimeo/psalm/pull/11100. + * + * @return CursorInterface */ - final public function getInnerIterator(): Iterator + public function getInnerIterator(): CursorInterface { $cursor = parent::getInnerIterator(); assert($cursor instanceof CursorInterface); - assert($cursor instanceof Iterator); return $cursor; } @@ -99,10 +94,8 @@ final public function getInnerIterator(): Iterator * Null may be returned if no change documents have been iterated and the * server did not include a postBatchResumeToken in its aggregate or getMore * command response. - * - * @return array|object|null */ - public function getResumeToken() + public function getResumeToken(): array|object|null { return $this->resumeToken; } @@ -115,12 +108,8 @@ public function getServer(): Server return $this->server; } - /** - * @see https://php.net/iteratoriterator.key - * @return int|null - */ - #[ReturnTypeWillChange] - public function key() + /** @see https://php.net/iteratoriterator.key */ + public function key(): ?int { return $this->valid() ? parent::key() : null; } @@ -173,18 +162,10 @@ public function valid(): bool /** * @internal - * @psalm-param CursorInterface&Iterator $cursor + * @psalm-param CursorInterface $cursor */ public function __construct(CursorInterface $cursor, int $firstBatchSize, array|object|null $initialResumeToken, private ?object $postBatchResumeToken = null) { - if (! $cursor instanceof Iterator) { - throw InvalidArgumentException::invalidType( - '$cursor', - $cursor, - CursorInterface::class . '&' . Iterator::class, - ); - } - if (isset($initialResumeToken) && ! is_document($initialResumeToken)) { throw InvalidArgumentException::expectedDocumentType('$initialResumeToken', $initialResumeToken); } @@ -238,11 +219,10 @@ final public function commandSucceeded(CommandSucceededEvent $event): void * Extracts the resume token (i.e. "_id" field) from a change document. * * @param array|object $document Change document - * @return array|object * @throws InvalidArgumentException * @throws ResumeTokenException if the resume token is not found or invalid */ - private function extractResumeToken(array|object $document) + private function extractResumeToken(array|object $document): array|object { if (! is_document($document)) { throw InvalidArgumentException::expectedDocumentType('$document', $document); @@ -290,7 +270,7 @@ private function isAtEndOfBatch(): bool /** * Perform housekeeping after an iteration event. * - * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#updating-the-cached-resume-token + * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.md#updating-the-cached-resume-token */ private function onIteration(bool $incrementBatchPosition): void { diff --git a/src/Model/CodecCursor.php b/src/Model/CodecCursor.php index 642f3c3c1..d7a3776b2 100644 --- a/src/Model/CodecCursor.php +++ b/src/Model/CodecCursor.php @@ -17,30 +17,24 @@ namespace MongoDB\Model; -use Iterator; use MongoDB\BSON\Document; use MongoDB\BSON\Int64; use MongoDB\Codec\DocumentCodec; -use MongoDB\Driver\Cursor; -use MongoDB\Driver\CursorId; use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Server; -use ReturnTypeWillChange; use function assert; use function iterator_to_array; use function sprintf; use function trigger_error; -use const E_USER_DEPRECATED; use const E_USER_WARNING; /** * @template TValue of object - * @template-implements CursorInterface - * @template-implements Iterator + * @template-implements CursorInterface */ -class CodecCursor implements CursorInterface, Iterator +class CodecCursor implements CursorInterface { private const TYPEMAP = ['root' => 'bson']; @@ -64,33 +58,16 @@ public function current(): ?object * @param DocumentCodec $codec * @return self */ - public static function fromCursor(Cursor $cursor, DocumentCodec $codec): self + public static function fromCursor(CursorInterface $cursor, DocumentCodec $codec): self { $cursor->setTypeMap(self::TYPEMAP); return new self($cursor, $codec); } - /** - * @return CursorId|Int64 - * @psalm-return ($asInt64 is true ? Int64 : CursorId) - */ - #[ReturnTypeWillChange] - public function getId(bool $asInt64 = false) + public function getId(): Int64 { - if (! $asInt64) { - @trigger_error( - sprintf( - 'The method "%s" will no longer return a "%s" instance in the future. Pass "true" as argument to change to the new behavior and receive a "%s" instance instead.', - __METHOD__, - CursorId::class, - Int64::class, - ), - E_USER_DEPRECATED, - ); - } - - return $this->cursor->getId($asInt64); + return $this->cursor->getId(); } public function getServer(): Server @@ -138,7 +115,7 @@ public function valid(): bool } /** @param DocumentCodec $codec */ - private function __construct(private Cursor $cursor, private DocumentCodec $codec) + private function __construct(private CursorInterface $cursor, private DocumentCodec $codec) { } } diff --git a/src/Model/CollectionInfo.php b/src/Model/CollectionInfo.php index 893fea6bf..3d273e598 100644 --- a/src/Model/CollectionInfo.php +++ b/src/Model/CollectionInfo.php @@ -19,7 +19,6 @@ use ArrayAccess; use MongoDB\Exception\BadMethodCallException; -use ReturnTypeWillChange; use function array_key_exists; @@ -31,7 +30,7 @@ * collection. It provides methods to access options for the collection. * * @see \MongoDB\Database::listCollections() - * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.rst + * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-collections.md * @template-implements ArrayAccess */ class CollectionInfo implements ArrayAccess @@ -45,9 +44,8 @@ public function __construct(private array $info) * Return the collection info as an array. * * @see https://php.net/oop5.magic#language.oop5.magic.debuginfo - * @return array */ - public function __debugInfo() + public function __debugInfo(): array { return $this->info; } @@ -56,10 +54,8 @@ public function __debugInfo() * Return the maximum number of documents to keep in the capped collection. * * @deprecated 1.0 Deprecated in favor of using getOptions - * - * @return integer|null */ - public function getCappedMax() + public function getCappedMax(): ?int { /* The MongoDB server might return this number as an integer or float */ return isset($this->info['options']['max']) ? (int) $this->info['options']['max'] : null; @@ -69,10 +65,8 @@ public function getCappedMax() * Return the maximum size (in bytes) of the capped collection. * * @deprecated 1.0 Deprecated in favor of using getOptions - * - * @return integer|null */ - public function getCappedSize() + public function getCappedSize(): ?int { /* The MongoDB server might return this number as an integer or float */ return isset($this->info['options']['size']) ? (int) $this->info['options']['size'] : null; @@ -100,9 +94,8 @@ public function getInfo(): array * Return the collection name. * * @see https://mongodb.com/docs/manual/reference/command/listCollections/#output - * @return string */ - public function getName() + public function getName(): string { return (string) $this->info['name']; } @@ -111,9 +104,8 @@ public function getName() * Return the collection options. * * @see https://mongodb.com/docs/manual/reference/command/listCollections/#output - * @return array */ - public function getOptions() + public function getOptions(): array { return (array) ($this->info['options'] ?? []); } @@ -132,23 +124,27 @@ public function getType(): string * Return whether the collection is a capped collection. * * @deprecated 1.0 Deprecated in favor of using getOptions - * - * @return boolean */ - public function isCapped() + public function isCapped(): bool { return ! empty($this->info['options']['capped']); } + /** + * Determines whether the collection is a view. + */ + public function isView(): bool + { + return $this->getType() === 'view'; + } + /** * Check whether a field exists in the collection information. * * @see https://php.net/arrayaccess.offsetexists - * @return boolean * @psalm-param array-key $offset */ - #[ReturnTypeWillChange] - public function offsetExists(mixed $offset) + public function offsetExists(mixed $offset): bool { return array_key_exists($offset, $this->info); } @@ -157,11 +153,9 @@ public function offsetExists(mixed $offset) * Return the field's value from the collection information. * * @see https://php.net/arrayaccess.offsetget - * @return mixed * @psalm-param array-key $offset */ - #[ReturnTypeWillChange] - public function offsetGet(mixed $offset) + public function offsetGet(mixed $offset): mixed { return $this->info[$offset]; } @@ -171,10 +165,8 @@ public function offsetGet(mixed $offset) * * @see https://php.net/arrayaccess.offsetset * @throws BadMethodCallException - * @return void */ - #[ReturnTypeWillChange] - public function offsetSet(mixed $offset, mixed $value) + public function offsetSet(mixed $offset, mixed $value): void { throw BadMethodCallException::classIsImmutable(self::class); } @@ -184,10 +176,8 @@ public function offsetSet(mixed $offset, mixed $value) * * @see https://php.net/arrayaccess.offsetunset * @throws BadMethodCallException - * @return void */ - #[ReturnTypeWillChange] - public function offsetUnset(mixed $offset) + public function offsetUnset(mixed $offset): void { throw BadMethodCallException::classIsImmutable(self::class); } diff --git a/src/Model/CollectionInfoCommandIterator.php b/src/Model/CollectionInfoCommandIterator.php deleted file mode 100644 index 543d8fc38..000000000 --- a/src/Model/CollectionInfoCommandIterator.php +++ /dev/null @@ -1,60 +0,0 @@ -> - */ -class CollectionInfoCommandIterator extends IteratorIterator implements CollectionInfoIterator -{ - /** @param Traversable $iterator */ - public function __construct(Traversable $iterator, private ?string $databaseName = null) - { - parent::__construct($iterator); - } - - /** - * Return the current element as a CollectionInfo instance. - * - * @see CollectionInfoIterator::current() - * @see https://php.net/iterator.current - */ - public function current(): CollectionInfo - { - $info = parent::current(); - - if ($this->databaseName !== null && isset($info['idIndex']) && ! isset($info['idIndex']['ns'])) { - $info['idIndex']['ns'] = $this->databaseName . '.' . $info['name']; - } - - return new CollectionInfo($info); - } -} diff --git a/src/Model/CollectionInfoIterator.php b/src/Model/CollectionInfoIterator.php deleted file mode 100644 index e7ebf4984..000000000 --- a/src/Model/CollectionInfoIterator.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ -interface CollectionInfoIterator extends Iterator -{ - /** - * Return the current element as a CollectionInfo instance. - * - * @return CollectionInfo - */ - #[ReturnTypeWillChange] - public function current(); -} diff --git a/src/Model/DatabaseInfo.php b/src/Model/DatabaseInfo.php index c46bfb7d3..4e8fd8c51 100644 --- a/src/Model/DatabaseInfo.php +++ b/src/Model/DatabaseInfo.php @@ -19,7 +19,6 @@ use ArrayAccess; use MongoDB\Exception\BadMethodCallException; -use ReturnTypeWillChange; use function array_key_exists; @@ -44,29 +43,24 @@ public function __construct(private array $info) * Return the database info as an array. * * @see https://php.net/oop5.magic#language.oop5.magic.debuginfo - * @return array */ - public function __debugInfo() + public function __debugInfo(): array { return $this->info; } /** * Return the database name. - * - * @return string */ - public function getName() + public function getName(): string { return (string) $this->info['name']; } /** * Return the databases size on disk (in bytes). - * - * @return integer */ - public function getSizeOnDisk() + public function getSizeOnDisk(): int { /* The MongoDB server might return this number as an integer or float */ return (int) $this->info['sizeOnDisk']; @@ -74,10 +68,8 @@ public function getSizeOnDisk() /** * Return whether the database is empty. - * - * @return boolean */ - public function isEmpty() + public function isEmpty(): bool { return (bool) $this->info['empty']; } @@ -86,11 +78,9 @@ public function isEmpty() * Check whether a field exists in the database information. * * @see https://php.net/arrayaccess.offsetexists - * @return boolean * @psalm-param array-key $offset */ - #[ReturnTypeWillChange] - public function offsetExists(mixed $offset) + public function offsetExists(mixed $offset): bool { return array_key_exists($offset, $this->info); } @@ -99,11 +89,9 @@ public function offsetExists(mixed $offset) * Return the field's value from the database information. * * @see https://php.net/arrayaccess.offsetget - * @return mixed * @psalm-param array-key $offset */ - #[ReturnTypeWillChange] - public function offsetGet(mixed $offset) + public function offsetGet(mixed $offset): mixed { return $this->info[$offset]; } @@ -113,10 +101,8 @@ public function offsetGet(mixed $offset) * * @see https://php.net/arrayaccess.offsetset * @throws BadMethodCallException - * @return void */ - #[ReturnTypeWillChange] - public function offsetSet(mixed $offset, mixed $value) + public function offsetSet(mixed $offset, mixed $value): void { throw BadMethodCallException::classIsImmutable(self::class); } @@ -126,10 +112,8 @@ public function offsetSet(mixed $offset, mixed $value) * * @see https://php.net/arrayaccess.offsetunset * @throws BadMethodCallException - * @return void */ - #[ReturnTypeWillChange] - public function offsetUnset(mixed $offset) + public function offsetUnset(mixed $offset): void { throw BadMethodCallException::classIsImmutable(self::class); } diff --git a/src/Model/DatabaseInfoIterator.php b/src/Model/DatabaseInfoIterator.php deleted file mode 100644 index 72199bd32..000000000 --- a/src/Model/DatabaseInfoIterator.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ -interface DatabaseInfoIterator extends Iterator -{ - /** - * Return the current element as a DatabaseInfo instance. - * - * @return DatabaseInfo - */ - #[ReturnTypeWillChange] - public function current(); -} diff --git a/src/Model/DatabaseInfoLegacyIterator.php b/src/Model/DatabaseInfoLegacyIterator.php deleted file mode 100644 index 1348b0c57..000000000 --- a/src/Model/DatabaseInfoLegacyIterator.php +++ /dev/null @@ -1,92 +0,0 @@ -databases)); - } - - /** - * Return the key of the current element. - * - * @see https://php.net/iterator.key - */ - public function key(): int - { - return key($this->databases); - } - - /** - * Move forward to next element. - * - * @see https://php.net/iterator.next - */ - public function next(): void - { - next($this->databases); - } - - /** - * Rewind the Iterator to the first element. - * - * @see https://php.net/iterator.rewind - */ - public function rewind(): void - { - reset($this->databases); - } - - /** - * Checks if current position is valid. - * - * @see https://php.net/iterator.valid - */ - public function valid(): bool - { - return key($this->databases) !== null; - } -} diff --git a/src/Model/IndexInfo.php b/src/Model/IndexInfo.php index eeb4368a5..86ae3dc6b 100644 --- a/src/Model/IndexInfo.php +++ b/src/Model/IndexInfo.php @@ -19,13 +19,10 @@ use ArrayAccess; use MongoDB\Exception\BadMethodCallException; -use ReturnTypeWillChange; +use Stringable; use function array_key_exists; use function array_search; -use function trigger_error; - -use const E_USER_DEPRECATED; /** * Index information model class. @@ -38,11 +35,11 @@ * db.collection.createIndex() documentation. * * @see \MongoDB\Collection::listIndexes() - * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst + * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.md * @see https://mongodb.com/docs/manual/reference/method/db.collection.createIndex/ * @template-implements ArrayAccess */ -class IndexInfo implements ArrayAccess +class IndexInfo implements ArrayAccess, Stringable { /** @param array $info Index info */ public function __construct(private array $info) @@ -53,107 +50,66 @@ public function __construct(private array $info) * Return the collection info as an array. * * @see https://php.net/oop5.magic#language.oop5.magic.debuginfo - * @return array */ - public function __debugInfo() + public function __debugInfo(): array { return $this->info; } /** * Return the index name to allow casting IndexInfo to string. - * - * @return string */ - public function __toString() + public function __toString(): string { return $this->getName(); } /** * Return the index key. - * - * @return array */ - public function getKey() + public function getKey(): array { return (array) $this->info['key']; } /** * Return the index name. - * - * @return string */ - public function getName() + public function getName(): string { return (string) $this->info['name']; } - /** - * Return the index namespace (e.g. "db.collection"). - * - * @deprecated - * - * @return string - */ - public function getNamespace() - { - @trigger_error('MongoDB 4.4 drops support for the namespace in indexes, the method "IndexInfo::getNamespace()" will be removed in version 2.0', E_USER_DEPRECATED); - - return (string) $this->info['ns']; - } - /** * Return the index version. - * - * @return integer */ - public function getVersion() + public function getVersion(): int { return (int) $this->info['v']; } /** * Return whether or not this index is of type 2dsphere. - * - * @return boolean */ - public function is2dSphere() + public function is2dSphere(): bool { return array_search('2dsphere', $this->getKey(), true) !== false; } - /** - * Return whether or not this index is of type geoHaystack. - * - * @return boolean - * @deprecated Since 1.16: MongoDB 5.0 removes support for geoHaystack indexes. - */ - public function isGeoHaystack() - { - @trigger_error('MongoDB 5.0 removes support for "geoHaystack" indexes, the method "IndexInfo::isGeoHaystack()" will be removed in version 2.0', E_USER_DEPRECATED); - - return array_search('geoHaystack', $this->getKey(), true) !== false; - } - /** * Return whether this is a sparse index. * * @see https://mongodb.com/docs/manual/core/index-sparse/ - * @return boolean */ - public function isSparse() + public function isSparse(): bool { return ! empty($this->info['sparse']); } /** * Return whether or not this index is of type text. - * - * @return boolean */ - public function isText() + public function isText(): bool { return array_search('text', $this->getKey(), true) !== false; } @@ -162,9 +118,8 @@ public function isText() * Return whether this is a TTL index. * * @see https://mongodb.com/docs/manual/core/index-ttl/ - * @return boolean */ - public function isTtl() + public function isTtl(): bool { return array_key_exists('expireAfterSeconds', $this->info); } @@ -173,9 +128,8 @@ public function isTtl() * Return whether this is a unique index. * * @see https://mongodb.com/docs/manual/core/index-unique/ - * @return boolean */ - public function isUnique() + public function isUnique(): bool { return ! empty($this->info['unique']); } @@ -184,11 +138,9 @@ public function isUnique() * Check whether a field exists in the index information. * * @see https://php.net/arrayaccess.offsetexists - * @return boolean * @psalm-param array-key $offset */ - #[ReturnTypeWillChange] - public function offsetExists(mixed $offset) + public function offsetExists(mixed $offset): bool { return array_key_exists($offset, $this->info); } @@ -201,12 +153,10 @@ public function offsetExists(mixed $offset) * also be used to access fields that do not have a helper method. * * @see https://php.net/arrayaccess.offsetget - * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst#getting-full-index-information - * @return mixed + * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.md#getting-full-index-information * @psalm-param array-key $offset */ - #[ReturnTypeWillChange] - public function offsetGet(mixed $offset) + public function offsetGet(mixed $offset): mixed { return $this->info[$offset]; } @@ -216,10 +166,8 @@ public function offsetGet(mixed $offset) * * @see https://php.net/arrayaccess.offsetset * @throws BadMethodCallException - * @return void */ - #[ReturnTypeWillChange] - public function offsetSet(mixed $offset, mixed $value) + public function offsetSet(mixed $offset, mixed $value): void { throw BadMethodCallException::classIsImmutable(self::class); } @@ -229,10 +177,8 @@ public function offsetSet(mixed $offset, mixed $value) * * @see https://php.net/arrayaccess.offsetunset * @throws BadMethodCallException - * @return void */ - #[ReturnTypeWillChange] - public function offsetUnset(mixed $offset) + public function offsetUnset(mixed $offset): void { throw BadMethodCallException::classIsImmutable(self::class); } diff --git a/src/Model/IndexInfoIterator.php b/src/Model/IndexInfoIterator.php deleted file mode 100644 index b7929c20f..000000000 --- a/src/Model/IndexInfoIterator.php +++ /dev/null @@ -1,41 +0,0 @@ - - */ -interface IndexInfoIterator extends Iterator -{ - /** - * Return the current element as a IndexInfo instance. - * - * @return IndexInfo - */ - #[ReturnTypeWillChange] - public function current(); -} diff --git a/src/Model/IndexInfoIteratorIterator.php b/src/Model/IndexInfoIteratorIterator.php deleted file mode 100644 index 0bcc4bd85..000000000 --- a/src/Model/IndexInfoIteratorIterator.php +++ /dev/null @@ -1,64 +0,0 @@ -> - */ -class IndexInfoIteratorIterator extends IteratorIterator implements IndexInfoIterator -{ - /** @param Traversable $iterator */ - public function __construct(Traversable $iterator, private ?string $ns = null) - { - parent::__construct($iterator); - } - - /** - * Return the current element as an IndexInfo instance. - * - * @see IndexInfoIterator::current() - * @see https://php.net/iterator.current - */ - public function current(): IndexInfo - { - $info = parent::current(); - - if (! array_key_exists('ns', $info) && $this->ns !== null) { - $info['ns'] = $this->ns; - } - - return new IndexInfo($info); - } -} diff --git a/src/Model/IndexInput.php b/src/Model/IndexInput.php index 657c2b6da..98e47369c 100644 --- a/src/Model/IndexInput.php +++ b/src/Model/IndexInput.php @@ -20,6 +20,7 @@ use MongoDB\BSON\Serializable; use MongoDB\Exception\InvalidArgumentException; use stdClass; +use Stringable; use function is_float; use function is_int; @@ -35,10 +36,10 @@ * * @internal * @see \MongoDB\Collection::createIndexes() - * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.rst + * @see https://github.com/mongodb/specifications/blob/master/source/enumerate-indexes.md * @see https://mongodb.com/docs/manual/reference/method/db.collection.createIndex/ */ -class IndexInput implements Serializable +final class IndexInput implements Serializable, Stringable { /** * @param array $index Index specification diff --git a/src/Model/SearchIndexInput.php b/src/Model/SearchIndexInput.php index bad880a7f..073deb1a0 100644 --- a/src/Model/SearchIndexInput.php +++ b/src/Model/SearchIndexInput.php @@ -31,10 +31,10 @@ * * @internal * @see \MongoDB\Collection::createSearchIndexes() - * @see https://github.com/mongodb/specifications/blob/master/source/index-management/index-management.rst#search-indexes + * @see https://github.com/mongodb/specifications/blob/master/source/index-management/index-management.md#search-indexes * @see https://mongodb.com/docs/manual/reference/method/db.collection.createSearchIndex/ */ -class SearchIndexInput implements Serializable +final class SearchIndexInput implements Serializable { /** * @param array{definition: array|object, name?: string, type?: string} $index Search index specification diff --git a/src/Operation/Aggregate.php b/src/Operation/Aggregate.php index 7c968a250..b5da6470c 100644 --- a/src/Operation/Aggregate.php +++ b/src/Operation/Aggregate.php @@ -17,10 +17,8 @@ namespace MongoDB\Operation; -use Iterator; use MongoDB\Codec\DocumentCodec; use MongoDB\Driver\Command; -use MongoDB\Driver\Cursor; use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\ReadConcern; @@ -47,10 +45,8 @@ * * @see \MongoDB\Collection::aggregate() * @see https://mongodb.com/docs/manual/reference/command/aggregate/ - * - * @final extending this class will not be supported in v2.0.0 */ -class Aggregate implements Executable, Explainable +final class Aggregate implements Explainable { private bool $isWrite; @@ -215,13 +211,11 @@ public function __construct(private string $databaseName, private ?string $colle /** * Execute the operation. * - * @see Executable::execute() - * @return CursorInterface&Iterator * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if read concern or write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): CursorInterface { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if ($inTransaction) { @@ -256,9 +250,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { $cmd = $this->createCommandDocument(); @@ -321,7 +314,7 @@ private function createCommandOptions(): array * @see https://php.net/manual/en/mongodb-driver-server.executereadcommand.php * @see https://php.net/manual/en/mongodb-driver-server.executereadwritecommand.php */ - private function executeCommand(Server $server, Command $command): Cursor + private function executeCommand(Server $server, Command $command): CursorInterface { $options = []; diff --git a/src/Operation/BulkWrite.php b/src/Operation/BulkWrite.php index c8f695675..dc6dbb4c7 100644 --- a/src/Operation/BulkWrite.php +++ b/src/Operation/BulkWrite.php @@ -45,10 +45,8 @@ * Operation for executing multiple write operations. * * @see \MongoDB\Collection::bulkWrite() - * - * @final extending this class will not be supported in v2.0.0 */ -class BulkWrite implements Executable +final class BulkWrite { public const DELETE_MANY = 'deleteMany'; public const DELETE_ONE = 'deleteOne'; @@ -195,12 +193,10 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return BulkWriteResult * @throws UnsupportedException if write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): BulkWriteResult { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if ($inTransaction && isset($this->options['writeConcern'])) { diff --git a/src/Operation/ClientBulkWriteCommand.php b/src/Operation/ClientBulkWriteCommand.php new file mode 100644 index 000000000..e7768adcb --- /dev/null +++ b/src/Operation/ClientBulkWriteCommand.php @@ -0,0 +1,95 @@ +options['session']) && ! $this->options['session'] instanceof Session) { + throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); + } + + if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { + throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); + } + + if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { + unset($this->options['writeConcern']); + } + } + + /** + * Execute the operation. + * + * @throws UnsupportedException if write concern is used and unsupported + * @throws DriverRuntimeException for other driver errors (e.g. connection errors) + */ + public function execute(Server $server): BulkWriteCommandResult + { + $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); + if ($inTransaction && isset($this->options['writeConcern'])) { + throw UnsupportedException::writeConcernNotSupportedInTransaction(); + } + + $options = array_filter($this->options, fn ($value) => $value !== null); + + return $server->executeBulkWriteCommand($this->bulkWriteCommand, $options); + } +} diff --git a/src/Operation/Count.php b/src/Operation/Count.php index 1c51aff60..ae40c790f 100644 --- a/src/Operation/Count.php +++ b/src/Operation/Count.php @@ -40,10 +40,8 @@ * * @see \MongoDB\Collection::count() * @see https://mongodb.com/docs/manual/reference/command/count/ - * - * @final extending this class will not be supported in v2.0.0 */ -class Count implements Executable, Explainable +final class Count implements Explainable { /** * Constructs a count command. @@ -126,13 +124,11 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return integer * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if read concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): int { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if ($inTransaction && isset($this->options['readConcern'])) { @@ -154,9 +150,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { $cmd = $this->createCommandDocument(); diff --git a/src/Operation/CountDocuments.php b/src/Operation/CountDocuments.php index 76b111ebe..86046f499 100644 --- a/src/Operation/CountDocuments.php +++ b/src/Operation/CountDocuments.php @@ -35,11 +35,9 @@ * Operation for obtaining an exact count of documents in a collection * * @see \MongoDB\Collection::countDocuments() - * @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#countdocuments - * - * @final extending this class will not be supported in v2.0.0 + * @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.md#countdocuments */ -class CountDocuments implements Executable +final class CountDocuments { private array $aggregateOptions; @@ -105,13 +103,11 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return integer * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if collation or read concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): int { $cursor = $this->aggregate->execute($server); diff --git a/src/Operation/CreateCollection.php b/src/Operation/CreateCollection.php index e0f267af3..45a4925d3 100644 --- a/src/Operation/CreateCollection.php +++ b/src/Operation/CreateCollection.php @@ -24,46 +24,26 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; -use function current; use function is_array; use function is_bool; use function is_integer; use function is_string; use function MongoDB\is_document; use function MongoDB\is_pipeline; -use function trigger_error; - -use const E_USER_DEPRECATED; /** * Operation for the create command. * * @see \MongoDB\Database::createCollection() * @see https://mongodb.com/docs/manual/reference/command/create/ - * - * @final extending this class will not be supported in v2.0.0 */ -class CreateCollection implements Executable +final class CreateCollection { - /** @deprecated 1.21 */ - public const USE_POWER_OF_2_SIZES = 1; - - /** @deprecated 1.21 */ - public const NO_PADDING = 2; - /** * Constructs a create command. * * Supported options: * - * * autoIndexId (boolean): Specify false to disable the automatic creation - * of an index on the _id field. For replica sets, this option cannot be - * false. The default is true. - * - * This option has been deprecated since MongoDB 3.2. As of MongoDB 4.0, - * this option cannot be false when creating a replicated collection - * (i.e. a collection outside of the local database in any mongod mode). - * * * capped (boolean): Specify true to create a capped collection. If set, * the size option must also be specified. The default is false. * @@ -89,11 +69,6 @@ class CreateCollection implements Executable * * This is not supported for servers versions < 5.0. * - * * flags (integer): Options for the MMAPv1 storage engine only. Must be a - * bitwise combination CreateCollection::USE_POWER_OF_2_SIZES and - * CreateCollection::NO_PADDING. The default is - * CreateCollection::USE_POWER_OF_2_SIZES. - * * * indexOptionDefaults (document): Default configuration for indexes when * creating the collection. * @@ -117,9 +92,6 @@ class CreateCollection implements Executable * * This is not supported for servers versions < 5.0. * - * * typeMap (array): Type map for BSON deserialization. This will only be - * used for the returned command result document. - * * * validationAction (string): Validation action. * * * validationLevel (string): Validation level. @@ -140,10 +112,6 @@ class CreateCollection implements Executable */ public function __construct(private string $databaseName, private string $collectionName, private array $options = []) { - if (isset($this->options['autoIndexId']) && ! is_bool($this->options['autoIndexId'])) { - throw InvalidArgumentException::invalidType('"autoIndexId" option', $this->options['autoIndexId'], 'boolean'); - } - if (isset($this->options['capped']) && ! is_bool($this->options['capped'])) { throw InvalidArgumentException::invalidType('"capped" option', $this->options['capped'], 'boolean'); } @@ -168,10 +136,6 @@ public function __construct(private string $databaseName, private string $collec throw InvalidArgumentException::invalidType('"expireAfterSeconds" option', $this->options['expireAfterSeconds'], 'integer'); } - if (isset($this->options['flags']) && ! is_integer($this->options['flags'])) { - throw InvalidArgumentException::invalidType('"flags" option', $this->options['flags'], 'integer'); - } - if (isset($this->options['indexOptionDefaults']) && ! is_document($this->options['indexOptionDefaults'])) { throw InvalidArgumentException::expectedDocumentType('"indexOptionDefaults" option', $this->options['indexOptionDefaults']); } @@ -204,10 +168,6 @@ public function __construct(private string $databaseName, private string $collec throw InvalidArgumentException::expectedDocumentType('"timeseries" option', $this->options['timeseries']); } - if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); - } - if (isset($this->options['validationAction']) && ! is_string($this->options['validationAction'])) { throw InvalidArgumentException::invalidType('"validationAction" option', $this->options['validationAction'], 'string'); } @@ -232,14 +192,6 @@ public function __construct(private string $databaseName, private string $collec unset($this->options['writeConcern']); } - if (isset($this->options['autoIndexId'])) { - trigger_error('The "autoIndexId" option is deprecated and will be removed in version 2.0', E_USER_DEPRECATED); - } - - if (isset($this->options['flags'])) { - trigger_error('The "flags" option is deprecated and will be removed in version 2.0', E_USER_DEPRECATED); - } - if (isset($this->options['pipeline']) && ! is_pipeline($this->options['pipeline'], true /* allowEmpty */)) { throw new InvalidArgumentException('"pipeline" option is not a valid aggregation pipeline'); } @@ -248,19 +200,11 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return array|object Command result document * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): void { - $cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); - - if (isset($this->options['typeMap'])) { - $cursor->setTypeMap($this->options['typeMap']); - } - - return current($cursor->toArray()); + $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); } /** @@ -270,7 +214,7 @@ private function createCommand(): Command { $cmd = ['create' => $this->collectionName]; - foreach (['autoIndexId', 'capped', 'comment', 'expireAfterSeconds', 'flags', 'max', 'maxTimeMS', 'pipeline', 'size', 'validationAction', 'validationLevel', 'viewOn'] as $option) { + foreach (['capped', 'comment', 'expireAfterSeconds', 'max', 'maxTimeMS', 'pipeline', 'size', 'validationAction', 'validationLevel', 'viewOn'] as $option) { if (isset($this->options[$option])) { $cmd[$option] = $this->options[$option]; } diff --git a/src/Operation/CreateEncryptedCollection.php b/src/Operation/CreateEncryptedCollection.php index a5cde6514..01729c0c7 100644 --- a/src/Operation/CreateEncryptedCollection.php +++ b/src/Operation/CreateEncryptedCollection.php @@ -44,11 +44,11 @@ * @internal * @see \MongoDB\Database::createCollection() * @see \MongoDB\Database::createEncryptedCollection() - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#create-collection-helper - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#create-encrypted-collection-helper + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.md#create-collection-helper + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.md#create-encrypted-collection-helper * @see https://www.mongodb.com/docs/manual/core/queryable-encryption/fundamentals/manage-collections/ */ -class CreateEncryptedCollection implements Executable +final class CreateEncryptedCollection { private const WIRE_VERSION_FOR_QUERYABLE_ENCRYPTION_V2 = 21; @@ -97,21 +97,20 @@ public function __construct(private string $databaseName, private string $collec * "encryptedFields" option and reconstruct the internal CreateCollection * operation used for creating the encrypted collection. * - * The $encryptedFields reference parameter may be used to determine which - * data keys have been created. + * Returns the data keys that have been created. * * @see \MongoDB\Database::createEncryptedCollection() * @see https://www.php.net/manual/en/mongodb-driver-clientencryption.createdatakey.php * @throws DriverRuntimeException for errors creating a data key */ - public function createDataKeys(ClientEncryption $clientEncryption, string $kmsProvider, ?array $masterKey, ?array &$encryptedFields = null): void + public function createDataKeys(ClientEncryption $clientEncryption, string $kmsProvider, ?array $masterKey): array { /** @psalm-var array{fields: list|Serializable|PackedArray} */ $encryptedFields = document_to_array($this->options['encryptedFields']); // NOP if there are no fields to examine if (! isset($encryptedFields['fields'])) { - return; + return $encryptedFields; } // Allow PackedArray or Serializable object for the fields array @@ -128,7 +127,7 @@ public function createDataKeys(ClientEncryption $clientEncryption, string $kmsPr // Skip invalid types and defer to the server to raise an error if (! is_array($encryptedFields['fields'])) { - return; + return $encryptedFields; } $createDataKeyArgs = [ @@ -152,15 +151,15 @@ public function createDataKeys(ClientEncryption $clientEncryption, string $kmsPr $this->options['encryptedFields'] = $encryptedFields; $this->createCollection = new CreateCollection($this->databaseName, $this->collectionName, $this->options); + + return $encryptedFields; } /** - * @see Executable::execute() - * @return array|object Command result document from creating the encrypted collection * @throws DriverRuntimeException for other driver errors (e.g. connection errors) * @throws UnsupportedException if the server does not support Queryable Encryption */ - public function execute(Server $server) + public function execute(Server $server): void { if (! server_supports_feature($server, self::WIRE_VERSION_FOR_QUERYABLE_ENCRYPTION_V2)) { throw new UnsupportedException('Driver support of Queryable Encryption is incompatible with server. Upgrade server to use Queryable Encryption.'); @@ -170,10 +169,8 @@ public function execute(Server $server) $createMetadataCollection->execute($server); } - $result = $this->createCollection->execute($server); + $this->createCollection->execute($server); $this->createSafeContentIndex->execute($server); - - return $result; } } diff --git a/src/Operation/CreateIndexes.php b/src/Operation/CreateIndexes.php index e2ac08813..6591270b7 100644 --- a/src/Operation/CreateIndexes.php +++ b/src/Operation/CreateIndexes.php @@ -40,10 +40,8 @@ * @see \MongoDB\Collection::createIndex() * @see \MongoDB\Collection::createIndexes() * @see https://mongodb.com/docs/manual/reference/command/createIndexes/ - * - * @final extending this class will not be supported in v2.0.0 */ -class CreateIndexes implements Executable +final class CreateIndexes { private const WIRE_VERSION_FOR_COMMIT_QUORUM = 9; @@ -118,12 +116,11 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() * @return string[] The names of the created indexes * @throws UnsupportedException if write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): array { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if ($inTransaction && isset($this->options['writeConcern'])) { diff --git a/src/Operation/CreateSearchIndexes.php b/src/Operation/CreateSearchIndexes.php index d5be61116..d21ed9428 100644 --- a/src/Operation/CreateSearchIndexes.php +++ b/src/Operation/CreateSearchIndexes.php @@ -36,10 +36,8 @@ * @see \MongoDB\Collection::createSearchIndex() * @see \MongoDB\Collection::createSearchIndexes() * @see https://mongodb.com/docs/manual/reference/command/createSearchIndexes/ - * - * @final extending this class will not be supported in v2.0.0 */ -class CreateSearchIndexes implements Executable +final class CreateSearchIndexes { private array $indexes = []; @@ -70,7 +68,6 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() * @return string[] The names of the created indexes * @throws UnsupportedException if write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) diff --git a/src/Operation/DatabaseCommand.php b/src/Operation/DatabaseCommand.php index 7ea406baa..567bbff9e 100644 --- a/src/Operation/DatabaseCommand.php +++ b/src/Operation/DatabaseCommand.php @@ -18,7 +18,7 @@ namespace MongoDB\Operation; use MongoDB\Driver\Command; -use MongoDB\Driver\Cursor; +use MongoDB\Driver\CursorInterface; use MongoDB\Driver\ReadPreference; use MongoDB\Driver\Server; use MongoDB\Driver\Session; @@ -31,10 +31,8 @@ * Operation for executing a database command. * * @see \MongoDB\Database::command() - * - * @final extending this class will not be supported in v2.0.0 */ -class DatabaseCommand implements Executable +final class DatabaseCommand { private Command $command; @@ -80,13 +78,7 @@ public function __construct(private string $databaseName, array|object $command, $this->command = $command instanceof Command ? $command : new Command($command); } - /** - * Execute the operation. - * - * @see Executable::execute() - * @return Cursor - */ - public function execute(Server $server) + public function execute(Server $server): CursorInterface { $cursor = $server->executeCommand($this->databaseName, $this->command, $this->createOptions()); diff --git a/src/Operation/Delete.php b/src/Operation/Delete.php index c5826a6d9..84a803c79 100644 --- a/src/Operation/Delete.php +++ b/src/Operation/Delete.php @@ -40,7 +40,7 @@ * @internal * @see https://mongodb.com/docs/manual/reference/command/delete/ */ -class Delete implements Executable, Explainable +final class Delete implements Explainable { private const WIRE_VERSION_FOR_HINT = 9; @@ -118,12 +118,10 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return DeleteResult * @throws UnsupportedException if hint or write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): DeleteResult { /* CRUD spec requires a client-side error when using "hint" with an * unacknowledged write concern on an unsupported server. */ @@ -151,9 +149,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { $cmd = ['delete' => $this->collectionName, 'deletes' => [['q' => $this->filter] + $this->createDeleteOptions()]]; diff --git a/src/Operation/DeleteMany.php b/src/Operation/DeleteMany.php index d6d443b32..5278ee08a 100644 --- a/src/Operation/DeleteMany.php +++ b/src/Operation/DeleteMany.php @@ -28,10 +28,8 @@ * * @see \MongoDB\Collection::deleteOne() * @see https://mongodb.com/docs/manual/reference/command/delete/ - * - * @final extending this class will not be supported in v2.0.0 */ -class DeleteMany implements Executable, Explainable +final class DeleteMany implements Explainable { private Delete $delete; @@ -76,12 +74,10 @@ public function __construct(string $databaseName, string $collectionName, array| /** * Execute the operation. * - * @see Executable::execute() - * @return DeleteResult * @throws UnsupportedException if collation is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): DeleteResult { return $this->delete->execute($server); } @@ -90,9 +86,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { return $this->delete->getCommandDocument(); } diff --git a/src/Operation/DeleteOne.php b/src/Operation/DeleteOne.php index 8bbdb45ce..44dd47ba5 100644 --- a/src/Operation/DeleteOne.php +++ b/src/Operation/DeleteOne.php @@ -28,10 +28,8 @@ * * @see \MongoDB\Collection::deleteOne() * @see https://mongodb.com/docs/manual/reference/command/delete/ - * - * @final extending this class will not be supported in v2.0.0 */ -class DeleteOne implements Executable, Explainable +final class DeleteOne implements Explainable { private Delete $delete; @@ -76,12 +74,10 @@ public function __construct(string $databaseName, string $collectionName, array| /** * Execute the operation. * - * @see Executable::execute() - * @return DeleteResult * @throws UnsupportedException if collation is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): DeleteResult { return $this->delete->execute($server); } @@ -90,9 +86,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { return $this->delete->getCommandDocument(); } diff --git a/src/Operation/Distinct.php b/src/Operation/Distinct.php index bd69cde11..c2aa3ef23 100644 --- a/src/Operation/Distinct.php +++ b/src/Operation/Distinct.php @@ -40,10 +40,8 @@ * * @see \MongoDB\Collection::distinct() * @see https://mongodb.com/docs/manual/reference/command/distinct/ - * - * @final extending this class will not be supported in v2.0.0 */ -class Distinct implements Executable, Explainable +final class Distinct implements Explainable { /** * Constructs a distinct command. @@ -122,13 +120,11 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return array * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if read concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): array { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if ($inTransaction && isset($this->options['readConcern'])) { @@ -154,9 +150,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { $cmd = $this->createCommandDocument(); diff --git a/src/Operation/DropCollection.php b/src/Operation/DropCollection.php index d19859de1..0c520801c 100644 --- a/src/Operation/DropCollection.php +++ b/src/Operation/DropCollection.php @@ -26,19 +26,14 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; -use function current; -use function is_array; - /** * Operation for the drop command. * * @see \MongoDB\Collection::drop() * @see \MongoDB\Database::dropCollection() * @see https://mongodb.com/docs/manual/reference/command/drop/ - * - * @final extending this class will not be supported in v2.0.0 */ -class DropCollection implements Executable +final class DropCollection { private const ERROR_CODE_NAMESPACE_NOT_FOUND = 26; @@ -53,9 +48,6 @@ class DropCollection implements Executable * * * session (MongoDB\Driver\Session): Client session. * - * * typeMap (array): Type map for BSON deserialization. This will be used - * for the returned command result document. - * * * writeConcern (MongoDB\Driver\WriteConcern): Write concern. * * @param string $databaseName Database name @@ -69,10 +61,6 @@ public function __construct(private string $databaseName, private string $collec throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); - } - if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } @@ -85,12 +73,10 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return array|object Command result document * @throws UnsupportedException if write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): void { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if ($inTransaction && isset($this->options['writeConcern'])) { @@ -98,23 +84,16 @@ public function execute(Server $server) } try { - $cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); + $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); } catch (CommandException $e) { /* The server may return an error if the collection does not exist. - * Check for an error code and return the command reply instead of - * throwing. */ + * Ignore the exception to make the drop operation idempotent */ if ($e->getCode() === self::ERROR_CODE_NAMESPACE_NOT_FOUND) { - return $e->getResultDocument(); + return; } throw $e; } - - if (isset($this->options['typeMap'])) { - $cursor->setTypeMap($this->options['typeMap']); - } - - return current($cursor->toArray()); } /** diff --git a/src/Operation/DropDatabase.php b/src/Operation/DropDatabase.php index 290887900..1cc71a4c0 100644 --- a/src/Operation/DropDatabase.php +++ b/src/Operation/DropDatabase.php @@ -24,19 +24,14 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; -use function current; -use function is_array; - /** * Operation for the dropDatabase command. * * @see \MongoDB\Client::dropDatabase() * @see \MongoDB\Database::drop() * @see https://mongodb.com/docs/manual/reference/command/dropDatabase/ - * - * @final extending this class will not be supported in v2.0.0 */ -class DropDatabase implements Executable +final class DropDatabase { /** * Constructs a dropDatabase command. @@ -49,9 +44,6 @@ class DropDatabase implements Executable * * * session (MongoDB\Driver\Session): Client session. * - * * typeMap (array): Type map for BSON deserialization. This will be used - * for the returned command result document. - * * * writeConcern (MongoDB\Driver\WriteConcern): Write concern. * * @param string $databaseName Database name @@ -64,10 +56,6 @@ public function __construct(private string $databaseName, private array $options throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); - } - if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } @@ -80,19 +68,11 @@ public function __construct(private string $databaseName, private array $options /** * Execute the operation. * - * @see Executable::execute() - * @return array|object Command result document * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): void { - $cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); - - if (isset($this->options['typeMap'])) { - $cursor->setTypeMap($this->options['typeMap']); - } - - return current($cursor->toArray()); + $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); } /** diff --git a/src/Operation/DropEncryptedCollection.php b/src/Operation/DropEncryptedCollection.php index 491a8e9eb..646844a3a 100644 --- a/src/Operation/DropEncryptedCollection.php +++ b/src/Operation/DropEncryptedCollection.php @@ -34,10 +34,10 @@ * @internal * @see \MongoDB\Database::dropCollection() * @see \MongoDB\Collection::drop() - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#drop-collection-helper + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.md#drop-collection-helper * @see https://www.mongodb.com/docs/manual/core/queryable-encryption/fundamentals/manage-collections/ */ -class DropEncryptedCollection implements Executable +final class DropEncryptedCollection { private DropCollection $dropCollection; @@ -84,17 +84,13 @@ public function __construct(string $databaseName, string $collectionName, array $this->dropCollection = new DropCollection($databaseName, $collectionName, $options); } - /** - * @see Executable::execute() - * @return array|object Command result document from dropping the encrypted collection - * @throws DriverRuntimeException for other driver errors (e.g. connection errors) - */ - public function execute(Server $server) + /** @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ + public function execute(Server $server): void { foreach ($this->dropMetadataCollections as $dropMetadataCollection) { $dropMetadataCollection->execute($server); } - return $this->dropCollection->execute($server); + $this->dropCollection->execute($server); } } diff --git a/src/Operation/DropIndexes.php b/src/Operation/DropIndexes.php index 8fde88b4d..46a89f22f 100644 --- a/src/Operation/DropIndexes.php +++ b/src/Operation/DropIndexes.php @@ -25,8 +25,6 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; -use function current; -use function is_array; use function is_integer; /** @@ -34,10 +32,8 @@ * * @see \MongoDB\Collection::dropIndexes() * @see https://mongodb.com/docs/manual/reference/command/dropIndexes/ - * - * @final extending this class will not be supported in v2.0.0 */ -class DropIndexes implements Executable +final class DropIndexes { /** * Constructs a dropIndexes command. @@ -53,9 +49,6 @@ class DropIndexes implements Executable * * * session (MongoDB\Driver\Session): Client session. * - * * typeMap (array): Type map for BSON deserialization. This will be used - * for the returned command result document. - * * * writeConcern (MongoDB\Driver\WriteConcern): Write concern. * * @param string $databaseName Database name @@ -78,10 +71,6 @@ public function __construct(private string $databaseName, private string $collec throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); - } - if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } @@ -94,25 +83,17 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return array|object Command result document * @throws UnsupportedException if write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): void { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if ($inTransaction && isset($this->options['writeConcern'])) { throw UnsupportedException::writeConcernNotSupportedInTransaction(); } - $cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); - - if (isset($this->options['typeMap'])) { - $cursor->setTypeMap($this->options['typeMap']); - } - - return current($cursor->toArray()); + $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); } /** diff --git a/src/Operation/DropSearchIndex.php b/src/Operation/DropSearchIndex.php index e65adee6a..c79025fbb 100644 --- a/src/Operation/DropSearchIndex.php +++ b/src/Operation/DropSearchIndex.php @@ -29,10 +29,8 @@ * * @see \MongoDB\Collection::dropSearchIndexes() * @see https://mongodb.com/docs/manual/reference/command/dropSearchIndexes/ - * - * @final extending this class will not be supported in v2.0.0 */ -class DropSearchIndex implements Executable +final class DropSearchIndex { private const ERROR_CODE_NAMESPACE_NOT_FOUND = 26; @@ -55,7 +53,6 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() * @throws UnsupportedException if write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ diff --git a/src/Operation/EstimatedDocumentCount.php b/src/Operation/EstimatedDocumentCount.php index db876c71c..716996541 100644 --- a/src/Operation/EstimatedDocumentCount.php +++ b/src/Operation/EstimatedDocumentCount.php @@ -34,10 +34,8 @@ * * @see \MongoDB\Collection::estimatedDocumentCount() * @see https://mongodb.com/docs/manual/reference/command/count/ - * - * @final extending this class will not be supported in v2.0.0 */ -class EstimatedDocumentCount implements Executable, Explainable +final class EstimatedDocumentCount implements Explainable { private array $options; @@ -89,13 +87,11 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return integer * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if collation or read concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): int { return $this->createCount()->execute($server); } @@ -104,9 +100,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { return $this->createCount()->getCommandDocument(); } diff --git a/src/Operation/Executable.php b/src/Operation/Executable.php deleted file mode 100644 index 5bd8c68b3..000000000 --- a/src/Operation/Executable.php +++ /dev/null @@ -1,38 +0,0 @@ -executeCommand($this->databaseName, $this->createCommand(), $this->createOptions()); diff --git a/src/Operation/Explainable.php b/src/Operation/Explainable.php index f380e1c32..0f8717f4b 100644 --- a/src/Operation/Explainable.php +++ b/src/Operation/Explainable.php @@ -23,12 +23,10 @@ * * @internal */ -interface Explainable extends Executable +interface Explainable { /** * Returns the command document for this operation. - * - * @return array */ - public function getCommandDocument(); + public function getCommandDocument(): array; } diff --git a/src/Operation/Find.php b/src/Operation/Find.php index a4a010010..8d84c7bf0 100644 --- a/src/Operation/Find.php +++ b/src/Operation/Find.php @@ -17,7 +17,6 @@ namespace MongoDB\Operation; -use Iterator; use MongoDB\Codec\DocumentCodec; use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; @@ -34,9 +33,7 @@ use function is_array; use function is_bool; use function is_integer; -use function is_object; use function is_string; -use function MongoDB\document_to_array; use function MongoDB\is_document; /** @@ -45,10 +42,8 @@ * @see \MongoDB\Collection::find() * @see https://mongodb.com/docs/manual/tutorial/query-documents/ * @see https://mongodb.com/docs/manual/reference/operator/query-modifier/ - * - * @final extending this class will not be supported in v2.0.0 */ -class Find implements Executable, Explainable +final class Find implements Explainable { public const NON_TAILABLE = 1; public const TAILABLE = 2; @@ -92,28 +87,15 @@ class Find implements Executable, Explainable * * maxAwaitTimeMS (integer): The maxium amount of time for the server to wait * on new documents to satisfy a query, if cursorType is TAILABLE_AWAIT. * - * * maxScan (integer): Maximum number of documents or index keys to scan - * when executing the query. - * - * This option has been deprecated since version 1.4. - * * * maxTimeMS (integer): The maximum amount of time to allow the query to - * run. If "$maxTimeMS" also exists in the modifiers document, this - * option will take precedence. + * run. * * * min (document): The inclusive upper bound for a specific index. * - * * modifiers (document): Meta operators that modify the output or - * behavior of a query. Use of these operators is deprecated in favor of - * named options. - * * * noCursorTimeout (boolean): The server normally times out idle cursors * after an inactivity period (10 minutes) to prevent excess memory use. * Set this option to prevent that. * - * * oplogReplay (boolean): Internal replication use only. The driver - * should not set this. This option is deprecated as of MongoDB 4.4. - * * * projection (document): Limits the fields to return for the matching * document. * @@ -132,14 +114,7 @@ class Find implements Executable, Explainable * * * skip (integer): The number of documents to skip before returning. * - * * snapshot (boolean): Prevents the cursor from returning a document more - * than once because of an intervening write operation. - * - * This options has been deprecated since version 1.4. - * - * * sort (document): The order in which to return matching documents. If - * "$orderby" also exists in the modifiers document, this option will - * take precedence. + * * sort (document): The order in which to return matching documents. * * * let (document): Map of parameter names and values. Values must be * constant or closed expressions that do not reference document fields. @@ -211,10 +186,6 @@ public function __construct(private string $databaseName, private string $collec throw InvalidArgumentException::invalidType('"maxAwaitTimeMS" option', $this->options['maxAwaitTimeMS'], 'integer'); } - if (isset($this->options['maxScan']) && ! is_integer($this->options['maxScan'])) { - throw InvalidArgumentException::invalidType('"maxScan" option', $this->options['maxScan'], 'integer'); - } - if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); } @@ -223,18 +194,10 @@ public function __construct(private string $databaseName, private string $collec throw InvalidArgumentException::expectedDocumentType('"min" option', $this->options['min']); } - if (isset($this->options['modifiers']) && ! is_document($this->options['modifiers'])) { - throw InvalidArgumentException::expectedDocumentType('"modifiers" option', $this->options['modifiers']); - } - if (isset($this->options['noCursorTimeout']) && ! is_bool($this->options['noCursorTimeout'])) { throw InvalidArgumentException::invalidType('"noCursorTimeout" option', $this->options['noCursorTimeout'], 'boolean'); } - if (isset($this->options['oplogReplay']) && ! is_bool($this->options['oplogReplay'])) { - throw InvalidArgumentException::invalidType('"oplogReplay" option', $this->options['oplogReplay'], 'boolean'); - } - if (isset($this->options['projection']) && ! is_document($this->options['projection'])) { throw InvalidArgumentException::expectedDocumentType('"projection" option', $this->options['projection']); } @@ -263,10 +226,6 @@ public function __construct(private string $databaseName, private string $collec throw InvalidArgumentException::invalidType('"skip" option', $this->options['skip'], 'integer'); } - if (isset($this->options['snapshot']) && ! is_bool($this->options['snapshot'])) { - throw InvalidArgumentException::invalidType('"snapshot" option', $this->options['snapshot'], 'boolean'); - } - if (isset($this->options['sort']) && ! is_document($this->options['sort'])) { throw InvalidArgumentException::expectedDocumentType('"sort" option', $this->options['sort']); } @@ -291,12 +250,10 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return CursorInterface&Iterator * @throws UnsupportedException if read concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): CursorInterface { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if ($inTransaction && isset($this->options['readConcern'])) { @@ -320,9 +277,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { $cmd = ['find' => $this->collectionName, 'filter' => (object) $this->filter]; @@ -335,28 +291,6 @@ public function getCommandDocument() // maxAwaitTimeMS is a Query level option so should not be considered here unset($options['maxAwaitTimeMS']); - $modifierFallback = [ - ['allowPartialResults', 'partial'], - ['comment', '$comment'], - ['hint', '$hint'], - ['maxScan', '$maxScan'], - ['max', '$max'], - ['maxTimeMS', '$maxTimeMS'], - ['min', '$min'], - ['returnKey', '$returnKey'], - ['showRecordId', '$showDiskLoc'], - ['sort', '$orderby'], - ['snapshot', '$snapshot'], - ]; - - foreach ($modifierFallback as $modifier) { - if (! isset($options[$modifier[0]]) && isset($options['modifiers'][$modifier[1]])) { - $options[$modifier[0]] = $options['modifiers'][$modifier[1]]; - } - } - - unset($options['modifiers']); - return $cmd + $options; } @@ -401,7 +335,7 @@ private function createQueryOptions(): array } } - foreach (['allowDiskUse', 'allowPartialResults', 'batchSize', 'comment', 'hint', 'limit', 'maxAwaitTimeMS', 'maxScan', 'maxTimeMS', 'noCursorTimeout', 'oplogReplay', 'projection', 'readConcern', 'returnKey', 'showRecordId', 'skip', 'snapshot', 'sort'] as $option) { + foreach (['allowDiskUse', 'allowPartialResults', 'batchSize', 'comment', 'hint', 'limit', 'maxAwaitTimeMS', 'maxTimeMS', 'noCursorTimeout', 'projection', 'readConcern', 'returnKey', 'showRecordId', 'skip', 'sort'] as $option) { if (isset($this->options[$option])) { $options[$option] = $this->options[$option]; } @@ -413,12 +347,6 @@ private function createQueryOptions(): array } } - if (! empty($this->options['modifiers'])) { - /** @psalm-var array|object */ - $modifiers = $this->options['modifiers']; - $options['modifiers'] = is_object($modifiers) ? document_to_array($modifiers) : $modifiers; - } - // Ensure no cursor is left behind when limit == batchSize by increasing batchSize if (isset($options['limit'], $options['batchSize']) && $options['limit'] === $options['batchSize']) { assert(is_integer($options['batchSize'])); diff --git a/src/Operation/FindAndModify.php b/src/Operation/FindAndModify.php index 07944db3d..3d77dd643 100644 --- a/src/Operation/FindAndModify.php +++ b/src/Operation/FindAndModify.php @@ -51,7 +51,7 @@ * @internal * @see https://mongodb.com/docs/manual/reference/command/findAndModify/ */ -class FindAndModify implements Executable, Explainable +final class FindAndModify implements Explainable { private const WIRE_VERSION_FOR_HINT = 9; @@ -221,13 +221,11 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return array|object|null * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if hint or write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): array|object|null { /* Server versions >= 4.2.0 raise errors for unsupported update options. * For previous versions, the CRUD spec requires a client-side error. */ @@ -274,9 +272,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { return $this->createCommandDocument(); } diff --git a/src/Operation/FindOne.php b/src/Operation/FindOne.php index 161d01dd0..f685de5ae 100644 --- a/src/Operation/FindOne.php +++ b/src/Operation/FindOne.php @@ -30,10 +30,8 @@ * @see \MongoDB\Collection::findOne() * @see https://mongodb.com/docs/manual/tutorial/query-documents/ * @see https://mongodb.com/docs/manual/reference/operator/query-modifier/ - * - * @final extending this class will not be supported in v2.0.0 */ -class FindOne implements Executable, Explainable +final class FindOne implements Explainable { private Find $find; @@ -57,20 +55,11 @@ class FindOne implements Executable, Explainable * * * max (document): The exclusive upper bound for a specific index. * - * * maxScan (integer): Maximum number of documents or index keys to scan - * when executing the query. - * - * This option has been deprecated since version 1.4. - * * * maxTimeMS (integer): The maximum amount of time to allow the query to - * run. If "$maxTimeMS" also exists in the modifiers document, this - * option will take precedence. + * run. * * * min (document): The inclusive upper bound for a specific index. * - * * modifiers (document): Meta-operators modifying the output or behavior - * of a query. - * * * projection (document): Limits the fields to return for the matching * document. * @@ -89,9 +78,7 @@ class FindOne implements Executable, Explainable * * * skip (integer): The number of documents to skip before returning. * - * * sort (document): The order in which to return matching documents. If - * "$orderby" also exists in the modifiers document, this option will - * take precedence. + * * sort (document): The order in which to return matching documents. * * * let (document): Map of parameter names and values. Values must be * constant or closed expressions that do not reference document fields. @@ -119,12 +106,10 @@ public function __construct(string $databaseName, string $collectionName, array| /** * Execute the operation. * - * @see Executable::execute() - * @return array|object|null * @throws UnsupportedException if collation or read concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): array|object|null { $cursor = $this->find->execute($server); $document = current($cursor->toArray()); @@ -136,9 +121,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { return $this->find->getCommandDocument(); } diff --git a/src/Operation/FindOneAndDelete.php b/src/Operation/FindOneAndDelete.php index a560ee4c6..7c0f22ad0 100644 --- a/src/Operation/FindOneAndDelete.php +++ b/src/Operation/FindOneAndDelete.php @@ -29,10 +29,8 @@ * * @see \MongoDB\Collection::findOneAndDelete() * @see https://mongodb.com/docs/manual/reference/command/findAndModify/ - * - * @final extending this class will not be supported in v2.0.0 */ -class FindOneAndDelete implements Executable, Explainable +final class FindOneAndDelete implements Explainable { private FindAndModify $findAndModify; @@ -109,12 +107,10 @@ public function __construct(string $databaseName, string $collectionName, array| /** * Execute the operation. * - * @see Executable::execute() - * @return array|object|null * @throws UnsupportedException if collation or write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): array|object|null { return $this->findAndModify->execute($server); } @@ -123,9 +119,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { return $this->findAndModify->getCommandDocument(); } diff --git a/src/Operation/FindOneAndReplace.php b/src/Operation/FindOneAndReplace.php index dfd761305..5ea0c5a34 100644 --- a/src/Operation/FindOneAndReplace.php +++ b/src/Operation/FindOneAndReplace.php @@ -34,10 +34,8 @@ * * @see \MongoDB\Collection::findOneAndReplace() * @see https://mongodb.com/docs/manual/reference/command/findAndModify/ - * - * @final extending this class will not be supported in v2.0.0 */ -class FindOneAndReplace implements Executable, Explainable +final class FindOneAndReplace implements Explainable { public const RETURN_DOCUMENT_BEFORE = 1; public const RETURN_DOCUMENT_AFTER = 2; @@ -152,12 +150,10 @@ public function __construct(string $databaseName, string $collectionName, array| /** * Execute the operation. * - * @see Executable::execute() - * @return array|object|null * @throws UnsupportedException if collation or write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): array|object|null { return $this->findAndModify->execute($server); } @@ -166,15 +162,13 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { return $this->findAndModify->getCommandDocument(); } - /** @return array|object */ - private function validateReplacement(array|object $replacement, ?DocumentCodec $codec) + private function validateReplacement(array|object $replacement, ?DocumentCodec $codec): array|object { if (isset($codec)) { $replacement = $codec->encode($replacement); diff --git a/src/Operation/FindOneAndUpdate.php b/src/Operation/FindOneAndUpdate.php index 3566c9822..00e97972b 100644 --- a/src/Operation/FindOneAndUpdate.php +++ b/src/Operation/FindOneAndUpdate.php @@ -33,10 +33,8 @@ * * @see \MongoDB\Collection::findOneAndUpdate() * @see https://mongodb.com/docs/manual/reference/command/findAndModify/ - * - * @final extending this class will not be supported in v2.0.0 */ -class FindOneAndUpdate implements Executable, Explainable +final class FindOneAndUpdate implements Explainable { public const RETURN_DOCUMENT_BEFORE = 1; public const RETURN_DOCUMENT_AFTER = 2; @@ -152,12 +150,10 @@ public function __construct(string $databaseName, string $collectionName, array| /** * Execute the operation. * - * @see Executable::execute() - * @return array|object|null * @throws UnsupportedException if collation or write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): array|object|null { return $this->findAndModify->execute($server); } @@ -166,9 +162,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { return $this->findAndModify->getCommandDocument(); } diff --git a/src/Operation/InsertMany.php b/src/Operation/InsertMany.php index 5e227cbc2..70e149076 100644 --- a/src/Operation/InsertMany.php +++ b/src/Operation/InsertMany.php @@ -37,10 +37,8 @@ * * @see \MongoDB\Collection::insertMany() * @see https://mongodb.com/docs/manual/reference/command/insert/ - * - * @final extending this class will not be supported in v2.0.0 */ -class InsertMany implements Executable +final class InsertMany { /** @var list */ private array $documents; @@ -116,12 +114,10 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return InsertManyResult * @throws UnsupportedException if write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): InsertManyResult { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if ($inTransaction && isset($this->options['writeConcern'])) { diff --git a/src/Operation/InsertOne.php b/src/Operation/InsertOne.php index 6d8f58cd8..dff9f79f6 100644 --- a/src/Operation/InsertOne.php +++ b/src/Operation/InsertOne.php @@ -35,10 +35,8 @@ * * @see \MongoDB\Collection::insertOne() * @see https://mongodb.com/docs/manual/reference/command/insert/ - * - * @final extending this class will not be supported in v2.0.0 */ -class InsertOne implements Executable +final class InsertOne { private array|object $document; @@ -99,12 +97,10 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return InsertOneResult * @throws UnsupportedException if write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): InsertOneResult { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if (isset($this->options['writeConcern']) && $inTransaction) { @@ -158,8 +154,7 @@ private function createExecuteOptions(): array return $options; } - /** @return array|object */ - private function validateDocument(array|object $document, ?DocumentCodec $codec) + private function validateDocument(array|object $document, ?DocumentCodec $codec): array|object { if ($codec) { $document = $codec->encode($document); diff --git a/src/Operation/ListCollectionNames.php b/src/Operation/ListCollectionNames.php index fe0ea12c6..3b32cd589 100644 --- a/src/Operation/ListCollectionNames.php +++ b/src/Operation/ListCollectionNames.php @@ -22,6 +22,7 @@ use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; +use MongoDB\Model\CachingIterator; use MongoDB\Model\CallbackIterator; /** @@ -29,10 +30,8 @@ * * @see \MongoDB\Database::listCollectionNames() * @see https://mongodb.com/docs/manual/reference/command/listCollections/ - * - * @final extending this class will not be supported in v2.0.0 */ -class ListCollectionNames implements Executable +final class ListCollectionNames { private ListCollectionsCommand $listCollections; @@ -69,15 +68,16 @@ public function __construct(string $databaseName, array $options = []) /** * Execute the operation. * - * @see Executable::execute() - * @return Iterator + * @return Iterator * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ public function execute(Server $server): Iterator { - return new CallbackIterator( - $this->listCollections->execute($server), - fn (array $collectionInfo): string => (string) $collectionInfo['name'], + return new CachingIterator( + new CallbackIterator( + $this->listCollections->execute($server), + fn (array $collectionInfo): string => (string) $collectionInfo['name'], + ), ); } } diff --git a/src/Operation/ListCollections.php b/src/Operation/ListCollections.php index 6a877b17f..a86b0a1e5 100644 --- a/src/Operation/ListCollections.php +++ b/src/Operation/ListCollections.php @@ -22,18 +22,17 @@ use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; +use MongoDB\Model\CachingIterator; +use MongoDB\Model\CallbackIterator; use MongoDB\Model\CollectionInfo; -use MongoDB\Model\CollectionInfoCommandIterator; /** * Operation for the listCollections command. * * @see \MongoDB\Database::listCollections() * @see https://mongodb.com/docs/manual/reference/command/listCollections/ - * - * @final extending this class will not be supported in v2.0.0 */ -class ListCollections implements Executable +final class ListCollections { private ListCollectionsCommand $listCollections; @@ -62,7 +61,7 @@ class ListCollections implements Executable * @param array $options Command options * @throws InvalidArgumentException for parameter/option parsing errors */ - public function __construct(private string $databaseName, array $options = []) + public function __construct(string $databaseName, array $options = []) { $this->listCollections = new ListCollectionsCommand($databaseName, ['nameOnly' => false] + $options); } @@ -70,12 +69,19 @@ public function __construct(private string $databaseName, array $options = []) /** * Execute the operation. * - * @see Executable::execute() * @return Iterator * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): Iterator { - return new CollectionInfoCommandIterator($this->listCollections->execute($server), $this->databaseName); + /** @var Iterator $collections */ + $collections = $this->listCollections->execute($server); + + return new CachingIterator( + new CallbackIterator( + $collections, + fn (array $collectionInfo, int $key): CollectionInfo => new CollectionInfo($collectionInfo), + ), + ); } } diff --git a/src/Operation/ListDatabaseNames.php b/src/Operation/ListDatabaseNames.php index f7301c169..74263fb53 100644 --- a/src/Operation/ListDatabaseNames.php +++ b/src/Operation/ListDatabaseNames.php @@ -32,10 +32,8 @@ * * @see \MongoDB\Client::listDatabaseNames() * @see https://mongodb.com/docs/manual/reference/command/listDatabases/#mongodb-dbcommand-dbcmd.listDatabases - * - * @final extending this class will not be supported in v2.0.0 */ -class ListDatabaseNames implements Executable +final class ListDatabaseNames { private ListDatabasesCommand $listDatabases; @@ -71,7 +69,7 @@ public function __construct(array $options = []) /** * Execute the operation. * - * @see Executable::execute() + * @return Iterator * @throws UnexpectedValueException if the command response was malformed * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ diff --git a/src/Operation/ListDatabases.php b/src/Operation/ListDatabases.php index 8aa31ac1f..ab32ef7e6 100644 --- a/src/Operation/ListDatabases.php +++ b/src/Operation/ListDatabases.php @@ -17,24 +17,23 @@ namespace MongoDB\Operation; +use ArrayIterator; use Iterator; use MongoDB\Command\ListDatabases as ListDatabasesCommand; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Server; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnexpectedValueException; +use MongoDB\Model\CallbackIterator; use MongoDB\Model\DatabaseInfo; -use MongoDB\Model\DatabaseInfoLegacyIterator; /** * Operation for the ListDatabases command. * * @see \MongoDB\Client::listDatabases() - * @see https://mongodb.com/docs/manual/reference/command/listDatabases/#mongodb-dbcommand-dbcmd.listDatabases - * - * @final extending this class will not be supported in v2.0.0 + * @see https://mongodb.com/docs/manual/reference/command/listDatabases/#mongodb-dbcommand-dbcmd.listDatabases` */ -class ListDatabases implements Executable +final class ListDatabases { private ListDatabasesCommand $listDatabases; @@ -70,13 +69,18 @@ public function __construct(array $options = []) /** * Execute the operation. * - * @see Executable::execute() * @return Iterator * @throws UnexpectedValueException if the command response was malformed * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): Iterator { - return new DatabaseInfoLegacyIterator($this->listDatabases->execute($server)); + /** @var list $databases */ + $databases = $this->listDatabases->execute($server); + + return new CallbackIterator( + new ArrayIterator($databases), + fn (array $databaseInfo): DatabaseInfo => new DatabaseInfo($databaseInfo), + ); } } diff --git a/src/Operation/ListIndexes.php b/src/Operation/ListIndexes.php index 1116a25a6..8fab516fc 100644 --- a/src/Operation/ListIndexes.php +++ b/src/Operation/ListIndexes.php @@ -20,14 +20,15 @@ use EmptyIterator; use Iterator; use MongoDB\Driver\Command; +use MongoDB\Driver\CursorInterface; use MongoDB\Driver\Exception\CommandException; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Server; use MongoDB\Driver\Session; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Model\CachingIterator; +use MongoDB\Model\CallbackIterator; use MongoDB\Model\IndexInfo; -use MongoDB\Model\IndexInfoIteratorIterator; use function is_integer; @@ -36,10 +37,8 @@ * * @see \MongoDB\Collection::listIndexes() * @see https://mongodb.com/docs/manual/reference/command/listIndexes/ - * - * @final extending this class will not be supported in v2.0.0 */ -class ListIndexes implements Executable +final class ListIndexes { private const ERROR_CODE_DATABASE_NOT_FOUND = 60; private const ERROR_CODE_NAMESPACE_NOT_FOUND = 26; @@ -77,41 +76,10 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() * @return Iterator * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) - { - return $this->executeCommand($server); - } - - /** - * Create options for executing the command. - * - * Note: read preference is intentionally omitted, as the spec requires that - * the command be executed on the primary. - * - * @see https://php.net/manual/en/mongodb-driver-server.executecommand.php - */ - private function createOptions(): array - { - $options = []; - - if (isset($this->options['session'])) { - $options['session'] = $this->options['session']; - } - - return $options; - } - - /** - * Returns information for all indexes for this collection using the - * listIndexes command. - * - * @throws DriverRuntimeException for other driver errors (e.g. connection errors) - */ - private function executeCommand(Server $server): IndexInfoIteratorIterator + public function execute(Server $server): Iterator { $cmd = ['listIndexes' => $this->collectionName]; @@ -122,24 +90,45 @@ private function executeCommand(Server $server): IndexInfoIteratorIterator } try { + /** @var CursorInterface $cursor */ $cursor = $server->executeReadCommand($this->databaseName, new Command($cmd), $this->createOptions()); + $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); } catch (CommandException $e) { /* The server may return an error if the collection does not exist. * Check for possible error codes (see: SERVER-20463) and return an * empty iterator instead of throwing. */ if ($e->getCode() === self::ERROR_CODE_NAMESPACE_NOT_FOUND || $e->getCode() === self::ERROR_CODE_DATABASE_NOT_FOUND) { - return new IndexInfoIteratorIterator(new EmptyIterator()); + return new EmptyIterator(); } throw $e; } - $cursor->setTypeMap(['root' => 'array', 'document' => 'array']); + return new CachingIterator( + new CallbackIterator( + $cursor, + fn (array $indexInfo): IndexInfo => new IndexInfo($indexInfo), + ), + ); + } + + /** + * Create options for executing the command. + * + * Note: read preference is intentionally omitted, as the spec requires that + * the command be executed on the primary. + * + * @see https://php.net/manual/en/mongodb-driver-server.executecommand.php + */ + private function createOptions(): array + { + $options = []; - /** @var CachingIterator $iterator */ - $iterator = new CachingIterator($cursor); + if (isset($this->options['session'])) { + $options['session'] = $this->options['session']; + } - return new IndexInfoIteratorIterator($iterator, $this->databaseName . '.' . $this->collectionName); + return $options; } } diff --git a/src/Operation/ListSearchIndexes.php b/src/Operation/ListSearchIndexes.php index 5d59725ed..ae0eb6d6f 100644 --- a/src/Operation/ListSearchIndexes.php +++ b/src/Operation/ListSearchIndexes.php @@ -34,10 +34,8 @@ * * @see \MongoDB\Collection::listSearchIndexes() * @see https://mongodb.com/docs/manual/reference/command/listSearchIndexes/ - * - * @final extending this class will not be supported in v2.0.0 */ -class ListSearchIndexes implements Executable +final class ListSearchIndexes { private array $listSearchIndexesOptions; private array $aggregateOptions; @@ -70,7 +68,6 @@ public function __construct(private string $databaseName, private string $collec * Execute the operation. * * @return Iterator&Countable - * @see Executable::execute() * @throws UnexpectedValueException if the command response was malformed * @throws UnsupportedException if collation or read concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) diff --git a/src/Operation/MapReduce.php b/src/Operation/MapReduce.php deleted file mode 100644 index a62dd2f86..000000000 --- a/src/Operation/MapReduce.php +++ /dev/null @@ -1,391 +0,0 @@ -options['bypassDocumentValidation']) && ! is_bool($this->options['bypassDocumentValidation'])) { - throw InvalidArgumentException::invalidType('"bypassDocumentValidation" option', $this->options['bypassDocumentValidation'], 'boolean'); - } - - if (isset($this->options['collation']) && ! is_document($this->options['collation'])) { - throw InvalidArgumentException::expectedDocumentType('"collation" option', $this->options['collation']); - } - - if (isset($this->options['finalize']) && ! $this->options['finalize'] instanceof JavascriptInterface) { - throw InvalidArgumentException::invalidType('"finalize" option', $this->options['finalize'], JavascriptInterface::class); - } - - if (isset($this->options['jsMode']) && ! is_bool($this->options['jsMode'])) { - throw InvalidArgumentException::invalidType('"jsMode" option', $this->options['jsMode'], 'boolean'); - } - - if (isset($this->options['limit']) && ! is_integer($this->options['limit'])) { - throw InvalidArgumentException::invalidType('"limit" option', $this->options['limit'], 'integer'); - } - - if (isset($this->options['maxTimeMS']) && ! is_integer($this->options['maxTimeMS'])) { - throw InvalidArgumentException::invalidType('"maxTimeMS" option', $this->options['maxTimeMS'], 'integer'); - } - - if (isset($this->options['query']) && ! is_document($this->options['query'])) { - throw InvalidArgumentException::expectedDocumentType('"query" option', $this->options['query']); - } - - if (isset($this->options['readConcern']) && ! $this->options['readConcern'] instanceof ReadConcern) { - throw InvalidArgumentException::invalidType('"readConcern" option', $this->options['readConcern'], ReadConcern::class); - } - - if (isset($this->options['readPreference']) && ! $this->options['readPreference'] instanceof ReadPreference) { - throw InvalidArgumentException::invalidType('"readPreference" option', $this->options['readPreference'], ReadPreference::class); - } - - if (isset($this->options['scope']) && ! is_document($this->options['scope'])) { - throw InvalidArgumentException::expectedDocumentType('"scope" option', $this->options['scope']); - } - - if (isset($this->options['session']) && ! $this->options['session'] instanceof Session) { - throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); - } - - if (isset($this->options['sort']) && ! is_document($this->options['sort'])) { - throw InvalidArgumentException::expectedDocumentType('"sort" option', $this->options['sort']); - } - - if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); - } - - if (isset($this->options['verbose']) && ! is_bool($this->options['verbose'])) { - throw InvalidArgumentException::invalidType('"verbose" option', $this->options['verbose'], 'boolean'); - } - - if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { - throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); - } - - if (isset($this->options['bypassDocumentValidation']) && ! $this->options['bypassDocumentValidation']) { - unset($this->options['bypassDocumentValidation']); - } - - if (isset($this->options['readConcern']) && $this->options['readConcern']->isDefault()) { - unset($this->options['readConcern']); - } - - if (isset($this->options['writeConcern']) && $this->options['writeConcern']->isDefault()) { - unset($this->options['writeConcern']); - } - - // Handle deprecation of CodeWScope - if ($map->getScope() !== null) { - @trigger_error('Use of Javascript with scope in "$map" argument for MapReduce is deprecated. Put all scope variables in the "scope" option of the MapReduce operation.', E_USER_DEPRECATED); - } - - if ($reduce->getScope() !== null) { - @trigger_error('Use of Javascript with scope in "$reduce" argument for MapReduce is deprecated. Put all scope variables in the "scope" option of the MapReduce operation.', E_USER_DEPRECATED); - } - - if (isset($this->options['finalize']) && $this->options['finalize']->getScope() !== null) { - @trigger_error('Use of Javascript with scope in "finalize" option for MapReduce is deprecated. Put all scope variables in the "scope" option of the MapReduce operation.', E_USER_DEPRECATED); - } - - $this->checkOutDeprecations($out); - - $this->out = $out; - } - - /** - * Execute the operation. - * - * @see Executable::execute() - * @return MapReduceResult - * @throws UnexpectedValueException if the command response was malformed - * @throws UnsupportedException if read concern or write concern is used and unsupported - * @throws DriverRuntimeException for other driver errors (e.g. connection errors) - */ - public function execute(Server $server) - { - $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); - if ($inTransaction) { - if (isset($this->options['readConcern'])) { - throw UnsupportedException::readConcernNotSupportedInTransaction(); - } - - if (isset($this->options['writeConcern'])) { - throw UnsupportedException::writeConcernNotSupportedInTransaction(); - } - } - - $hasOutputCollection = ! is_mapreduce_output_inline($this->out); - - $command = $this->createCommand(); - $options = $this->createOptions($hasOutputCollection); - - /* If the mapReduce operation results in a write, use - * executeReadWriteCommand to ensure we're handling the writeConcern - * option. - * In other cases, we use executeCommand as this will prevent the - * mapReduce operation from being retried when retryReads is enabled. - * See https://github.com/mongodb/specifications/blob/master/source/retryable-reads/retryable-reads.rst#unsupported-read-operations. */ - $cursor = $hasOutputCollection - ? $server->executeReadWriteCommand($this->databaseName, $command, $options) - : $server->executeCommand($this->databaseName, $command, $options); - - if (isset($this->options['typeMap']) && ! $hasOutputCollection) { - $cursor->setTypeMap(create_field_path_type_map($this->options['typeMap'], 'results.$')); - } - - $result = current($cursor->toArray()); - assert($result instanceof stdClass); - - $getIterator = $this->createGetIteratorCallable($result, $server); - - return new MapReduceResult($getIterator, $result); - } - - private function checkOutDeprecations(string|array|object $out): void - { - if (is_string($out)) { - return; - } - - $out = document_to_array($out); - - if (isset($out['nonAtomic']) && ! $out['nonAtomic']) { - @trigger_error('Specifying false for "out.nonAtomic" is deprecated.', E_USER_DEPRECATED); - } - - if (isset($out['sharded']) && ! $out['sharded']) { - @trigger_error('Specifying false for "out.sharded" is deprecated.', E_USER_DEPRECATED); - } - } - - /** - * Create the mapReduce command. - */ - private function createCommand(): Command - { - $cmd = [ - 'mapReduce' => $this->collectionName, - 'map' => $this->map, - 'reduce' => $this->reduce, - 'out' => $this->out, - ]; - - foreach (['bypassDocumentValidation', 'comment', 'finalize', 'jsMode', 'limit', 'maxTimeMS', 'verbose'] as $option) { - if (isset($this->options[$option])) { - $cmd[$option] = $this->options[$option]; - } - } - - foreach (['collation', 'query', 'scope', 'sort'] as $option) { - if (isset($this->options[$option])) { - $cmd[$option] = (object) $this->options[$option]; - } - } - - return new Command($cmd); - } - - /** - * Creates a callable for MapReduceResult::getIterator(). - * - * @psalm-return MapReduceCallable - * @throws UnexpectedValueException if the command response was malformed - */ - private function createGetIteratorCallable(stdClass $result, Server $server): callable - { - // Inline results can be wrapped with an ArrayIterator - if (isset($result->results) && is_array($result->results)) { - $results = $result->results; - - return fn () => new ArrayIterator($results); - } - - if (isset($result->result) && (is_string($result->result) || is_object($result->result))) { - $options = isset($this->options['typeMap']) ? ['typeMap' => $this->options['typeMap']] : []; - - $find = is_string($result->result) - ? new Find($this->databaseName, $result->result, [], $options) - : new Find($result->result->db, $result->result->collection, [], $options); - - return fn () => $find->execute($server); - } - - throw new UnexpectedValueException('mapReduce command did not return inline results or an output collection'); - } - - /** - * Create options for executing the command. - * - * @see https://php.net/manual/en/mongodb-driver-server.executereadcommand.php - * @see https://php.net/manual/en/mongodb-driver-server.executereadwritecommand.php - */ - private function createOptions(bool $hasOutputCollection): array - { - $options = []; - - if (isset($this->options['readConcern'])) { - $options['readConcern'] = $this->options['readConcern']; - } - - if (! $hasOutputCollection && isset($this->options['readPreference'])) { - $options['readPreference'] = $this->options['readPreference']; - } - - if (isset($this->options['session'])) { - $options['session'] = $this->options['session']; - } - - if ($hasOutputCollection && isset($this->options['writeConcern'])) { - $options['writeConcern'] = $this->options['writeConcern']; - } - - return $options; - } -} diff --git a/src/Operation/ModifyCollection.php b/src/Operation/ModifyCollection.php index f8d0c721d..7b40f2f8c 100644 --- a/src/Operation/ModifyCollection.php +++ b/src/Operation/ModifyCollection.php @@ -32,10 +32,8 @@ * * @see \MongoDB\Database::modifyCollection() * @see https://mongodb.com/docs/manual/reference/command/collMod/ - * - * @final extending this class will not be supported in v2.0.0 */ -class ModifyCollection implements Executable +final class ModifyCollection { /** * Constructs a collMod command. @@ -85,11 +83,10 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() * @return array|object Command result document * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): array|object { $cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); diff --git a/src/Operation/RenameCollection.php b/src/Operation/RenameCollection.php index 245231a13..b3848c67e 100644 --- a/src/Operation/RenameCollection.php +++ b/src/Operation/RenameCollection.php @@ -25,8 +25,6 @@ use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; -use function current; -use function is_array; use function is_bool; /** @@ -35,10 +33,8 @@ * @see \MongoDB\Collection::rename() * @see \MongoDB\Database::renameCollection() * @see https://mongodb.com/docs/manual/reference/command/renameCollection/ - * - * @final extending this class will not be supported in v2.0.0 */ -class RenameCollection implements Executable +final class RenameCollection { private string $fromNamespace; @@ -55,9 +51,6 @@ class RenameCollection implements Executable * * * session (MongoDB\Driver\Session): Client session. * - * * typeMap (array): Type map for BSON deserialization. This will be used - * for the returned command result document. - * * * writeConcern (MongoDB\Driver\WriteConcern): Write concern. * * * dropTarget (boolean): If true, MongoDB will drop the target before @@ -76,10 +69,6 @@ public function __construct(string $fromDatabaseName, string $fromCollectionName throw InvalidArgumentException::invalidType('"session" option', $this->options['session'], Session::class); } - if (isset($this->options['typeMap']) && ! is_array($this->options['typeMap'])) { - throw InvalidArgumentException::invalidType('"typeMap" option', $this->options['typeMap'], 'array'); - } - if (isset($this->options['writeConcern']) && ! $this->options['writeConcern'] instanceof WriteConcern) { throw InvalidArgumentException::invalidType('"writeConcern" option', $this->options['writeConcern'], WriteConcern::class); } @@ -99,25 +88,17 @@ public function __construct(string $fromDatabaseName, string $fromCollectionName /** * Execute the operation. * - * @see Executable::execute() - * @return array|object Command result document * @throws UnsupportedException if write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): void { $inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction(); if ($inTransaction && isset($this->options['writeConcern'])) { throw UnsupportedException::writeConcernNotSupportedInTransaction(); } - $cursor = $server->executeWriteCommand('admin', $this->createCommand(), $this->createOptions()); - - if (isset($this->options['typeMap'])) { - $cursor->setTypeMap($this->options['typeMap']); - } - - return current($cursor->toArray()); + $server->executeWriteCommand('admin', $this->createCommand(), $this->createOptions()); } /** diff --git a/src/Operation/ReplaceOne.php b/src/Operation/ReplaceOne.php index 843640923..70bfd8f77 100644 --- a/src/Operation/ReplaceOne.php +++ b/src/Operation/ReplaceOne.php @@ -33,10 +33,8 @@ * * @see \MongoDB\Collection::replaceOne() * @see https://mongodb.com/docs/manual/reference/command/update/ - * - * @final extending this class will not be supported in v2.0.0 */ -class ReplaceOne implements Executable +final class ReplaceOne { private Update $update; @@ -111,18 +109,15 @@ public function __construct(string $databaseName, string $collectionName, array| /** * Execute the operation. * - * @see Executable::execute() - * @return UpdateResult * @throws UnsupportedException if collation is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): UpdateResult { return $this->update->execute($server); } - /** @return array|object */ - private function validateReplacement(array|object $replacement, ?DocumentCodec $codec) + private function validateReplacement(array|object $replacement, ?DocumentCodec $codec): array|object { if ($codec) { $replacement = $codec->encode($replacement); diff --git a/src/Operation/Update.php b/src/Operation/Update.php index 2016d89a8..3c8118b81 100644 --- a/src/Operation/Update.php +++ b/src/Operation/Update.php @@ -44,7 +44,7 @@ * @internal * @see https://mongodb.com/docs/manual/reference/command/update/ */ -class Update implements Executable, Explainable +final class Update implements Explainable { private const WIRE_VERSION_FOR_HINT = 8; @@ -171,12 +171,10 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() - * @return UpdateResult * @throws UnsupportedException if hint or write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): UpdateResult { /* CRUD spec requires a client-side error when using "hint" with an * unacknowledged write concern on an unsupported server. */ @@ -204,9 +202,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { $cmd = ['update' => $this->collectionName, 'updates' => [['q' => $this->filter, 'u' => $this->update] + $this->createUpdateOptions()]]; diff --git a/src/Operation/UpdateMany.php b/src/Operation/UpdateMany.php index f600d46e6..97865288c 100644 --- a/src/Operation/UpdateMany.php +++ b/src/Operation/UpdateMany.php @@ -31,10 +31,8 @@ * * @see \MongoDB\Collection::updateMany() * @see https://mongodb.com/docs/manual/reference/command/update/ - * - * @final extending this class will not be supported in v2.0.0 */ -class UpdateMany implements Executable, Explainable +final class UpdateMany implements Explainable { private Update $update; @@ -99,12 +97,10 @@ public function __construct(string $databaseName, string $collectionName, array| /** * Execute the operation. * - * @see Executable::execute() - * @return UpdateResult * @throws UnsupportedException if collation is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): UpdateResult { return $this->update->execute($server); } @@ -113,9 +109,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { return $this->update->getCommandDocument(); } diff --git a/src/Operation/UpdateOne.php b/src/Operation/UpdateOne.php index d7bbe57cb..7ead97312 100644 --- a/src/Operation/UpdateOne.php +++ b/src/Operation/UpdateOne.php @@ -31,10 +31,8 @@ * * @see \MongoDB\Collection::updateOne() * @see https://mongodb.com/docs/manual/reference/command/update/ - * - * @final extending this class will not be supported in v2.0.0 */ -class UpdateOne implements Executable, Explainable +final class UpdateOne implements Explainable { private Update $update; @@ -105,12 +103,10 @@ public function __construct(string $databaseName, string $collectionName, array| /** * Execute the operation. * - * @see Executable::execute() - * @return UpdateResult * @throws UnsupportedException if collation is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): UpdateResult { return $this->update->execute($server); } @@ -119,9 +115,8 @@ public function execute(Server $server) * Returns the command document for this operation. * * @see Explainable::getCommandDocument() - * @return array */ - public function getCommandDocument() + public function getCommandDocument(): array { return $this->update->getCommandDocument(); } diff --git a/src/Operation/UpdateSearchIndex.php b/src/Operation/UpdateSearchIndex.php index effb4a942..4543914bb 100644 --- a/src/Operation/UpdateSearchIndex.php +++ b/src/Operation/UpdateSearchIndex.php @@ -30,10 +30,8 @@ * * @see \MongoDB\Collection::updateSearchIndexes() * @see https://mongodb.com/docs/manual/reference/command/updateSearchIndexes/ - * - * @final extending this class will not be supported in v2.0.0 */ -class UpdateSearchIndex implements Executable +final class UpdateSearchIndex { private object $definition; @@ -63,7 +61,6 @@ public function __construct(private string $databaseName, private string $collec /** * Execute the operation. * - * @see Executable::execute() * @throws UnsupportedException if write concern is used and unsupported * @throws DriverRuntimeException for other driver errors (e.g. connection errors) */ diff --git a/src/Operation/Watch.php b/src/Operation/Watch.php index 7e192b298..a46d5de57 100644 --- a/src/Operation/Watch.php +++ b/src/Operation/Watch.php @@ -17,7 +17,6 @@ namespace MongoDB\Operation; -use Iterator; use MongoDB\BSON\TimestampInterface; use MongoDB\ChangeStream; use MongoDB\Codec\DocumentCodec; @@ -56,12 +55,9 @@ * * @see \MongoDB\Collection::watch() * @see https://mongodb.com/docs/manual/changeStreams/ - * - * @final extending this class will not be supported in v2.0.0 */ -class Watch implements Executable, /* @internal */ CommandSubscriber +final class Watch implements /* @internal */ CommandSubscriber { - public const FULL_DOCUMENT_DEFAULT = 'default'; public const FULL_DOCUMENT_UPDATE_LOOKUP = 'updateLookup'; public const FULL_DOCUMENT_WHEN_AVAILABLE = 'whenAvailable'; public const FULL_DOCUMENT_REQUIRED = 'required'; @@ -267,12 +263,10 @@ public function __construct(private Manager $manager, ?string $databaseName, pri /** * Execute the operation. * - * @see Executable::execute() - * @return ChangeStream * @throws UnsupportedException if collation or read concern is used and unsupported * @throws RuntimeException for other driver errors (e.g. connection errors) */ - public function execute(Server $server) + public function execute(Server $server): ChangeStream { return new ChangeStream( $this->createChangeStreamIterator($server), @@ -355,10 +349,8 @@ private function createChangeStreamIterator(Server $server): ChangeStreamIterato * * The command will be executed using APM so that we can capture data from * its response (e.g. firstBatch size, postBatchResumeToken). - * - * @return CursorInterface&Iterator */ - private function executeAggregate(Server $server) + private function executeAggregate(Server $server): CursorInterface { addSubscriber($this); @@ -372,10 +364,9 @@ private function executeAggregate(Server $server) /** * Return the initial resume token for creating the ChangeStreamIterator. * - * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#updating-the-cached-resume-token - * @return array|object|null + * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.md#updating-the-cached-resume-token */ - private function getInitialResumeToken() + private function getInitialResumeToken(): array|object|null { if ($this->firstBatchSize === 0 && isset($this->postBatchResumeToken)) { return $this->postBatchResumeToken; @@ -395,7 +386,7 @@ private function getInitialResumeToken() /** * Resumes a change stream. * - * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#resume-process + * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.md#resume-process * @throws InvalidArgumentException */ private function resume(array|object|null $resumeToken = null, bool $hasAdvanced = false): ChangeStreamIterator @@ -431,7 +422,7 @@ private function resume(array|object|null $resumeToken = null, bool $hasAdvanced /** * Determine whether to capture operation time from an aggregate response. * - * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.rst#startatoperationtime + * @see https://github.com/mongodb/specifications/blob/master/source/change-streams/change-streams.md#startatoperationtime */ private function shouldCaptureOperationTime(): bool { diff --git a/src/Operation/WithTransaction.php b/src/Operation/WithTransaction.php index f0b5b0ec7..760825946 100644 --- a/src/Operation/WithTransaction.php +++ b/src/Operation/WithTransaction.php @@ -11,7 +11,7 @@ use function time; /** @internal */ -class WithTransaction +final class WithTransaction { /** @var callable */ private $callback; diff --git a/src/UpdateResult.php b/src/UpdateResult.php index 58a73a890..a9d50b751 100644 --- a/src/UpdateResult.php +++ b/src/UpdateResult.php @@ -17,19 +17,16 @@ namespace MongoDB; +use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\WriteResult; -use MongoDB\Exception\BadMethodCallException; /** * Result class for an update operation. */ class UpdateResult { - private bool $isAcknowledged; - public function __construct(private WriteResult $writeResult) { - $this->isAcknowledged = $writeResult->isAcknowledged(); } /** @@ -38,16 +35,11 @@ public function __construct(private WriteResult $writeResult) * This method should only be called if the write was acknowledged. * * @see UpdateResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getMatchedCount() + public function getMatchedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getMatchedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getMatchedCount(); } /** @@ -59,16 +51,11 @@ public function getMatchedCount() * This method should only be called if the write was acknowledged. * * @see UpdateResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getModifiedCount() + public function getModifiedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getModifiedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getModifiedCount(); } /** @@ -77,16 +64,11 @@ public function getModifiedCount() * This method should only be called if the write was acknowledged. * * @see UpdateResult::isAcknowledged() - * @return integer|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getUpsertedCount() + public function getUpsertedCount(): int { - if ($this->isAcknowledged) { - return $this->writeResult->getUpsertedCount(); - } - - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return $this->writeResult->getUpsertedCount(); } /** @@ -101,20 +83,15 @@ public function getUpsertedCount() * This method should only be called if the write was acknowledged. * * @see UpdateResult::isAcknowledged() - * @return mixed|null - * @throws BadMethodCallException if the write result is unacknowledged + * @throws LogicException if the write result is unacknowledged */ - public function getUpsertedId() + public function getUpsertedId(): mixed { - if ($this->isAcknowledged) { - foreach ($this->writeResult->getUpsertedIds() as $id) { - return $id; - } - - return null; + foreach ($this->writeResult->getUpsertedIds() as $id) { + return $id; } - throw BadMethodCallException::unacknowledgedWriteResultAccess(__METHOD__); + return null; } /** @@ -123,11 +100,9 @@ public function getUpsertedId() * If the update was not acknowledged, other fields from the WriteResult * (e.g. matchedCount) will be undefined and their getter methods should not * be invoked. - * - * @return boolean */ - public function isAcknowledged() + public function isAcknowledged(): bool { - return $this->isAcknowledged; + return $this->writeResult->isAcknowledged(); } } diff --git a/src/functions.php b/src/functions.php index b38fc1fae..2e68987c6 100644 --- a/src/functions.php +++ b/src/functions.php @@ -122,10 +122,9 @@ function all_servers_support_write_stage_on_secondary(array $servers): bool * @internal * @param array|object $document Document to which the type map will be applied * @param array $typeMap Type map for BSON deserialization. - * @return array|object * @throws InvalidArgumentException */ -function apply_type_map_to_document(array|object $document, array $typeMap) +function apply_type_map_to_document(array|object $document, array $typeMap): array|object { if (! is_document($document)) { throw InvalidArgumentException::expectedDocumentType('$document', $document); @@ -179,13 +178,12 @@ function document_to_array(array|object $document): array * autoEncryption driver option (if available). * * @internal - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#collection-encryptedfields-lookup-getencryptedfields + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.md#collection-encryptedfields-lookup-getencryptedfields * @see Collection::drop() * @see Database::createCollection() * @see Database::dropCollection() - * @return array|object|null */ -function get_encrypted_fields_from_driver(string $databaseName, string $collectionName, Manager $manager) +function get_encrypted_fields_from_driver(string $databaseName, string $collectionName, Manager $manager): array|object|null { $encryptedFieldsMap = (array) $manager->getEncryptedFieldsMap(); @@ -196,12 +194,11 @@ function get_encrypted_fields_from_driver(string $databaseName, string $collecti * Return a collection's encryptedFields option from the server (if any). * * @internal - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.rst#collection-encryptedfields-lookup-getencryptedfields + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/client-side-encryption.md#collection-encryptedfields-lookup-getencryptedfields * @see Collection::drop() * @see Database::dropCollection() - * @return array|object|null */ -function get_encrypted_fields_from_server(string $databaseName, string $collectionName, Server $server) +function get_encrypted_fields_from_server(string $databaseName, string $collectionName, Server $server): array|object|null { $collectionInfoIterator = (new ListCollections($databaseName, ['filter' => ['name' => $collectionName]]))->execute($server); @@ -385,24 +382,6 @@ function is_last_pipeline_operator_write(array $pipeline): bool return $key === '$merge' || $key === '$out'; } -/** - * Return whether the "out" option for a mapReduce operation is "inline". - * - * This is used to determine if a mapReduce command requires a primary. - * - * @internal - * @see https://mongodb.com/docs/manual/reference/command/mapReduce/#output-inline - * @param string|array|object $out Output specification - */ -function is_mapreduce_output_inline(string|array|object $out): bool -{ - if (! is_array($out) && ! is_object($out)) { - return false; - } - - return array_key_first(document_to_array($out)) === 'inline'; -} - /** * Return whether the write concern is acknowledged. * @@ -464,10 +443,9 @@ function is_string_array(mixed $input): bool * @internal * @see https://bugs.php.net/bug.php?id=49664 * @param mixed $element Value to be copied - * @return mixed * @throws ReflectionException */ -function recursive_copy(mixed $element) +function recursive_copy(mixed $element): mixed { if (is_array($element)) { foreach ($element as $key => $value) { @@ -628,7 +606,7 @@ function select_server(Manager $manager, array $options): Server * must be forced due to the existence of pre-5.0 servers in the topology. * * @internal - * @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.rst#aggregation-pipelines-with-write-stages + * @see https://github.com/mongodb/specifications/blob/master/source/crud/crud.md#aggregation-pipelines-with-write-stages */ function select_server_for_aggregate_write_stage(Manager $manager, array &$options): Server { diff --git a/stubs/Driver/BulkWriteCommand.stub.php b/stubs/Driver/BulkWriteCommand.stub.php new file mode 100644 index 000000000..464b3f1ae --- /dev/null +++ b/stubs/Driver/BulkWriteCommand.stub.php @@ -0,0 +1,40 @@ + + */ +final class Cursor implements CursorInterface +{ + /** + * @return TValue|null + * @psalm-ignore-nullable-return + */ + public function current(): array|object|null + { + } + + public function next(): void + { + } + + /** @psalm-ignore-nullable-return */ + public function key(): ?int + { + } + + public function valid(): bool + { + } + + public function rewind(): void + { + } + + /** @return array */ + public function toArray(): array + { + } + + public function getId(): Int64 + { + } + + public function getServer(): Server + { + } + + public function isDead(): bool + { + } + + public function setTypeMap(array $typemap): void + { + } +} diff --git a/stubs/Driver/CursorInterface.stub.php b/stubs/Driver/CursorInterface.stub.php new file mode 100644 index 000000000..d2a89737a --- /dev/null +++ b/stubs/Driver/CursorInterface.stub.php @@ -0,0 +1,33 @@ + + */ +interface CursorInterface extends Iterator +{ + /** + * @return TValue|null + * @psalm-ignore-nullable-return + */ + public function current(): array|object|null; + + public function getId(): Int64; + + public function getServer(): Server; + + public function isDead(): bool; + + /** @psalm-ignore-nullable-return */ + public function key(): ?int; + + public function setTypeMap(array $typemap): void; + + /** @return array */ + public function toArray(): array; +} diff --git a/stubs/Driver/WriteResult.stub.php b/stubs/Driver/WriteResult.stub.php new file mode 100644 index 000000000..daa2f4eb1 --- /dev/null +++ b/stubs/Driver/WriteResult.stub.php @@ -0,0 +1,32 @@ +assertSamePipeline(Pipelines::ConcatArraysWarehouseCollection, $pipeline); + } +} diff --git a/tests/Builder/Accumulator/Pipelines.php b/tests/Builder/Accumulator/Pipelines.php index fedc91ab4..23b313828 100644 --- a/tests/Builder/Accumulator/Pipelines.php +++ b/tests/Builder/Accumulator/Pipelines.php @@ -374,6 +374,26 @@ enum Pipelines: string ] JSON; + /** + * Warehouse collection + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/concatArrays/#example + */ + case ConcatArraysWarehouseCollection = <<<'JSON' + [ + { + "$project": { + "items": { + "$concatArrays": [ + "$instock", + "$ordered" + ] + } + } + } + ] + JSON; + /** * Use in $group Stage * @@ -1913,6 +1933,35 @@ enum Pipelines: string ] JSON; + /** + * Flowers collection + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/setUnion/#example + */ + case SetUnionFlowersCollection = <<<'JSON' + [ + { + "$project": { + "flowerFieldA": { + "$numberInt": "1" + }, + "flowerFieldB": { + "$numberInt": "1" + }, + "allValues": { + "$setUnion": [ + "$flowerFieldA", + "$flowerFieldB" + ] + }, + "_id": { + "$numberInt": "0" + } + } + } + ] + JSON; + /** * Shift Using a Positive Integer * diff --git a/tests/Builder/Accumulator/SetUnionAccumulatorTest.php b/tests/Builder/Accumulator/SetUnionAccumulatorTest.php new file mode 100644 index 000000000..a6b83c5c2 --- /dev/null +++ b/tests/Builder/Accumulator/SetUnionAccumulatorTest.php @@ -0,0 +1,36 @@ +assertSamePipeline(Pipelines::SetUnionFlowersCollection, $pipeline); + } +} diff --git a/tests/Builder/BuilderEncoderTest.php b/tests/Builder/BuilderEncoderTest.php index 719ab9f7e..53080ab9e 100644 --- a/tests/Builder/BuilderEncoderTest.php +++ b/tests/Builder/BuilderEncoderTest.php @@ -393,7 +393,7 @@ public function canEncode(mixed $value): bool return $value instanceof FieldPathInterface; } - public function encode(mixed $value) + public function encode(mixed $value): mixed { return '$prefix.' . $value->name; } @@ -423,6 +423,48 @@ public function encode(mixed $value) $this->assertSamePipeline($expected, $pipeline, $codec); } + public function testCustomEncoderIterable(): void + { + $customEncoders = static function (): Generator { + yield FieldPathInterface::class => new class implements Encoder { + use EncodeIfSupported; + + public function canEncode(mixed $value): bool + { + return $value instanceof FieldPathInterface; + } + + public function encode(mixed $value): mixed + { + return '$prefix.' . $value->name; + } + }; + }; + + $codec = new BuilderEncoder($customEncoders()); + + $pipeline = new Pipeline( + Stage::project( + threeFavorites: Expression::slice( + Expression::arrayFieldPath('items'), + n: 3, + ), + ), + ); + + $expected = [ + [ + '$project' => [ + 'threeFavorites' => [ + '$slice' => ['$prefix.items', 3], + ], + ], + ], + ]; + + $this->assertSamePipeline($expected, $pipeline, $codec); + } + /** @param list> $expected */ private static function assertSamePipeline(array $expected, Pipeline $pipeline, $codec = new BuilderEncoder()): void { diff --git a/tests/Builder/Expression/CreateObjectIdOperatorTest.php b/tests/Builder/Expression/CreateObjectIdOperatorTest.php new file mode 100644 index 000000000..687d80638 --- /dev/null +++ b/tests/Builder/Expression/CreateObjectIdOperatorTest.php @@ -0,0 +1,27 @@ +assertSamePipeline(Pipelines::CreateObjectIdExample, $pipeline); + } +} diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index f84d00984..e5bdd225e 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -910,6 +910,23 @@ enum Pipelines: string ] JSON; + /** + * Example + * + * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/createObjectId/#example + */ + case CreateObjectIdExample = <<<'JSON' + [ + { + "$project": { + "objectId": { + "$createObjectId": {} + } + } + } + ] + JSON; + /** * Add a Future Date * @@ -4203,6 +4220,28 @@ enum Pipelines: string ] JSON; + /** Support regex search string */ + case ReplaceAllSupportRegexSearchString = <<<'JSON' + [ + { + "$project": { + "item": { + "$replaceAll": { + "input": "123-456-7890", + "find": { + "$regularExpression": { + "pattern": "\\d{3}", + "options": "" + } + }, + "replacement": "xxx" + } + } + } + } + ] + JSON; + /** * Example * @@ -4998,6 +5037,27 @@ enum Pipelines: string ] JSON; + /** Support regex delimiter */ + case SplitSupportRegexDelimiter = <<<'JSON' + [ + { + "$project": { + "split": { + "$split": [ + "abc", + { + "$regularExpression": { + "pattern": "b", + "options": "" + } + } + ] + } + } + } + ] + JSON; + /** * Example * diff --git a/tests/Builder/Expression/ReplaceAllOperatorTest.php b/tests/Builder/Expression/ReplaceAllOperatorTest.php index 1d1fa6ada..d2247daec 100644 --- a/tests/Builder/Expression/ReplaceAllOperatorTest.php +++ b/tests/Builder/Expression/ReplaceAllOperatorTest.php @@ -4,6 +4,7 @@ namespace MongoDB\Tests\Builder\Expression; +use MongoDB\BSON\Regex; use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; @@ -28,4 +29,19 @@ public function testExample(): void $this->assertSamePipeline(Pipelines::ReplaceAllExample, $pipeline); } + + public function testSupportRegexSearchString(): void + { + $pipeline = new Pipeline( + Stage::project( + item: Expression::replaceAll( + input: '123-456-7890', + find: new Regex('\d{3}'), + replacement: 'xxx', + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReplaceAllSupportRegexSearchString, $pipeline); + } } diff --git a/tests/Builder/Expression/SplitOperatorTest.php b/tests/Builder/Expression/SplitOperatorTest.php index c302f4e9d..ee997d539 100644 --- a/tests/Builder/Expression/SplitOperatorTest.php +++ b/tests/Builder/Expression/SplitOperatorTest.php @@ -50,4 +50,18 @@ public function testExample(): void $this->assertSamePipeline(Pipelines::SplitExample, $pipeline); } + + public function testSupportRegexDelimiter(): void + { + $pipeline = new Pipeline( + Stage::project( + split: Expression::split( + string: 'abc', + delimiter: new Regex('b'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SplitSupportRegexDelimiter, $pipeline); + } } diff --git a/tests/ClientFunctionalTest.php b/tests/ClientFunctionalTest.php index ca0ea2722..393f23607 100644 --- a/tests/ClientFunctionalTest.php +++ b/tests/ClientFunctionalTest.php @@ -2,6 +2,7 @@ namespace MongoDB\Tests; +use Iterator; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; @@ -12,7 +13,6 @@ use MongoDB\Driver\Monitoring\CommandSubscriber; use MongoDB\Driver\Session; use MongoDB\Model\DatabaseInfo; -use MongoDB\Model\DatabaseInfoIterator; use function call_user_func; use function is_callable; @@ -47,8 +47,7 @@ public function testDropDatabase(): void $writeResult = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite); $this->assertEquals(1, $writeResult->getInsertedCount()); - $commandResult = $this->client->dropDatabase($this->getDatabaseName()); - $this->assertCommandSucceeded($commandResult); + $this->client->dropDatabase($this->getDatabaseName()); $this->assertCollectionCount($this->getNamespace(), 0); } @@ -62,7 +61,7 @@ public function testListDatabases(): void $databases = $this->client->listDatabases(); - $this->assertInstanceOf(DatabaseInfoIterator::class, $databases); + $this->assertInstanceOf(Iterator::class, $databases); foreach ($databases as $database) { $this->assertInstanceOf(DatabaseInfo::class, $database); diff --git a/tests/Collection/BuilderCollectionFunctionalTest.php b/tests/Collection/BuilderCollectionFunctionalTest.php index 2ace15109..484ad972d 100644 --- a/tests/Collection/BuilderCollectionFunctionalTest.php +++ b/tests/Collection/BuilderCollectionFunctionalTest.php @@ -6,8 +6,7 @@ use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; - -use function iterator_to_array; +use PHPUnit\Framework\Attributes\TestWith; class BuilderCollectionFunctionalTest extends FunctionalTestCase { @@ -18,17 +17,21 @@ public function setUp(): void $this->collection->insertMany([['x' => 1], ['x' => 2], ['x' => 2]]); } - public function testAggregate(): void + #[TestWith([true])] + #[TestWith([false])] + public function testAggregate(bool $pipelineAsArray): void { $this->collection->insertMany([['x' => 10], ['x' => 10], ['x' => 10]]); - $pipeline = new Pipeline( + $pipeline = [ Stage::bucketAuto( groupBy: Expression::intFieldPath('x'), buckets: 2, ), - ); - // Extract the list of stages for arg type restriction - $pipeline = iterator_to_array($pipeline); + ]; + + if (! $pipelineAsArray) { + $pipeline = new Pipeline(...$pipeline); + } $results = $this->collection->aggregate($pipeline)->toArray(); $this->assertCount(2, $results); @@ -183,6 +186,20 @@ public function testFindOneAndUpdate(): void $this->assertEquals(3, $result->x); } + public function testFindOneAndUpdateWithPipelineUpdate(): void + { + $result = $this->collection->findOneAndUpdate( + Query::query(x: Query::lt(2)), + new Pipeline( + Stage::set(x: 3), + ), + ); + $this->assertEquals(1, $result->x); + + $result = $this->collection->findOne(Query::query(x: Query::eq(3))); + $this->assertEquals(3, $result->x); + } + public function testReplaceOne(): void { $this->collection->insertOne(['x' => 1]); @@ -257,7 +274,9 @@ public function testUpdateManyWithPipeline(): void $this->assertEquals(3, $result[0]->x); } - public function testWatch(): void + #[TestWith([true])] + #[TestWith([false])] + public function testWatch(bool $pipelineAsArray): void { $this->skipIfChangeStreamIsNotSupported(); @@ -265,11 +284,13 @@ public function testWatch(): void $this->markTestSkipped('Test does not apply on sharded clusters: need more than a single getMore call on the change stream.'); } - $pipeline = new Pipeline( + $pipeline = [ Stage::match(operationType: Query::eq('insert')), - ); - // Extract the list of stages for arg type restriction - $pipeline = iterator_to_array($pipeline); + ]; + + if (! $pipelineAsArray) { + $pipeline = new Pipeline(...$pipeline); + } $changeStream = $this->collection->watch($pipeline); $this->collection->insertOne(['x' => 3]); diff --git a/tests/Collection/CollectionFunctionalTest.php b/tests/Collection/CollectionFunctionalTest.php index d937f7bee..95659381c 100644 --- a/tests/Collection/CollectionFunctionalTest.php +++ b/tests/Collection/CollectionFunctionalTest.php @@ -3,7 +3,6 @@ namespace MongoDB\Tests\Collection; use Closure; -use MongoDB\BSON\Javascript; use MongoDB\Codec\DocumentCodec; use MongoDB\Codec\Encoder; use MongoDB\Collection; @@ -15,11 +14,9 @@ use MongoDB\Driver\WriteConcern; use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\UnsupportedException; -use MongoDB\MapReduceResult; use MongoDB\Operation\Count; use MongoDB\Tests\CommandObserver; use PHPUnit\Framework\Attributes\DataProvider; -use PHPUnit\Framework\Attributes\Group; use ReflectionClass; use TypeError; @@ -30,7 +27,6 @@ use function json_encode; use function str_contains; use function usort; -use function version_compare; use const JSON_THROW_ON_ERROR; @@ -82,6 +78,22 @@ public static function provideInvalidConstructorOptions(): array ]); } + public function testGetBuilderEncoder(): void + { + $collectionOptions = ['builderEncoder' => $this->createMock(Encoder::class)]; + $collection = new Collection($this->manager, $this->getDatabaseName(), $this->getCollectionName(), $collectionOptions); + + $this->assertSame($collectionOptions['builderEncoder'], $collection->getBuilderEncoder()); + } + + public function testGetCodec(): void + { + $collectionOptions = ['codec' => $this->createMock(DocumentCodec::class)]; + $collection = new Collection($this->manager, $this->getDatabaseName(), $this->getCollectionName(), $collectionOptions); + + $this->assertSame($collectionOptions['codec'], $collection->getCodec()); + } + public function testGetManager(): void { $this->assertSame($this->manager, $this->collection->getManager()); @@ -259,8 +271,7 @@ public function testDrop(): void $writeResult = $this->collection->insertOne(['x' => 1]); $this->assertEquals(1, $writeResult->getInsertedCount()); - $commandResult = $this->collection->drop(); - $this->assertCommandSucceeded($commandResult); + $this->collection->drop(); $this->assertCollectionDoesNotExist($this->getCollectionName()); } @@ -336,8 +347,7 @@ public function testRenameToSameDatabase(): void $writeResult = $this->collection->insertOne(['_id' => 1]); $this->assertEquals(1, $writeResult->getInsertedCount()); - $commandResult = $this->collection->rename($toCollectionName, null, ['dropTarget' => true]); - $this->assertCommandSucceeded($commandResult); + $this->collection->rename($toCollectionName, null, ['dropTarget' => true]); $this->assertCollectionDoesNotExist($this->getCollectionName()); $this->assertCollectionExists($toCollectionName); @@ -365,8 +375,7 @@ public function testRenameToDifferentDatabase(): void $writeResult = $this->collection->insertOne(['_id' => 1]); $this->assertEquals(1, $writeResult->getInsertedCount()); - $commandResult = $this->collection->rename($toCollectionName, $toDatabaseName); - $this->assertCommandSucceeded($commandResult); + $this->collection->rename($toCollectionName, $toDatabaseName); $this->assertCollectionDoesNotExist($this->getCollectionName()); $this->assertCollectionExists($toCollectionName, $toDatabaseName); @@ -435,35 +444,6 @@ public function testWithOptionsPassesOptions(): void $this->assertSame(true, $rp->getValue($clone)); } - #[Group('matrix-testing-exclude-server-4.4-driver-4.0')] - #[Group('matrix-testing-exclude-server-4.4-driver-4.2')] - #[Group('matrix-testing-exclude-server-5.0-driver-4.0')] - #[Group('matrix-testing-exclude-server-5.0-driver-4.2')] - public function testMapReduce(): void - { - $this->createFixtures(3); - - $map = new Javascript('function() { emit(1, this.x); }'); - $reduce = new Javascript('function(key, values) { return Array.sum(values); }'); - $out = ['inline' => 1]; - - $result = $this->assertDeprecated( - fn () => $this->collection->mapReduce($map, $reduce, $out), - ); - - $this->assertInstanceOf(MapReduceResult::class, $result); - $expected = [ - [ '_id' => 1.0, 'value' => 66.0 ], - ]; - - $this->assertSameDocuments($expected, $result); - - if (version_compare($this->getServerVersion(), '4.3.0', '<')) { - $this->assertGreaterThanOrEqual(0, $result->getExecutionTimeMS()); - $this->assertNotEmpty($result->getCounts()); - } - } - public static function collectionMethodClosures() { return [ @@ -676,19 +656,6 @@ function($collection, $session, $options = []) { ], */ - /* Disabled, as it's illegal to use mapReduce command in transactions - 'mapReduce' => [ - function($collection, $session, $options = []) { - $collection->mapReduce( - new \MongoDB\BSON\Javascript('function() { emit(this.state, this.pop); }'), - new \MongoDB\BSON\Javascript('function(key, values) { return Array.sum(values) }'), - ['inline' => 1], - ['session' => $session] + $options - ); - }, 'rw' - ], - */ - 'replaceOne' => [ function ($collection, $session, $options = []): void { $collection->replaceOne( @@ -785,7 +752,7 @@ public function testMethodInTransactionWithWriteConcernOption($method): void $session->startTransaction(); $this->expectException(UnsupportedException::class); - $this->expectExceptionMessage('"writeConcern" option cannot be specified within a transaction'); + $this->expectExceptionMessage('Cannot set write concern after starting a transaction'); try { call_user_func($method, $this->collection, $session, ['writeConcern' => new WriteConcern(1)]); @@ -805,7 +772,7 @@ public function testMethodInTransactionWithReadConcernOption($method): void $session->startTransaction(); $this->expectException(UnsupportedException::class); - $this->expectExceptionMessage('"readConcern" option cannot be specified within a transaction'); + $this->expectExceptionMessage('Cannot set read concern after starting a transaction'); try { call_user_func($method, $this->collection, $session, ['readConcern' => new ReadConcern(ReadConcern::LOCAL)]); diff --git a/tests/Database/BuilderDatabaseFunctionalTest.php b/tests/Database/BuilderDatabaseFunctionalTest.php index 9b89d87e1..491d2ccc1 100644 --- a/tests/Database/BuilderDatabaseFunctionalTest.php +++ b/tests/Database/BuilderDatabaseFunctionalTest.php @@ -6,8 +6,7 @@ use MongoDB\Builder\Pipeline; use MongoDB\Builder\Query; use MongoDB\Builder\Stage; - -use function iterator_to_array; +use PHPUnit\Framework\Attributes\TestWith; class BuilderDatabaseFunctionalTest extends FunctionalTestCase { @@ -18,11 +17,13 @@ public function tearDown(): void parent::tearDown(); } - public function testAggregate(): void + #[TestWith([true])] + #[TestWith([false])] + public function testAggregate(bool $pipelineAsArray): void { $this->skipIfServerVersion('<', '6.0.0', '$documents stage is not supported'); - $pipeline = new Pipeline( + $pipeline = [ Stage::documents([ ['x' => 1], ['x' => 2], @@ -32,15 +33,19 @@ public function testAggregate(): void groupBy: Expression::intFieldPath('x'), buckets: 2, ), - ); - // Extract the list of stages for arg type restriction - $pipeline = iterator_to_array($pipeline); + ]; + + if (! $pipelineAsArray) { + $pipeline = new Pipeline(...$pipeline); + } $results = $this->database->aggregate($pipeline)->toArray(); $this->assertCount(2, $results); } - public function testWatch(): void + #[TestWith([true])] + #[TestWith([false])] + public function testWatch(bool $pipelineAsArray): void { $this->skipIfChangeStreamIsNotSupported(); @@ -48,11 +53,13 @@ public function testWatch(): void $this->markTestSkipped('Test does not apply on sharded clusters: need more than a single getMore call on the change stream.'); } - $pipeline = new Pipeline( + $pipeline = [ Stage::match(operationType: Query::eq('insert')), - ); - // Extract the list of stages for arg type restriction - $pipeline = iterator_to_array($pipeline); + ]; + + if (! $pipelineAsArray) { + $pipeline = new Pipeline(...$pipeline); + } $changeStream = $this->database->watch($pipeline); $this->database->selectCollection($this->getCollectionName())->insertOne(['x' => 3]); diff --git a/tests/Database/CollectionManagementFunctionalTest.php b/tests/Database/CollectionManagementFunctionalTest.php index 485bcd8e5..dc5b49974 100644 --- a/tests/Database/CollectionManagementFunctionalTest.php +++ b/tests/Database/CollectionManagementFunctionalTest.php @@ -2,9 +2,9 @@ namespace MongoDB\Tests\Database; +use Iterator; use MongoDB\Driver\BulkWrite; use MongoDB\Model\CollectionInfo; -use MongoDB\Model\CollectionInfoIterator; /** * Functional tests for collection management methods. @@ -16,8 +16,7 @@ public function testCreateCollection(): void $that = $this; $basicCollectionName = $this->getCollectionName() . '.basic'; - $commandResult = $this->database->createCollection($basicCollectionName); - $this->assertCommandSucceeded($commandResult); + $this->database->createCollection($basicCollectionName); $this->assertCollectionExists($basicCollectionName, null, function (CollectionInfo $info) use ($that): void { $that->assertFalse($info->isCapped()); }); @@ -29,8 +28,7 @@ public function testCreateCollection(): void 'size' => 1_048_576, ]; - $commandResult = $this->database->createCollection($cappedCollectionName, $cappedCollectionOptions); - $this->assertCommandSucceeded($commandResult); + $this->database->createCollection($cappedCollectionName, $cappedCollectionOptions); $this->assertCollectionExists($cappedCollectionName, null, function (CollectionInfo $info) use ($that): void { $that->assertTrue($info->isCapped()); $that->assertEquals(100, $info->getCappedMax()); @@ -46,18 +44,16 @@ public function testDropCollection(): void $writeResult = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite); $this->assertEquals(1, $writeResult->getInsertedCount()); - $commandResult = $this->database->dropCollection($this->getCollectionName()); - $this->assertCommandSucceeded($commandResult); + $this->database->dropCollection($this->getCollectionName()); $this->assertCollectionCount($this->getNamespace(), 0); } public function testListCollections(): void { - $commandResult = $this->database->createCollection($this->getCollectionName()); - $this->assertCommandSucceeded($commandResult); + $this->database->createCollection($this->getCollectionName()); $collections = $this->database->listCollections(); - $this->assertInstanceOf(CollectionInfoIterator::class, $collections); + $this->assertInstanceOf(Iterator::class, $collections); foreach ($collections as $collection) { $this->assertInstanceOf(CollectionInfo::class, $collection); @@ -66,14 +62,13 @@ public function testListCollections(): void public function testListCollectionsWithFilter(): void { - $commandResult = $this->database->createCollection($this->getCollectionName()); - $this->assertCommandSucceeded($commandResult); + $this->database->createCollection($this->getCollectionName()); $collectionName = $this->getCollectionName(); $options = ['filter' => ['name' => $collectionName]]; $collections = $this->database->listCollections($options); - $this->assertInstanceOf(CollectionInfoIterator::class, $collections); + $this->assertInstanceOf(Iterator::class, $collections); foreach ($collections as $collection) { $this->assertInstanceOf(CollectionInfo::class, $collection); @@ -83,8 +78,7 @@ public function testListCollectionsWithFilter(): void public function testListCollectionNames(): void { - $commandResult = $this->database->createCollection($this->getCollectionName()); - $this->assertCommandSucceeded($commandResult); + $this->database->createCollection($this->getCollectionName()); $collections = $this->database->listCollectionNames(); @@ -95,8 +89,7 @@ public function testListCollectionNames(): void public function testListCollectionNamesWithFilter(): void { - $commandResult = $this->database->createCollection($this->getCollectionName()); - $this->assertCommandSucceeded($commandResult); + $this->database->createCollection($this->getCollectionName()); $collectionName = $this->getCollectionName(); $options = ['filter' => ['name' => $collectionName]]; diff --git a/tests/Database/DatabaseFunctionalTest.php b/tests/Database/DatabaseFunctionalTest.php index fa9d9c926..7f4e564c2 100644 --- a/tests/Database/DatabaseFunctionalTest.php +++ b/tests/Database/DatabaseFunctionalTest.php @@ -142,8 +142,7 @@ public function testDrop(): void $writeResult = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite); $this->assertEquals(1, $writeResult->getInsertedCount()); - $commandResult = $this->database->drop(); - $this->assertCommandSucceeded($commandResult); + $this->database->drop(); $this->assertCollectionCount($this->getNamespace(), 0); } @@ -155,8 +154,7 @@ public function testDropCollection(): void $writeResult = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite); $this->assertEquals(1, $writeResult->getInsertedCount()); - $commandResult = $this->database->dropCollection($this->getCollectionName()); - $this->assertCommandSucceeded($commandResult); + $this->database->dropCollection($this->getCollectionName()); $this->assertCollectionDoesNotExist($this->getCollectionName()); } @@ -222,13 +220,12 @@ public function testRenameCollectionToSameDatabase(): void $writeResult = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite); $this->assertEquals(1, $writeResult->getInsertedCount()); - $commandResult = $this->database->renameCollection( + $this->database->renameCollection( $this->getCollectionName(), $toCollectionName, null, ['dropTarget' => true], ); - $this->assertCommandSucceeded($commandResult); $this->assertCollectionDoesNotExist($this->getCollectionName()); $this->assertCollectionExists($toCollectionName); @@ -259,12 +256,11 @@ public function testRenameCollectionToDifferentDatabase(): void $writeResult = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite); $this->assertEquals(1, $writeResult->getInsertedCount()); - $commandResult = $this->database->renameCollection( + $this->database->renameCollection( $this->getCollectionName(), $toCollectionName, $toDatabaseName, ); - $this->assertCommandSucceeded($commandResult); $this->assertCollectionDoesNotExist($this->getCollectionName()); $this->assertCollectionExists($toCollectionName, $toDatabaseName); diff --git a/tests/FunctionalTestCase.php b/tests/FunctionalTestCase.php index 5084c27d4..156d80149 100644 --- a/tests/FunctionalTestCase.php +++ b/tests/FunctionalTestCase.php @@ -83,7 +83,7 @@ public static function createTestClient(?string $uri = null, array $options = [] return new Client( $uri ?? static::getUri(), static::appendAuthenticationOptions($options), - static::appendServerApiOption($driverOptions), + static::appendDriverOptions($driverOptions), ); } @@ -92,7 +92,7 @@ public static function createTestManager(?string $uri = null, array $options = [ return new Manager( $uri ?? static::getUri(), static::appendAuthenticationOptions($options), - static::appendServerApiOption($driverOptions), + static::appendDriverOptions($driverOptions), ); } @@ -213,10 +213,6 @@ protected function assertSameObjectId($expectedObjectId, $actualObjectId): void */ public function configureFailPoint(array|stdClass $command, ?Server $server = null): void { - if (! $this->isFailCommandSupported()) { - $this->markTestSkipped('failCommand is only supported on mongod >= 4.0.0 and mongos >= 4.1.5.'); - } - if (! $this->isFailCommandEnabled()) { $this->markTestSkipped('The enableTestCommands parameter is not enabled.'); } @@ -236,10 +232,7 @@ public function configureFailPoint(array|stdClass $command, ?Server $server = nu $failPointServer = $server ?: $this->getPrimaryServer(); $operation = new DatabaseCommand('admin', $command); - $cursor = $operation->execute($failPointServer); - $result = $cursor->toArray()[0]; - - $this->assertCommandSucceeded($result); + $operation->execute($failPointServer); // Record the fail point so it can be disabled during tearDown() $this->configuredFailPoints[] = [$command->configureFailPoint, $failPointServer]; @@ -503,15 +496,7 @@ protected function skipIfTransactionsAreNotSupported(): void } if ($this->isShardedCluster()) { - if (version_compare($this->getFeatureCompatibilityVersion(), '4.2', '<')) { - $this->markTestSkipped('Transactions are only supported on FCV 4.2 or higher'); - } - - return; - } - - if (version_compare($this->getFeatureCompatibilityVersion(), '4.0', '<')) { - $this->markTestSkipped('Transactions are only supported on FCV 4.0 or higher'); + $this->markTestSkipped('Transactions are only supported on FCV 4.2 or higher'); } if ($this->getServerStorageEngine() !== 'wiredTiger') { @@ -584,8 +569,12 @@ private static function appendAuthenticationOptions(array $options): array return $options; } - private static function appendServerApiOption(array $driverOptions): array + private static function appendDriverOptions(array $driverOptions): array { + if (isset($driverOptions['autoEncryption']) && getenv('CRYPT_SHARED_LIB_PATH')) { + $driverOptions['autoEncryption']['extraOptions']['cryptSharedLibPath'] = getenv('CRYPT_SHARED_LIB_PATH'); + } + if (getenv('API_VERSION') && ! isset($driverOptions['serverApi'])) { $driverOptions['serverApi'] = new ServerApi(getenv('API_VERSION')); } @@ -675,16 +664,6 @@ private static function getUriWithoutMultipleMongoses(): string return $uri; } - /** - * Checks if the failCommand command is supported on this server version - */ - private function isFailCommandSupported(): bool - { - $minVersion = $this->isShardedCluster() ? '4.1.5' : '4.0.0'; - - return version_compare($this->getServerVersion(), $minVersion, '>='); - } - /** * Checks if the failCommand command is enabled by checking the enableTestCommands parameter */ diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php index 999c33247..bf64b03ee 100644 --- a/tests/FunctionsTest.php +++ b/tests/FunctionsTest.php @@ -18,7 +18,6 @@ use function MongoDB\is_builder_pipeline; use function MongoDB\is_first_key_operator; use function MongoDB\is_last_pipeline_operator_write; -use function MongoDB\is_mapreduce_output_inline; use function MongoDB\is_pipeline; use function MongoDB\is_write_concern_acknowledged; @@ -161,20 +160,6 @@ public function testIsFirstKeyOperatorArgumentTypeCheck($document): void is_first_key_operator($document); } - #[DataProvider('provideDocumentCasts')] - public function testIsMapReduceOutputInlineWithDocumentValues(callable $cast): void - { - $this->assertTrue(is_mapreduce_output_inline($cast(['inline' => 1]))); - // Note: only the key is significant - $this->assertTrue(is_mapreduce_output_inline($cast(['inline' => 0]))); - $this->assertFalse(is_mapreduce_output_inline($cast(['replace' => 'collectionName']))); - } - - public function testIsMapReduceOutputInlineWithStringValue(): void - { - $this->assertFalse(is_mapreduce_output_inline('collectionName')); - } - #[DataProvider('provideTypeMapValues')] public function testCreateFieldPathTypeMap(array $expected, array $typeMap, $fieldPath = 'field'): void { diff --git a/tests/GridFS/BucketFunctionalTest.php b/tests/GridFS/BucketFunctionalTest.php index 8faef8e38..8c347c5cd 100644 --- a/tests/GridFS/BucketFunctionalTest.php +++ b/tests/GridFS/BucketFunctionalTest.php @@ -62,7 +62,6 @@ public function testValidConstructorOptions(): void 'readConcern' => new ReadConcern(ReadConcern::LOCAL), 'readPreference' => new ReadPreference(ReadPreference::PRIMARY), 'writeConcern' => new WriteConcern(WriteConcern::MAJORITY, 1000), - 'disableMD5' => true, ]); } @@ -79,7 +78,6 @@ public static function provideInvalidConstructorOptions() 'bucketName' => self::getInvalidStringValues(true), 'chunkSizeBytes' => self::getInvalidIntegerValues(true), 'codec' => self::getInvalidDocumentCodecValues(), - 'disableMD5' => self::getInvalidBooleanValues(true), 'readConcern' => self::getInvalidReadConcernValues(), 'readPreference' => self::getInvalidReadPreferenceValues(), 'typeMap' => self::getInvalidArrayValues(), @@ -810,46 +808,16 @@ public function testUploadingAnEmptyFile(): void [ 'projection' => [ 'length' => 1, - 'md5' => 1, '_id' => 0, ], ], ); - $expected = [ - 'length' => 0, - 'md5' => 'd41d8cd98f00b204e9800998ecf8427e', - ]; + $expected = ['length' => 0]; $this->assertSameDocument($expected, $fileDocument); } - public function testDisableMD5(): void - { - $options = ['disableMD5' => true]; - $id = $this->bucket->uploadFromStream('filename', self::createStream('data'), $options); - - $fileDocument = $this->filesCollection->findOne( - ['_id' => $id], - ); - - $this->assertArrayNotHasKey('md5', $fileDocument); - } - - public function testDisableMD5OptionInConstructor(): void - { - $options = ['disableMD5' => true]; - - $this->bucket = new Bucket($this->manager, $this->getDatabaseName(), $options); - $id = $this->bucket->uploadFromStream('filename', self::createStream('data')); - - $fileDocument = $this->filesCollection->findOne( - ['_id' => $id], - ); - - $this->assertArrayNotHasKey('md5', $fileDocument); - } - public function testUploadingFirstFileCreatesIndexes(): void { $this->bucket->uploadFromStream('filename', self::createStream('foo')); @@ -926,7 +894,7 @@ public function testDanglingOpenWritableStream(): void $client = MongoDB\Tests\FunctionalTestCase::createTestClient(); $database = $client->selectDatabase(getenv('MONGODB_DATABASE') ?: 'phplib_test'); $gridfs = $database->selectGridFSBucket(); - $stream = $gridfs->openUploadStream('hello.txt', ['disableMD5' => true]); + $stream = $gridfs->openUploadStream('hello.txt'); fwrite($stream, 'Hello MongoDB!'); PHP; @@ -1027,7 +995,7 @@ public function testResolveStreamContextForWrite(): void $this->assertArrayHasKey('filename', $context); $this->assertSame('filename', $context['filename']); $this->assertArrayHasKey('options', $context); - $this->assertSame(['chunkSizeBytes' => 261120, 'disableMD5' => false], $context['options']); + $this->assertSame(['chunkSizeBytes' => 261120], $context['options']); } /** diff --git a/tests/GridFS/WritableStreamFunctionalTest.php b/tests/GridFS/WritableStreamFunctionalTest.php index 98b654b8a..2b22baa53 100644 --- a/tests/GridFS/WritableStreamFunctionalTest.php +++ b/tests/GridFS/WritableStreamFunctionalTest.php @@ -45,7 +45,6 @@ public static function provideInvalidConstructorOptions() { return self::createOptionDataProvider([ 'chunkSizeBytes' => self::getInvalidIntegerValues(true), - 'disableMD5' => self::getInvalidBooleanValues(true), 'metadata' => self::getInvalidDocumentValues(), ]); } @@ -72,31 +71,4 @@ public function testWriteBytesAlwaysUpdatesFileSize(): void $stream->close(); $this->assertSame(1536, $stream->getSize()); } - - #[DataProvider('provideInputDataAndExpectedMD5')] - public function testWriteBytesCalculatesMD5($input, $expectedMD5): void - { - $stream = new WritableStream($this->collectionWrapper, 'filename'); - $stream->writeBytes($input); - $stream->close(); - - $fileDocument = $this->filesCollection->findOne( - ['_id' => $stream->getFile()->_id], - ['projection' => ['md5' => 1, '_id' => 0]], - ); - - $this->assertSameDocument(['md5' => $expectedMD5], $fileDocument); - } - - public static function provideInputDataAndExpectedMD5() - { - return [ - ['', 'd41d8cd98f00b204e9800998ecf8427e'], - ['foobar', '3858f62230ac3c915f300c664312c63f'], - [str_repeat('foobar', 43520), '88ff0e5fcb0acb27947d736b5d69cb73'], - [str_repeat('foobar', 43521), '8ff86511c95a06a611842ceb555d8454'], - [str_repeat('foobar', 87040), '45bfa1a9ec36728ee7338d15c5a30c13'], - [str_repeat('foobar', 87041), '95e78f624f8e745bcfd2d11691fa601e'], - ]; - } } diff --git a/tests/Model/BSONIteratorTest.php b/tests/Model/BSONIteratorTest.php index 749194c5d..bac019156 100644 --- a/tests/Model/BSONIteratorTest.php +++ b/tests/Model/BSONIteratorTest.php @@ -19,7 +19,7 @@ class BSONIteratorTest extends TestCase #[DataProvider('provideTypeMapOptionsAndExpectedDocuments')] public function testValidValues(?array $typeMap, array $expectedDocuments): void { - $binaryString = implode(array_map( + $binaryString = implode('', array_map( fn ($input) => (string) Document::fromPHP($input), [ ['_id' => 1, 'x' => ['foo' => 'bar']], diff --git a/tests/Model/CodecCursorFunctionalTest.php b/tests/Model/CodecCursorFunctionalTest.php index d2662a138..64c7268b7 100644 --- a/tests/Model/CodecCursorFunctionalTest.php +++ b/tests/Model/CodecCursorFunctionalTest.php @@ -4,15 +4,9 @@ use MongoDB\BSON\Int64; use MongoDB\Codec\DocumentCodec; -use MongoDB\Driver\CursorId; use MongoDB\Model\CodecCursor; use MongoDB\Tests\FunctionalTestCase; -use function restore_error_handler; -use function set_error_handler; - -use const E_DEPRECATED; -use const E_USER_DEPRECATED; use const E_USER_WARNING; class CodecCursorFunctionalTest extends FunctionalTestCase @@ -34,44 +28,6 @@ public function testSetTypeMap(): void $this->assertError(E_USER_WARNING, fn () => $codecCursor->setTypeMap(['root' => 'array'])); } - public function testGetIdReturnTypeWithoutArgument(): void - { - $collection = self::createTestClient()->selectCollection($this->getDatabaseName(), $this->getCollectionName()); - $cursor = $collection->find(); - - $codecCursor = CodecCursor::fromCursor($cursor, $this->createMock(DocumentCodec::class)); - - $deprecations = []; - - try { - $previousErrorHandler = set_error_handler( - function (...$args) use (&$previousErrorHandler, &$deprecations) { - $deprecations[] = $args; - - return true; - }, - E_USER_DEPRECATED | E_DEPRECATED, - ); - - $cursorId = $codecCursor->getId(); - } finally { - restore_error_handler(); - } - - self::assertInstanceOf(CursorId::class, $cursorId); - - // Expect 2 deprecations: 1 from CodecCursor, one from Cursor - self::assertCount(2, $deprecations); - self::assertSame( - 'The method "MongoDB\Model\CodecCursor::getId" will no longer return a "MongoDB\Driver\CursorId" instance in the future. Pass "true" as argument to change to the new behavior and receive a "MongoDB\BSON\Int64" instance instead.', - $deprecations[0][1], - ); - self::assertSame( - 'MongoDB\Driver\Cursor::getId(): The method "MongoDB\Driver\Cursor::getId" will no longer return a "MongoDB\Driver\CursorId" instance in the future. Pass "true" as argument to change to the new behavior and receive a "MongoDB\BSON\Int64" instance instead.', - $deprecations[1][1], - ); - } - public function testGetIdReturnTypeWithArgument(): void { $collection = self::createTestClient()->selectCollection($this->getDatabaseName(), $this->getCollectionName()); @@ -79,24 +35,6 @@ public function testGetIdReturnTypeWithArgument(): void $codecCursor = CodecCursor::fromCursor($cursor, $this->createMock(DocumentCodec::class)); - $deprecations = []; - - try { - $previousErrorHandler = set_error_handler( - function (...$args) use (&$previousErrorHandler, &$deprecations) { - $deprecations[] = $args; - - return true; - }, - E_USER_DEPRECATED | E_DEPRECATED, - ); - - $cursorId = $codecCursor->getId(true); - } finally { - restore_error_handler(); - } - - self::assertInstanceOf(Int64::class, $cursorId); - self::assertCount(0, $deprecations); + self::assertInstanceOf(Int64::class, $codecCursor->getId()); } } diff --git a/tests/Model/CollectionInfoTest.php b/tests/Model/CollectionInfoTest.php index 043023baa..9b926cfad 100644 --- a/tests/Model/CollectionInfoTest.php +++ b/tests/Model/CollectionInfoTest.php @@ -10,7 +10,7 @@ class CollectionInfoTest extends TestCase { public function testGetBasicInformation(): void { - $info = new CollectionInfo([ + $viewInfo = new CollectionInfo([ 'name' => 'foo', 'type' => 'view', 'options' => ['capped' => true, 'size' => 1_048_576], @@ -18,20 +18,27 @@ public function testGetBasicInformation(): void 'idIndex' => ['idIndex' => true], // Dummy option ]); - $this->assertSame('foo', $info->getName()); - $this->assertSame('foo', $info['name']); + $this->assertSame('foo', $viewInfo->getName()); + $this->assertSame('foo', $viewInfo['name']); + + $this->assertTrue($viewInfo->isView()); + $this->assertSame('view', $viewInfo['type']); - $this->assertSame('view', $info->getType()); - $this->assertSame('view', $info['type']); + $this->assertSame(['capped' => true, 'size' => 1_048_576], $viewInfo->getOptions()); + $this->assertSame(['capped' => true, 'size' => 1_048_576], $viewInfo['options']); - $this->assertSame(['capped' => true, 'size' => 1_048_576], $info->getOptions()); - $this->assertSame(['capped' => true, 'size' => 1_048_576], $info['options']); + $this->assertSame(['readOnly' => true], $viewInfo->getInfo()); + $this->assertSame(['readOnly' => true], $viewInfo['info']); - $this->assertSame(['readOnly' => true], $info->getInfo()); - $this->assertSame(['readOnly' => true], $info['info']); + $this->assertSame(['idIndex' => true], $viewInfo->getIdIndex()); + $this->assertSame(['idIndex' => true], $viewInfo['idIndex']); + + $collectionInfo = new CollectionInfo([ + 'name' => 'bar', + 'type' => 'collection', + ]); - $this->assertSame(['idIndex' => true], $info->getIdIndex()); - $this->assertSame(['idIndex' => true], $info['idIndex']); + $this->assertFalse($collectionInfo->isView()); } public function testMissingFields(): void diff --git a/tests/Model/IndexInfoFunctionalTest.php b/tests/Model/IndexInfoFunctionalTest.php index 33537382f..824507ec6 100644 --- a/tests/Model/IndexInfoFunctionalTest.php +++ b/tests/Model/IndexInfoFunctionalTest.php @@ -4,7 +4,6 @@ use MongoDB\Collection; use MongoDB\Tests\FunctionalTestCase; -use PHPUnit\Framework\Attributes\Group; class IndexInfoFunctionalTest extends FunctionalTestCase { @@ -33,27 +32,6 @@ public function testIs2dSphere(): void $this->assertEquals(3, $index['2dsphereIndexVersion']); } - #[Group('matrix-testing-exclude-server-5.0-driver-4.0')] - #[Group('matrix-testing-exclude-server-5.0-driver-4.2')] - #[Group('matrix-testing-exclude-server-5.0-driver-4.4')] - public function testIsGeoHaystack(): void - { - $this->skipIfGeoHaystackIndexIsNotSupported(); - - $indexName = $this->collection->createIndex(['pos' => 'geoHaystack', 'x' => 1], ['bucketSize' => 5]); - $result = $this->collection->listIndexes(); - - $result->rewind(); - $result->next(); - $index = $result->current(); - - $this->assertEquals($indexName, $index->getName()); - $this->assertDeprecated(function () use ($index): void { - $this->assertTrue($index->isGeoHaystack()); - }); - $this->assertEquals(5, $index['bucketSize']); - } - public function testIsText(): void { $indexName = $this->collection->createIndex(['x' => 'text']); diff --git a/tests/Model/IndexInfoTest.php b/tests/Model/IndexInfoTest.php index 30cb26aea..189b522bd 100644 --- a/tests/Model/IndexInfoTest.php +++ b/tests/Model/IndexInfoTest.php @@ -14,17 +14,12 @@ public function testBasicIndex(): void 'v' => 1, 'key' => ['x' => 1], 'name' => 'x_1', - 'ns' => 'foo.bar', ]); $this->assertSame(1, $info->getVersion()); $this->assertSame(['x' => 1], $info->getKey()); $this->assertSame('x_1', $info->getName()); - $this->assertSame('foo.bar', $info->getNamespace()); $this->assertFalse($info->is2dSphere()); - $this->assertDeprecated(function () use ($info): void { - $this->assertFalse($info->isGeoHaystack()); - }); $this->assertFalse($info->isSparse()); $this->assertFalse($info->isText()); $this->assertFalse($info->isTtl()); @@ -37,18 +32,13 @@ public function testSparseIndex(): void 'v' => 1, 'key' => ['y' => 1], 'name' => 'y_sparse', - 'ns' => 'foo.bar', 'sparse' => true, ]); $this->assertSame(1, $info->getVersion()); $this->assertSame(['y' => 1], $info->getKey()); $this->assertSame('y_sparse', $info->getName()); - $this->assertSame('foo.bar', $info->getNamespace()); $this->assertFalse($info->is2dSphere()); - $this->assertDeprecated(function () use ($info): void { - $this->assertFalse($info->isGeoHaystack()); - }); $this->assertTrue($info->isSparse()); $this->assertFalse($info->isText()); $this->assertFalse($info->isTtl()); @@ -61,18 +51,13 @@ public function testUniqueIndex(): void 'v' => 1, 'key' => ['z' => 1], 'name' => 'z_unique', - 'ns' => 'foo.bar', 'unique' => true, ]); $this->assertSame(1, $info->getVersion()); $this->assertSame(['z' => 1], $info->getKey()); $this->assertSame('z_unique', $info->getName()); - $this->assertSame('foo.bar', $info->getNamespace()); $this->assertFalse($info->is2dSphere()); - $this->assertDeprecated(function () use ($info): void { - $this->assertFalse($info->isGeoHaystack()); - }); $this->assertFalse($info->isSparse()); $this->assertFalse($info->isText()); $this->assertFalse($info->isTtl()); @@ -85,18 +70,13 @@ public function testTtlIndex(): void 'v' => 1, 'key' => ['z' => 1], 'name' => 'z_unique', - 'ns' => 'foo.bar', 'expireAfterSeconds' => 100, ]); $this->assertSame(1, $info->getVersion()); $this->assertSame(['z' => 1], $info->getKey()); $this->assertSame('z_unique', $info->getName()); - $this->assertSame('foo.bar', $info->getNamespace()); $this->assertFalse($info->is2dSphere()); - $this->assertDeprecated(function () use ($info): void { - $this->assertFalse($info->isGeoHaystack()); - }); $this->assertFalse($info->isSparse()); $this->assertFalse($info->isText()); $this->assertTrue($info->isTtl()); @@ -111,7 +91,6 @@ public function testDebugInfo(): void 'v' => 1, 'key' => ['x' => 1], 'name' => 'x_1', - 'ns' => 'foo.bar', ]; $info = new IndexInfo($expectedInfo); @@ -124,7 +103,6 @@ public function testImplementsArrayAccess(): void 'v' => 1, 'key' => ['x' => 1], 'name' => 'x_1', - 'ns' => 'foo.bar', ]); $this->assertInstanceOf('ArrayAccess', $info); @@ -138,7 +116,6 @@ public function testOffsetSetCannotBeCalled(): void 'v' => 1, 'key' => ['x' => 1], 'name' => 'x_1', - 'ns' => 'foo.bar', ]); $this->expectException(BadMethodCallException::class); @@ -152,7 +129,6 @@ public function testOffsetUnsetCannotBeCalled(): void 'v' => 1, 'key' => ['x' => 1], 'name' => 'x_1', - 'ns' => 'foo.bar', ]); $this->expectException(BadMethodCallException::class); @@ -166,40 +142,12 @@ public function testIs2dSphere(): void 'v' => 2, 'key' => ['pos' => '2dsphere'], 'name' => 'pos_2dsphere', - 'ns' => 'foo.bar', ]); $this->assertSame(2, $info->getVersion()); $this->assertSame(['pos' => '2dsphere'], $info->getKey()); $this->assertSame('pos_2dsphere', $info->getName()); - $this->assertSame('foo.bar', $info->getNamespace()); $this->assertTrue($info->is2dSphere()); - $this->assertDeprecated(function () use ($info): void { - $this->assertFalse($info->isGeoHaystack()); - }); - $this->assertFalse($info->isSparse()); - $this->assertFalse($info->isText()); - $this->assertFalse($info->isTtl()); - $this->assertFalse($info->isUnique()); - } - - public function testIsGeoHaystack(): void - { - $info = new IndexInfo([ - 'v' => 2, - 'key' => ['pos2' => 'geoHaystack', 'x' => 1], - 'name' => 'pos2_geoHaystack_x_1', - 'ns' => 'foo.bar', - ]); - - $this->assertSame(2, $info->getVersion()); - $this->assertSame(['pos2' => 'geoHaystack', 'x' => 1], $info->getKey()); - $this->assertSame('pos2_geoHaystack_x_1', $info->getName()); - $this->assertSame('foo.bar', $info->getNamespace()); - $this->assertFalse($info->is2dSphere()); - $this->assertDeprecated(function () use ($info): void { - $this->assertTrue($info->isGeoHaystack()); - }); $this->assertFalse($info->isSparse()); $this->assertFalse($info->isText()); $this->assertFalse($info->isTtl()); @@ -212,17 +160,12 @@ public function testIsText(): void 'v' => 2, 'key' => ['_fts' => 'text', '_ftsx' => 1], 'name' => 'title_text_description_text', - 'ns' => 'foo.bar', ]); $this->assertSame(2, $info->getVersion()); $this->assertSame(['_fts' => 'text', '_ftsx' => 1], $info->getKey()); $this->assertSame('title_text_description_text', $info->getName()); - $this->assertSame('foo.bar', $info->getNamespace()); $this->assertFalse($info->is2dSphere()); - $this->assertDeprecated(function () use ($info): void { - $this->assertFalse($info->isGeoHaystack()); - }); $this->assertFalse($info->isSparse()); $this->assertTrue($info->isText()); $this->assertFalse($info->isTtl()); diff --git a/tests/Operation/BulkWriteFunctionalTest.php b/tests/Operation/BulkWriteFunctionalTest.php index 1322589f4..9fce1c8ac 100644 --- a/tests/Operation/BulkWriteFunctionalTest.php +++ b/tests/Operation/BulkWriteFunctionalTest.php @@ -7,8 +7,8 @@ use MongoDB\BulkWriteResult; use MongoDB\Collection; use MongoDB\Driver\BulkWrite as Bulk; +use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\WriteConcern; -use MongoDB\Exception\BadMethodCallException; use MongoDB\Model\BSONDocument; use MongoDB\Operation\BulkWrite; use MongoDB\Tests\CommandObserver; @@ -318,48 +318,48 @@ public function testUnacknowledgedWriteConcern() #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesDeletedCount(BulkWriteResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getDeletedCount(); } #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesInsertCount(BulkWriteResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getInsertedCount(); } #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesMatchedCount(BulkWriteResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getMatchedCount(); } #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesModifiedCount(BulkWriteResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getModifiedCount(); } #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesUpsertedCount(BulkWriteResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getUpsertedCount(); } #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesUpsertedIds(BulkWriteResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getUpsertedIds(); } diff --git a/tests/Operation/CreateCollectionTest.php b/tests/Operation/CreateCollectionTest.php index 4012a7ed5..4ec92b32f 100644 --- a/tests/Operation/CreateCollectionTest.php +++ b/tests/Operation/CreateCollectionTest.php @@ -25,14 +25,12 @@ public function testConstructorOptionTypeChecks(array $options): void public static function provideInvalidConstructorOptions() { return self::createOptionDataProvider([ - 'autoIndexId' => self::getInvalidBooleanValues(), 'capped' => self::getInvalidBooleanValues(), 'changeStreamPreAndPostImages' => self::getInvalidDocumentValues(), 'clusteredIndex' => self::getInvalidDocumentValues(), 'collation' => self::getInvalidDocumentValues(), 'encryptedFields' => self::getInvalidDocumentValues(), 'expireAfterSeconds' => self::getInvalidIntegerValues(), - 'flags' => self::getInvalidIntegerValues(), 'indexOptionDefaults' => self::getInvalidDocumentValues(), 'max' => self::getInvalidIntegerValues(), 'maxTimeMS' => self::getInvalidIntegerValues(), @@ -41,7 +39,6 @@ public static function provideInvalidConstructorOptions() 'size' => self::getInvalidIntegerValues(), 'storageEngine' => self::getInvalidDocumentValues(), 'timeseries' => self::getInvalidDocumentValues(), - 'typeMap' => self::getInvalidArrayValues(), 'validationAction' => self::getInvalidStringValues(), 'validationLevel' => self::getInvalidStringValues(), 'validator' => self::getInvalidDocumentValues(), @@ -49,22 +46,4 @@ public static function provideInvalidConstructorOptions() 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } - - public function testAutoIndexIdOptionIsDeprecated(): void - { - $this->assertDeprecated(function (): void { - new CreateCollection($this->getDatabaseName(), $this->getCollectionName(), ['autoIndexId' => true]); - }); - - $this->assertDeprecated(function (): void { - new CreateCollection($this->getDatabaseName(), $this->getCollectionName(), ['autoIndexId' => false]); - }); - } - - public function testFlagsOptionIsDeprecated(): void - { - $this->assertDeprecated(function (): void { - new CreateCollection($this->getDatabaseName(), $this->getCollectionName(), ['flags' => CreateCollection::USE_POWER_OF_2_SIZES]); - }); - } } diff --git a/tests/Operation/CreateEncryptedCollectionFunctionalTest.php b/tests/Operation/CreateEncryptedCollectionFunctionalTest.php index 8d4978639..7c0cb644f 100644 --- a/tests/Operation/CreateEncryptedCollectionFunctionalTest.php +++ b/tests/Operation/CreateEncryptedCollectionFunctionalTest.php @@ -27,10 +27,6 @@ public function setUp(): void $this->skipIfClientSideEncryptionIsNotSupported(); - if (! static::isCryptSharedLibAvailable() && ! static::isMongocryptdAvailable()) { - $this->markTestSkipped('Neither crypt_shared nor mongocryptd are available'); - } - if ($this->isStandalone()) { $this->markTestSkipped('Queryable Encryption requires replica sets'); } @@ -64,11 +60,10 @@ public function testCreateDataKeysNopIfFieldsIsMissing($input, array $expectedOu ['encryptedFields' => $input], ); - $operation->createDataKeys( + $encryptedFieldsOutput = $operation->createDataKeys( $this->clientEncryption, 'local', null, - $encryptedFieldsOutput, ); $this->assertSame($expectedOutput, $encryptedFieldsOutput); @@ -95,11 +90,10 @@ public function testCreateDataKeysNopIfFieldsHasInvalidType($input, array $expec ['encryptedFields' => $input], ); - $operation->createDataKeys( + $encryptedFieldsOutput = $operation->createDataKeys( $this->clientEncryption, 'local', null, - $encryptedFieldsOutput, ); $this->assertSame($expectedOutput, $encryptedFieldsOutput); @@ -126,11 +120,10 @@ public function testCreateDataKeysSkipsNonDocumentFields($input, array $expected ['encryptedFields' => $input], ); - $operation->createDataKeys( + $encryptedFieldsOutput = $operation->createDataKeys( $this->clientEncryption, 'local', null, - $encryptedFieldsOutput, ); $this->assertSame($expectedOutput, $encryptedFieldsOutput); @@ -159,11 +152,10 @@ public function testCreateDataKeysDoesNotModifyOriginalEncryptedFieldsOption(): ['encryptedFields' => $originalEncryptedFields], ); - $operation->createDataKeys( + $modifiedEncryptedFields = $operation->createDataKeys( $this->clientEncryption, 'local', null, - $modifiedEncryptedFields, ); $this->assertSame($originalField, $originalEncryptedFields->fields[0]); @@ -181,11 +173,10 @@ public function testEncryptedFieldsDocuments($input): void ['encryptedFields' => $input], ); - $operation->createDataKeys( + $modifiedEncryptedFields = $operation->createDataKeys( $this->clientEncryption, 'local', null, - $modifiedEncryptedFields, ); $this->assertInstanceOf(Binary::class, $modifiedEncryptedFields['fields'][0]['keyId'] ?? null); diff --git a/tests/Operation/DeleteFunctionalTest.php b/tests/Operation/DeleteFunctionalTest.php index bf59b8822..2aceafa1b 100644 --- a/tests/Operation/DeleteFunctionalTest.php +++ b/tests/Operation/DeleteFunctionalTest.php @@ -5,8 +5,8 @@ use MongoDB\Collection; use MongoDB\DeleteResult; use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\WriteConcern; -use MongoDB\Exception\BadMethodCallException; use MongoDB\Exception\UnsupportedException; use MongoDB\Operation\Delete; use MongoDB\Tests\CommandObserver; @@ -138,8 +138,8 @@ public function testUnacknowledgedWriteConcern() #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesDeletedCount(DeleteResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getDeletedCount(); } diff --git a/tests/Operation/DropCollectionFunctionalTest.php b/tests/Operation/DropCollectionFunctionalTest.php index a7d317e67..3d2015521 100644 --- a/tests/Operation/DropCollectionFunctionalTest.php +++ b/tests/Operation/DropCollectionFunctionalTest.php @@ -36,9 +36,8 @@ public function testDropExistingCollection(): void $this->assertEquals(1, $writeResult->getInsertedCount()); $operation = new DropCollection($this->getDatabaseName(), $this->getCollectionName()); - $commandResult = $operation->execute($server); + $operation->execute($server); - $this->assertCommandSucceeded($commandResult); $this->assertCollectionDoesNotExist($this->getCollectionName()); } @@ -48,11 +47,7 @@ public function testDropNonexistentCollection(): void $this->assertCollectionDoesNotExist($this->getCollectionName()); $operation = new DropCollection($this->getDatabaseName(), $this->getCollectionName()); - $commandResult = $operation->execute($this->getPrimaryServer()); - - /* Avoid inspecting the result document as mongos returns {ok:1.0}, - * which is inconsistent from the expected mongod response of {ok:0}. */ - $this->assertIsObject($commandResult); + $operation->execute($this->getPrimaryServer()); } public function testSessionOption(): void diff --git a/tests/Operation/DropCollectionTest.php b/tests/Operation/DropCollectionTest.php index 19dc28bd4..5aa580520 100644 --- a/tests/Operation/DropCollectionTest.php +++ b/tests/Operation/DropCollectionTest.php @@ -19,7 +19,6 @@ public static function provideInvalidConstructorOptions() { return self::createOptionDataProvider([ 'session' => self::getInvalidSessionValues(), - 'typeMap' => self::getInvalidArrayValues(), 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/DropDatabaseTest.php b/tests/Operation/DropDatabaseTest.php index cd58e6356..c2d950223 100644 --- a/tests/Operation/DropDatabaseTest.php +++ b/tests/Operation/DropDatabaseTest.php @@ -19,7 +19,6 @@ public static function provideInvalidConstructorOptions() { return self::createOptionDataProvider([ 'session' => self::getInvalidSessionValues(), - 'typeMap' => self::getInvalidArrayValues(), 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/DropIndexesFunctionalTest.php b/tests/Operation/DropIndexesFunctionalTest.php index d54c8544a..f273cb216 100644 --- a/tests/Operation/DropIndexesFunctionalTest.php +++ b/tests/Operation/DropIndexesFunctionalTest.php @@ -48,7 +48,7 @@ public function testDropOneIndexByName(): void $this->assertIndexExists('x_1'); $operation = new DropIndexes($this->getDatabaseName(), $this->getCollectionName(), 'x_1'); - $this->assertCommandSucceeded($operation->execute($this->getPrimaryServer())); + $operation->execute($this->getPrimaryServer()); $operation = new ListIndexes($this->getDatabaseName(), $this->getCollectionName()); $indexes = $operation->execute($this->getPrimaryServer()); @@ -76,7 +76,7 @@ public function testDropAllIndexesByWildcard(): void $this->assertIndexExists('y_1'); $operation = new DropIndexes($this->getDatabaseName(), $this->getCollectionName(), '*'); - $this->assertCommandSucceeded($operation->execute($this->getPrimaryServer())); + $operation->execute($this->getPrimaryServer()); $operation = new ListIndexes($this->getDatabaseName(), $this->getCollectionName()); $indexes = $operation->execute($this->getPrimaryServer()); @@ -108,7 +108,7 @@ public function testDropByIndexInfo(): void $this->assertIndexExists('x_1'); $operation = new DropIndexes($this->getDatabaseName(), $this->getCollectionName(), $info); - $this->assertCommandSucceeded($operation->execute($this->getPrimaryServer())); + $operation->execute($this->getPrimaryServer()); $operation = new ListIndexes($this->getDatabaseName(), $this->getCollectionName()); $indexes = $operation->execute($this->getPrimaryServer()); diff --git a/tests/Operation/DropIndexesTest.php b/tests/Operation/DropIndexesTest.php index cc2aff405..214330512 100644 --- a/tests/Operation/DropIndexesTest.php +++ b/tests/Operation/DropIndexesTest.php @@ -26,7 +26,6 @@ public static function provideInvalidConstructorOptions() return self::createOptionDataProvider([ 'maxTimeMS' => self::getInvalidIntegerValues(), 'session' => self::getInvalidSessionValues(), - 'typeMap' => self::getInvalidArrayValues(), 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/ExplainFunctionalTest.php b/tests/Operation/ExplainFunctionalTest.php index a7696eeeb..772479deb 100644 --- a/tests/Operation/ExplainFunctionalTest.php +++ b/tests/Operation/ExplainFunctionalTest.php @@ -158,30 +158,6 @@ function (array $event): void { ); } - public function testFindModifiers(): void - { - $this->createFixtures(3); - - $operation = new Find( - $this->getDatabaseName(), - $this->getCollectionName(), - [], - ['modifiers' => ['$orderby' => ['_id' => 1]]], - ); - - (new CommandObserver())->observe( - function () use ($operation): void { - $explainOperation = new Explain($this->getDatabaseName(), $operation, ['typeMap' => ['root' => 'array', 'document' => 'array']]); - $explainOperation->execute($this->getPrimaryServer()); - }, - function (array $event): void { - $command = $event['started']->getCommand(); - $this->assertObjectHasProperty('sort', $command->explain); - $this->assertObjectNotHasProperty('modifiers', $command->explain); - }, - ); - } - #[DataProvider('provideVerbosityInformation')] public function testFindOne($verbosity, $executionStatsExpected, $allPlansExecutionExpected): void { @@ -331,8 +307,6 @@ public function testUpdateOne($verbosity, $executionStatsExpected, $allPlansExec public function testAggregate(): void { - $this->skipIfServerVersion('<', '4.0.0', 'Explaining aggregate command requires server version >= 4.0'); - $this->createFixtures(3); // Use a $sort stage to ensure the aggregate does not get optimised to a query diff --git a/tests/Operation/FindFunctionalTest.php b/tests/Operation/FindFunctionalTest.php index 81745c23f..0927b29fb 100644 --- a/tests/Operation/FindFunctionalTest.php +++ b/tests/Operation/FindFunctionalTest.php @@ -2,10 +2,8 @@ namespace MongoDB\Tests\Operation; -use MongoDB\BSON\Document; use MongoDB\Driver\BulkWrite; use MongoDB\Driver\ReadPreference; -use MongoDB\Model\BSONDocument; use MongoDB\Operation\CreateIndexes; use MongoDB\Operation\Find; use MongoDB\Tests\CommandObserver; @@ -14,7 +12,6 @@ use PHPUnit\Framework\Attributes\DataProvider; use stdClass; -use function is_array; use function microtime; class FindFunctionalTest extends FunctionalTestCase @@ -38,45 +35,6 @@ function (array $event) use ($expectedQuery): void { ); } - #[DataProvider('provideModifierDocuments')] - public function testModifierDocuments($modifiers, stdClass $expectedSort): void - { - (new CommandObserver())->observe( - function () use ($modifiers): void { - // @todo revert this lines after PHPC-2457 - if (is_array($modifiers)) { - $modifiers = [...$modifiers]; - } - - $operation = new Find( - $this->getDatabaseName(), - $this->getCollectionName(), - [], - ['modifiers' => $modifiers], - ); - - $this->assertDeprecated( - fn () => $operation->execute($this->getPrimaryServer()), - ); - }, - function (array $event) use ($expectedSort): void { - $this->assertEquals($expectedSort, $event['started']->getCommand()->sort ?? null); - }, - ); - } - - public static function provideModifierDocuments(): array - { - $expectedSort = (object) ['x' => 1]; - - return [ - 'array' => [['$orderby' => ['x' => 1]], $expectedSort], - 'object' => [(object) ['$orderby' => ['x' => 1]], $expectedSort], - 'Serializable' => [new BSONDocument(['$orderby' => ['x' => 1]]), $expectedSort], - 'Document' => [Document::fromPHP(['$orderby' => ['x' => 1]]), $expectedSort], - ]; - } - public function testDefaultReadConcernIsOmitted(): void { (new CommandObserver())->observe( diff --git a/tests/Operation/FindTest.php b/tests/Operation/FindTest.php index d7eccf7f3..97cc1d388 100644 --- a/tests/Operation/FindTest.php +++ b/tests/Operation/FindTest.php @@ -38,11 +38,8 @@ public static function provideInvalidConstructorOptions() 'limit' => self::getInvalidIntegerValues(), 'max' => self::getInvalidDocumentValues(), 'maxAwaitTimeMS' => self::getInvalidIntegerValues(), - 'maxScan' => self::getInvalidIntegerValues(), 'maxTimeMS' => self::getInvalidIntegerValues(), 'min' => self::getInvalidDocumentValues(), - 'modifiers' => self::getInvalidDocumentValues(), - 'oplogReplay' => self::getInvalidBooleanValues(), 'projection' => self::getInvalidDocumentValues(), 'readConcern' => self::getInvalidReadConcernValues(), 'readPreference' => self::getInvalidReadPreferenceValues(), @@ -50,7 +47,6 @@ public static function provideInvalidConstructorOptions() 'session' => self::getInvalidSessionValues(), 'showRecordId' => self::getInvalidBooleanValues(), 'skip' => self::getInvalidIntegerValues(), - 'snapshot' => self::getInvalidBooleanValues(), 'sort' => self::getInvalidDocumentValues(), 'typeMap' => self::getInvalidArrayValues(), ]); @@ -70,7 +66,6 @@ public static function provideInvalidConstructorCursorTypeOptions() public function testExplainableCommandDocument(): void { - // all options except deprecated "snapshot" and "maxScan" $options = [ 'allowDiskUse' => true, 'allowPartialResults' => true, @@ -83,7 +78,6 @@ public function testExplainableCommandDocument(): void 'maxTimeMS' => 100, 'min' => ['x' => 10], 'noCursorTimeout' => true, - 'oplogReplay' => true, 'projection' => ['_id' => 0], 'readConcern' => new ReadConcern(ReadConcern::LOCAL), 'returnKey' => true, @@ -94,7 +88,6 @@ public function testExplainableCommandDocument(): void // Intentionally omitted options 'cursorType' => Find::NON_TAILABLE, 'maxAwaitTimeMS' => 500, - 'modifiers' => ['foo' => 'bar'], 'readPreference' => new ReadPreference(ReadPreference::SECONDARY_PREFERRED), 'typeMap' => ['root' => 'array'], ]; @@ -111,7 +104,6 @@ public function testExplainableCommandDocument(): void 'limit' => 15, 'maxTimeMS' => 100, 'noCursorTimeout' => true, - 'oplogReplay' => true, 'projection' => ['_id' => 0], 'readConcern' => new ReadConcern(ReadConcern::LOCAL), 'returnKey' => true, diff --git a/tests/Operation/InsertManyFunctionalTest.php b/tests/Operation/InsertManyFunctionalTest.php index 68de6dfce..d8e520c98 100644 --- a/tests/Operation/InsertManyFunctionalTest.php +++ b/tests/Operation/InsertManyFunctionalTest.php @@ -5,8 +5,8 @@ use MongoDB\BSON\Document; use MongoDB\BSON\ObjectId; use MongoDB\Collection; +use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\WriteConcern; -use MongoDB\Exception\BadMethodCallException; use MongoDB\InsertManyResult; use MongoDB\Model\BSONDocument; use MongoDB\Operation\InsertMany; @@ -193,8 +193,8 @@ public function testUnacknowledgedWriteConcern() #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesInsertedCount(InsertManyResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getInsertedCount(); } diff --git a/tests/Operation/InsertOneFunctionalTest.php b/tests/Operation/InsertOneFunctionalTest.php index 9cef81f04..204d8bacc 100644 --- a/tests/Operation/InsertOneFunctionalTest.php +++ b/tests/Operation/InsertOneFunctionalTest.php @@ -5,8 +5,8 @@ use MongoDB\BSON\Document; use MongoDB\BSON\ObjectId; use MongoDB\Collection; +use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\WriteConcern; -use MongoDB\Exception\BadMethodCallException; use MongoDB\InsertOneResult; use MongoDB\Model\BSONDocument; use MongoDB\Operation\InsertOne; @@ -183,8 +183,8 @@ public function testUnacknowledgedWriteConcern() #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesInsertedCount(InsertOneResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getInsertedCount(); } diff --git a/tests/Operation/ListCollectionsFunctionalTest.php b/tests/Operation/ListCollectionsFunctionalTest.php index 259e6cbdb..ed8cb9b14 100644 --- a/tests/Operation/ListCollectionsFunctionalTest.php +++ b/tests/Operation/ListCollectionsFunctionalTest.php @@ -2,8 +2,8 @@ namespace MongoDB\Tests\Operation; +use Iterator; use MongoDB\Model\CollectionInfo; -use MongoDB\Model\CollectionInfoIterator; use MongoDB\Operation\DropDatabase; use MongoDB\Operation\InsertOne; use MongoDB\Operation\ListCollections; @@ -26,7 +26,7 @@ public function testListCollectionsForNewlyCreatedDatabase(): void $operation = new ListCollections($this->getDatabaseName(), ['filter' => ['name' => $this->getCollectionName()]]); $collections = $operation->execute($server); - $this->assertInstanceOf(CollectionInfoIterator::class, $collections); + $this->assertInstanceOf(Iterator::class, $collections); $this->assertCount(1, $collections); @@ -51,12 +51,14 @@ public function testIdIndexAndInfo(): void $operation = new ListCollections($this->getDatabaseName(), ['filter' => ['name' => $this->getCollectionName()]]); $collections = $operation->execute($server); - $this->assertInstanceOf(CollectionInfoIterator::class, $collections); + $this->assertInstanceOf(Iterator::class, $collections); foreach ($collections as $collection) { $this->assertInstanceOf(CollectionInfo::class, $collection); $this->assertArrayHasKey('readOnly', $collection['info']); - $this->assertEquals(['v' => 2, 'key' => ['_id' => 1], 'name' => '_id_', 'ns' => $this->getNamespace()], $collection['idIndex']); + // Use assertMatchesDocument as MongoDB 4.0 and 4.2 include a ns field + // TODO: change to assertEquals when dropping support for MongoDB 4.2 + $this->assertMatchesDocument(['v' => 2, 'key' => ['_id' => 1], 'name' => '_id_'], $collection['idIndex']); } } diff --git a/tests/Operation/ListDatabasesFunctionalTest.php b/tests/Operation/ListDatabasesFunctionalTest.php index 0a6271827..f881b34dd 100644 --- a/tests/Operation/ListDatabasesFunctionalTest.php +++ b/tests/Operation/ListDatabasesFunctionalTest.php @@ -2,8 +2,8 @@ namespace MongoDB\Tests\Operation; +use Iterator; use MongoDB\Model\DatabaseInfo; -use MongoDB\Model\DatabaseInfoIterator; use MongoDB\Operation\InsertOne; use MongoDB\Operation\ListDatabases; use MongoDB\Tests\CommandObserver; @@ -30,7 +30,7 @@ function (array $event): void { }, ); - $this->assertInstanceOf(DatabaseInfoIterator::class, $databases); + $this->assertInstanceOf(Iterator::class, $databases); foreach ($databases as $database) { $this->assertInstanceOf(DatabaseInfo::class, $database); @@ -65,7 +65,7 @@ public function testFilterOption(): void $operation = new ListDatabases(['filter' => ['name' => $this->getDatabaseName()]]); $databases = $operation->execute($server); - $this->assertInstanceOf(DatabaseInfoIterator::class, $databases); + $this->assertInstanceOf(Iterator::class, $databases); $this->assertCount(1, $databases); diff --git a/tests/Operation/ListIndexesFunctionalTest.php b/tests/Operation/ListIndexesFunctionalTest.php index dcdcac71c..f5a2f6bc2 100644 --- a/tests/Operation/ListIndexesFunctionalTest.php +++ b/tests/Operation/ListIndexesFunctionalTest.php @@ -2,8 +2,8 @@ namespace MongoDB\Tests\Operation; +use Iterator; use MongoDB\Model\IndexInfo; -use MongoDB\Model\IndexInfoIterator; use MongoDB\Operation\InsertOne; use MongoDB\Operation\ListIndexes; use MongoDB\Tests\CommandObserver; @@ -21,14 +21,13 @@ public function testListIndexesForNewlyCreatedCollection(): void $operation = new ListIndexes($this->getDatabaseName(), $this->getCollectionName()); $indexes = $operation->execute($this->getPrimaryServer()); - $this->assertInstanceOf(IndexInfoIterator::class, $indexes); + $this->assertInstanceOf(Iterator::class, $indexes); $this->assertCount(1, $indexes); foreach ($indexes as $index) { $this->assertInstanceOf(IndexInfo::class, $index); $this->assertEquals(['_id' => 1], $index->getKey()); - $this->assertSame($this->getNamespace(), $index->getNamespace()); } } diff --git a/tests/Operation/MapReduceFunctionalTest.php b/tests/Operation/MapReduceFunctionalTest.php deleted file mode 100644 index 5dbe45f3a..000000000 --- a/tests/Operation/MapReduceFunctionalTest.php +++ /dev/null @@ -1,314 +0,0 @@ -createCollection($this->getDatabaseName(), $this->getCollectionName()); - - (new CommandObserver())->observe( - function (): void { - $operation = new MapReduce( - $this->getDatabaseName(), - $this->getCollectionName(), - new Javascript('function() { emit(this.x, this.y); }'), - new Javascript('function(key, values) { return Array.sum(values); }'), - ['inline' => 1], - ['readConcern' => $this->createDefaultReadConcern()], - ); - - $operation->execute($this->getPrimaryServer()); - }, - function (array $event): void { - $this->assertObjectNotHasProperty('readConcern', $event['started']->getCommand()); - }, - ); - } - - public function testDefaultWriteConcernIsOmitted(): void - { - // Collection must exist for mapReduce command - $this->createCollection($this->getDatabaseName(), $this->getCollectionName()); - $this->dropCollection($this->getDatabaseName(), $this->getCollectionName() . '.output'); - - (new CommandObserver())->observe( - function (): void { - $operation = new MapReduce( - $this->getDatabaseName(), - $this->getCollectionName(), - new Javascript('function() { emit(this.x, this.y); }'), - new Javascript('function(key, values) { return Array.sum(values); }'), - $this->getCollectionName() . '.output', - ['writeConcern' => $this->createDefaultWriteConcern()], - ); - - $operation->execute($this->getPrimaryServer()); - }, - function (array $event): void { - $this->assertObjectNotHasProperty('writeConcern', $event['started']->getCommand()); - }, - ); - } - - public function testFinalize(): void - { - $this->createFixtures(3); - - $map = new Javascript('function() { emit(this.x, this.y); }'); - $reduce = new Javascript('function(key, values) { return Array.sum(values); }'); - $out = ['inline' => 1]; - $finalize = new Javascript('function(key, reducedValue) { return reducedValue; }'); - - $operation = new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out, ['finalize' => $finalize]); - $result = $operation->execute($this->getPrimaryServer()); - - $this->assertNotNull($result); - } - - public function testResult(): void - { - $this->createFixtures(3); - - $map = new Javascript('function() { emit(this.x, this.y); }'); - $reduce = new Javascript('function(key, values) { return Array.sum(values); }'); - $out = ['inline' => 1]; - - $operation = new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out); - $result = $operation->execute($this->getPrimaryServer()); - - $this->assertInstanceOf(MapReduceResult::class, $result); - - if (version_compare($this->getServerVersion(), '4.3.0', '<')) { - $this->assertGreaterThanOrEqual(0, $result->getExecutionTimeMS()); - $this->assertNotEmpty($result->getCounts()); - } - } - - public function testResultIncludesTimingWithVerboseOption(): void - { - $this->skipIfServerVersion('>=', '4.3.0', 'mapReduce statistics are no longer exposed'); - - $this->createFixtures(3); - - $map = new Javascript('function() { emit(this.x, this.y); }'); - $reduce = new Javascript('function(key, values) { return Array.sum(values); }'); - $out = ['inline' => 1]; - - $operation = new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out, ['verbose' => true]); - $result = $operation->execute($this->getPrimaryServer()); - - $this->assertInstanceOf(MapReduceResult::class, $result); - $this->assertGreaterThanOrEqual(0, $result->getExecutionTimeMS()); - $this->assertNotEmpty($result->getCounts()); - $this->assertNotEmpty($result->getTiming()); - } - - public function testResultDoesNotIncludeTimingWithoutVerboseOption(): void - { - $this->skipIfServerVersion('>=', '4.3.0', 'mapReduce statistics are no longer exposed'); - - $this->createFixtures(3); - - $map = new Javascript('function() { emit(this.x, this.y); }'); - $reduce = new Javascript('function(key, values) { return Array.sum(values); }'); - $out = ['inline' => 1]; - - $operation = new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out, ['verbose' => false]); - $result = $operation->execute($this->getPrimaryServer()); - - $this->assertInstanceOf(MapReduceResult::class, $result); - $this->assertGreaterThanOrEqual(0, $result->getExecutionTimeMS()); - $this->assertNotEmpty($result->getCounts()); - $this->assertEmpty($result->getTiming()); - } - - public function testSessionOption(): void - { - $this->createFixtures(3); - - (new CommandObserver())->observe( - function (): void { - $operation = new MapReduce( - $this->getDatabaseName(), - $this->getCollectionName(), - new Javascript('function() { emit(this.x, this.y); }'), - new Javascript('function(key, values) { return Array.sum(values); }'), - ['inline' => 1], - ['session' => $this->createSession()], - ); - - $operation->execute($this->getPrimaryServer()); - }, - function (array $event): void { - $this->assertObjectHasProperty('lsid', $event['started']->getCommand()); - }, - ); - } - - public function testBypassDocumentValidationSetWhenTrue(): void - { - $this->createFixtures(1); - - (new CommandObserver())->observe( - function (): void { - $operation = new MapReduce( - $this->getDatabaseName(), - $this->getCollectionName(), - new Javascript('function() { emit(this.x, this.y); }'), - new Javascript('function(key, values) { return Array.sum(values); }'), - ['inline' => 1], - ['bypassDocumentValidation' => true], - ); - - $operation->execute($this->getPrimaryServer()); - }, - function (array $event): void { - $this->assertObjectHasProperty('bypassDocumentValidation', $event['started']->getCommand()); - $this->assertEquals(true, $event['started']->getCommand()->bypassDocumentValidation); - }, - ); - } - - public function testBypassDocumentValidationUnsetWhenFalse(): void - { - $this->createFixtures(1); - - (new CommandObserver())->observe( - function (): void { - $operation = new MapReduce( - $this->getDatabaseName(), - $this->getCollectionName(), - new Javascript('function() { emit(this.x, this.y); }'), - new Javascript('function(key, values) { return Array.sum(values); }'), - ['inline' => 1], - ['bypassDocumentValidation' => false], - ); - - $operation->execute($this->getPrimaryServer()); - }, - function (array $event): void { - $this->assertObjectNotHasProperty('bypassDocumentValidation', $event['started']->getCommand()); - }, - ); - } - - #[DataProvider('provideTypeMapOptionsAndExpectedDocuments')] - public function testTypeMapOptionWithInlineResults(?array $typeMap, array $expectedDocuments): void - { - $this->createFixtures(3); - - $map = new Javascript('function() { emit(this.x, this.y); }'); - $reduce = new Javascript('function(key, values) { return Array.sum(values); }'); - $out = ['inline' => 1]; - - $operation = new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out, ['typeMap' => $typeMap]); - $results = iterator_to_array($operation->execute($this->getPrimaryServer())); - - $this->assertEquals($this->sortResults($expectedDocuments), $this->sortResults($results)); - } - - public static function provideTypeMapOptionsAndExpectedDocuments() - { - return [ - [ - null, - [ - (object) ['_id' => 1, 'value' => 3], - (object) ['_id' => 2, 'value' => 6], - (object) ['_id' => 3, 'value' => 9], - ], - ], - [ - ['root' => 'array'], - [ - ['_id' => 1, 'value' => 3], - ['_id' => 2, 'value' => 6], - ['_id' => 3, 'value' => 9], - ], - ], - [ - ['root' => 'object'], - [ - (object) ['_id' => 1, 'value' => 3], - (object) ['_id' => 2, 'value' => 6], - (object) ['_id' => 3, 'value' => 9], - ], - ], - ]; - } - - #[DataProvider('provideTypeMapOptionsAndExpectedDocuments')] - public function testTypeMapOptionWithOutputCollection(?array $typeMap, array $expectedDocuments): void - { - $this->createFixtures(3); - - $map = new Javascript('function() { emit(this.x, this.y); }'); - $reduce = new Javascript('function(key, values) { return Array.sum(values); }'); - $out = $this->getCollectionName() . '.output'; - $this->dropCollection($this->getDatabaseName(), $out); - - $operation = new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out, ['typeMap' => $typeMap]); - $results = iterator_to_array($operation->execute($this->getPrimaryServer())); - - $this->assertEquals($this->sortResults($expectedDocuments), $this->sortResults($results)); - - $operation = new Find($this->getDatabaseName(), $out, [], ['typeMap' => $typeMap]); - $cursor = $operation->execute($this->getPrimaryServer()); - - $this->assertEquals($this->sortResults($expectedDocuments), $this->sortResults(iterator_to_array($cursor))); - } - - /** - * Create data fixtures. - */ - private function createFixtures(int $n): void - { - $this->dropCollection($this->getDatabaseName(), $this->getCollectionName()); - $bulkWrite = new BulkWrite(['ordered' => true]); - - for ($i = 1; $i <= $n; $i++) { - $bulkWrite->insert(['x' => $i, 'y' => $i]); - $bulkWrite->insert(['x' => $i, 'y' => $i * 2]); - } - - $result = $this->manager->executeBulkWrite($this->getNamespace(), $bulkWrite); - - $this->assertEquals($n * 2, $result->getInsertedCount()); - } - - private function sortResults(array $results): array - { - $sortFunction = static function ($resultA, $resultB): int { - $idA = is_object($resultA) ? $resultA->_id : $resultA['_id']; - $idB = is_object($resultB) ? $resultB->_id : $resultB['_id']; - - return $idA <=> $idB; - }; - - $sortedResults = $results; - usort($sortedResults, $sortFunction); - - return $sortedResults; - } -} diff --git a/tests/Operation/MapReduceTest.php b/tests/Operation/MapReduceTest.php deleted file mode 100644 index 86af81197..000000000 --- a/tests/Operation/MapReduceTest.php +++ /dev/null @@ -1,95 +0,0 @@ -expectException(TypeError::class); - new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out); - } - - public static function provideInvalidOutValues() - { - return self::wrapValuesForDataProvider([123, 3.14, true]); - } - - #[DataProvider('provideDeprecatedOutValues')] - public function testConstructorOutArgumentDeprecations($out): void - { - $map = new Javascript('function() { emit(this.x, this.y); }'); - $reduce = new Javascript('function(key, values) { return Array.sum(values); }'); - - $this->assertDeprecated(function () use ($map, $reduce, $out): void { - new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out); - }); - } - - public static function provideDeprecatedOutValues(): array - { - return [ - 'nonAtomic:array' => [['nonAtomic' => false]], - 'nonAtomic:object' => [(object) ['nonAtomic' => false]], - 'nonAtomic:Serializable' => [new BSONDocument(['nonAtomic' => false])], - 'nonAtomic:Document' => [Document::fromPHP(['nonAtomic' => false])], - 'sharded:array' => [['sharded' => false]], - 'sharded:object' => [(object) ['sharded' => false]], - 'sharded:Serializable' => [new BSONDocument(['sharded' => false])], - 'sharded:Document' => [Document::fromPHP(['sharded' => false])], - ]; - } - - #[DataProvider('provideInvalidConstructorOptions')] - public function testConstructorOptionTypeChecks(array $options): void - { - $map = new Javascript('function() { emit(this.x, this.y); }'); - $reduce = new Javascript('function(key, values) { return Array.sum(values); }'); - $out = ['inline' => 1]; - - $this->expectException(InvalidArgumentException::class); - new MapReduce($this->getDatabaseName(), $this->getCollectionName(), $map, $reduce, $out, $options); - } - - public static function provideInvalidConstructorOptions() - { - return self::createOptionDataProvider([ - 'bypassDocumentValidation' => self::getInvalidBooleanValues(), - 'collation' => self::getInvalidDocumentValues(), - 'finalize' => self::getInvalidJavascriptValues(), - 'jsMode' => self::getInvalidBooleanValues(), - 'limit' => self::getInvalidIntegerValues(), - 'maxTimeMS' => self::getInvalidIntegerValues(), - 'query' => self::getInvalidDocumentValues(), - 'readConcern' => self::getInvalidReadConcernValues(), - 'readPreference' => self::getInvalidReadPreferenceValues(), - 'scope' => self::getInvalidDocumentValues(), - 'session' => self::getInvalidSessionValues(), - 'sort' => self::getInvalidDocumentValues(), - 'typeMap' => self::getInvalidArrayValues(), - 'verbose' => self::getInvalidBooleanValues(), - 'writeConcern' => self::getInvalidWriteConcernValues(), - ]); - } - - private static function getInvalidJavascriptValues() - { - return [123, 3.14, 'foo', true, [], new stdClass(), new ObjectId()]; - } -} diff --git a/tests/Operation/RenameCollectionFunctionalTest.php b/tests/Operation/RenameCollectionFunctionalTest.php index 9dccae0e3..37fb23d4d 100644 --- a/tests/Operation/RenameCollectionFunctionalTest.php +++ b/tests/Operation/RenameCollectionFunctionalTest.php @@ -65,9 +65,8 @@ public function testRenameCollectionToNonexistentTarget(): void $this->getDatabaseName(), $this->toCollectionName, ); - $commandResult = $operation->execute($server); + $operation->execute($server); - $this->assertCommandSucceeded($commandResult); $this->assertCollectionDoesNotExist($this->getCollectionName()); $this->assertCollectionExists($this->toCollectionName); diff --git a/tests/Operation/RenameCollectionTest.php b/tests/Operation/RenameCollectionTest.php index bae80e9ee..e0a438329 100644 --- a/tests/Operation/RenameCollectionTest.php +++ b/tests/Operation/RenameCollectionTest.php @@ -26,7 +26,6 @@ public static function provideInvalidConstructorOptions() return self::createOptionDataProvider([ 'dropTarget' => self::getInvalidBooleanValues(), 'session' => self::getInvalidSessionValues(), - 'typeMap' => self::getInvalidArrayValues(), 'writeConcern' => self::getInvalidWriteConcernValues(), ]); } diff --git a/tests/Operation/UpdateFunctionalTest.php b/tests/Operation/UpdateFunctionalTest.php index 5d1870172..b9424975b 100644 --- a/tests/Operation/UpdateFunctionalTest.php +++ b/tests/Operation/UpdateFunctionalTest.php @@ -5,8 +5,8 @@ use MongoDB\BSON\ObjectId; use MongoDB\Collection; use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Exception\LogicException; use MongoDB\Driver\WriteConcern; -use MongoDB\Exception\BadMethodCallException; use MongoDB\Exception\UnsupportedException; use MongoDB\Operation\Update; use MongoDB\Tests\CommandObserver; @@ -287,32 +287,32 @@ public function testUnacknowledgedWriteConcern() #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesMatchedCount(UpdateResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getMatchedCount(); } #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesModifiedCount(UpdateResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getModifiedCount(); } #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesUpsertedCount(UpdateResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getUpsertedCount(); } #[Depends('testUnacknowledgedWriteConcern')] public function testUnacknowledgedWriteConcernAccessesUpsertedId(UpdateResult $result): void { - $this->expectException(BadMethodCallException::class); - $this->expectExceptionMessageMatches('/[\w:\\\\]+ should not be called for an unacknowledged write result/'); + $this->expectException(LogicException::class); + $this->expectExceptionMessageMatches('/[\w:\\\\\(\)]+ should not be called for an unacknowledged write result/'); $result->getUpsertedId(); } diff --git a/tests/Operation/WatchFunctionalTest.php b/tests/Operation/WatchFunctionalTest.php index 6e57361ed..522b01ba4 100644 --- a/tests/Operation/WatchFunctionalTest.php +++ b/tests/Operation/WatchFunctionalTest.php @@ -6,7 +6,6 @@ use Generator; use Iterator; use MongoDB\BSON\Document; -use MongoDB\BSON\TimestampInterface; use MongoDB\ChangeStream; use MongoDB\Codec\DecodeIfSupported; use MongoDB\Codec\DocumentCodec; @@ -106,44 +105,6 @@ public function encode($value): Document ]; } - /** - * Prose test 1: "ChangeStream must continuously track the last seen - * resumeToken" - */ - #[DataProvider('provideCodecOptions')] - public function testGetResumeToken(array $options, Closure $getIdentifier): void - { - $this->skipIfServerVersion('>=', '4.0.7', 'postBatchResumeToken is supported'); - - $operation = new Watch( - $this->manager, - $this->getDatabaseName(), - $this->getCollectionName(), - [], - $options + $this->defaultOptions, - ); - $changeStream = $operation->execute($this->getPrimaryServer()); - - $changeStream->rewind(); - $this->assertFalse($changeStream->valid()); - $this->assertNull($changeStream->getResumeToken()); - - $this->insertDocument(['x' => 1]); - $this->insertDocument(['x' => 2]); - - $this->advanceCursorUntilValid($changeStream); - $this->assertSameDocument($getIdentifier($changeStream->current()), $changeStream->getResumeToken()); - - $changeStream->next(); - $this->assertTrue($changeStream->valid()); - $this->assertSameDocument($getIdentifier($changeStream->current()), $changeStream->getResumeToken()); - - $this->insertDocument(['x' => 3]); - - $this->advanceCursorUntilValid($changeStream); - $this->assertSameDocument($getIdentifier($changeStream->current()), $changeStream->getResumeToken()); - } - /** * Prose test 1: "ChangeStream must continuously track the last seen * resumeToken" @@ -165,8 +126,6 @@ public function testGetResumeToken(array $options, Closure $getIdentifier): void #[DataProvider('provideCodecOptions')] public function testGetResumeTokenWithPostBatchResumeToken(array $options, Closure $getIdentifier): void { - $this->skipIfServerVersion('<', '4.0.7', 'postBatchResumeToken is not supported'); - $operation = new Watch( $this->manager, $this->getDatabaseName(), @@ -267,8 +226,6 @@ function (array $event) use (&$commands): void { public function testResumeBeforeReceivingAnyResultsIncludesPostBatchResumeToken(): void { - $this->skipIfServerVersion('<', '4.0.7', 'postBatchResumeToken is not supported'); - $operation = new Watch($this->manager, $this->getDatabaseName(), $this->getCollectionName(), [], $this->defaultOptions); $events = []; @@ -330,79 +287,6 @@ private function assertResumeAfter($expectedResumeToken, stdClass $command): voi $this->assertEquals($expectedResumeToken, $command->pipeline[0]->{'$changeStream'}->resumeAfter); } - /** - * Prose test 9: "$changeStream stage for ChangeStream against a server - * >=4.0 and <4.0.7 that has not received any results yet MUST include a - * startAtOperationTime option when resuming a changestream." - */ - public function testResumeBeforeReceivingAnyResultsIncludesStartAtOperationTime(): void - { - $this->skipIfServerVersion('>=', '4.0.7', 'postBatchResumeToken takes precedence over startAtOperationTime'); - - $operation = new Watch($this->manager, $this->getDatabaseName(), $this->getCollectionName(), [], $this->defaultOptions); - - $events = []; - - (new CommandObserver())->observe( - function () use ($operation, &$changeStream): void { - $changeStream = $operation->execute($this->getPrimaryServer()); - }, - function (array $event) use (&$events): void { - $events[] = $event; - }, - ); - - $this->assertCount(1, $events); - $this->assertSame('aggregate', $events[0]['started']->getCommandName()); - $reply = $events[0]['succeeded']->getReply(); - $this->assertObjectHasProperty('operationTime', $reply); - $operationTime = $reply->operationTime; - $this->assertInstanceOf(TimestampInterface::class, $operationTime); - - $this->assertFalse($changeStream->valid()); - $this->forceChangeStreamResume(); - - $this->assertNoCommandExecuted(function () use ($changeStream): void { - $changeStream->rewind(); - }); - - $events = []; - - (new CommandObserver())->observe( - function () use ($changeStream): void { - $changeStream->next(); - }, - function (array $event) use (&$events): void { - $events[] = $event; - }, - ); - - $this->assertCount(3, $events); - - $this->assertSame('getMore', $events[0]['started']->getCommandName()); - $this->assertArrayHasKey('failed', $events[0]); - - $this->assertSame('aggregate', $events[1]['started']->getCommandName()); - $this->assertStartAtOperationTime($operationTime, $events[1]['started']->getCommand()); - $this->assertArrayHasKey('succeeded', $events[1]); - - // Original cursor is freed immediately after the change stream resumes - $this->assertSame('killCursors', $events[2]['started']->getCommandName()); - $this->assertArrayHasKey('succeeded', $events[2]); - - $this->assertFalse($changeStream->valid()); - } - - private function assertStartAtOperationTime(TimestampInterface $expectedOperationTime, stdClass $command): void - { - $this->assertObjectHasProperty('pipeline', $command); - $this->assertIsArray($command->pipeline); - $this->assertArrayHasKey(0, $command->pipeline); - $this->assertObjectHasProperty('$changeStream', $command->pipeline[0]); - $this->assertObjectHasProperty('startAtOperationTime', $command->pipeline[0]->{'$changeStream'}); - $this->assertEquals($expectedOperationTime, $command->pipeline[0]->{'$changeStream'}->startAtOperationTime); - } - public function testRewindMultipleTimesWithResults(): void { $this->skipIfIsShardedCluster('Cursor needs to be advanced multiple times and can\'t be rewound afterwards.'); @@ -720,7 +604,7 @@ public function testInitialCursorIsNotClosed(): void * reports the cursor as alive. While the cursor ID is accessed through * ChangeStream, we'll need to use reflection to access the internal * Cursor and call isDead(). */ - $this->assertNotEquals('0', (string) $changeStream->getCursorId(true)); + $this->assertNotEquals(0, $changeStream->getCursorId()); $rc = new ReflectionClass(ChangeStream::class); $iterator = $rc->getProperty('iterator')->getValue($changeStream); @@ -1319,8 +1203,6 @@ function (array $aggregateCommand) { */ public function testErrorDuringAggregateCommandDoesNotCauseResume(): void { - $this->skipIfServerVersion('<', '4.0.0', 'failCommand is not supported'); - $operation = new Watch($this->manager, $this->getDatabaseName(), $this->getCollectionName(), [], $this->defaultOptions); $commandCount = 0; @@ -1366,11 +1248,11 @@ public function testOriginalReadPreferenceIsPreservedOnResume(): void } $changeStream = $operation->execute($secondary); - $previousCursorId = $changeStream->getCursorId(true); + $previousCursorId = $changeStream->getCursorId(); $this->forceChangeStreamResume($secondary); $changeStream->next(); - $this->assertNotEquals($previousCursorId, $changeStream->getCursorId(true)); + $this->assertNotEquals($previousCursorId, $changeStream->getCursorId()); $getCursor = Closure::bind( fn () => $this->iterator->getInnerIterator(), @@ -1382,39 +1264,6 @@ public function testOriginalReadPreferenceIsPreservedOnResume(): void self::assertTrue($cursor->getServer()->isSecondary()); } - /** - * Prose test 12 - * For a ChangeStream under these conditions: - * - Running against a server <4.0.7. - * - The batch is empty or has been iterated to the last document. - * Expected result: - * - getResumeToken must return the _id of the last document returned if one exists. - * - getResumeToken must return resumeAfter from the initial aggregate if the option was specified. - * - If resumeAfter was not specified, the getResumeToken result must be empty. - */ - public function testGetResumeTokenReturnsOriginalResumeTokenOnEmptyBatch(): void - { - $this->skipIfServerVersion('>=', '4.0.7', 'postBatchResumeToken is supported'); - - $operation = new Watch($this->manager, $this->getDatabaseName(), $this->getCollectionName(), [], $this->defaultOptions); - $changeStream = $operation->execute($this->getPrimaryServer()); - - $this->assertNull($changeStream->getResumeToken()); - - $this->insertDocument(['x' => 1]); - - $changeStream->next(); - $this->assertTrue($changeStream->valid()); - $resumeToken = $changeStream->getResumeToken(); - $this->assertSame($resumeToken, $changeStream->current()->_id); - - $options = ['resumeAfter' => $resumeToken] + $this->defaultOptions; - $operation = new Watch($this->manager, $this->getDatabaseName(), $this->getCollectionName(), [], $options); - $changeStream = $operation->execute($this->getPrimaryServer()); - - $this->assertSame($resumeToken, $changeStream->getResumeToken()); - } - /** * Prose test 14 * For a ChangeStream under these conditions: @@ -1429,7 +1278,6 @@ public function testGetResumeTokenReturnsOriginalResumeTokenOnEmptyBatch(): void #[DataProvider('provideCodecOptions')] public function testResumeTokenBehaviour(array $options, Closure $getIdentifier): void { - $this->skipIfServerVersion('<', '4.1.1', 'Testing resumeAfter and startAfter can only be tested on servers >= 4.1.1'); $this->skipIfIsShardedCluster('Resume token behaviour can\'t be reliably tested on sharded clusters.'); $operation = new Watch( diff --git a/tests/PedantryTest.php b/tests/PedantryTest.php index 3d89f259a..4a5cd7096 100644 --- a/tests/PedantryTest.php +++ b/tests/PedantryTest.php @@ -12,6 +12,7 @@ use function array_filter; use function array_map; +use function array_values; use function in_array; use function realpath; use function str_contains; @@ -39,11 +40,12 @@ public function testMethodsAreOrderedAlphabeticallyByVisibility($className): voi $class = new ReflectionClass($className); $methods = $class->getMethods(); - $methods = array_filter( + $methods = array_values(array_filter( $methods, fn (ReflectionMethod $method) => $method->getDeclaringClass() == $class // Exclude inherited methods - && $method->getFileName() === $class->getFileName(), // Exclude methods inherited from traits - ); + && $method->getFileName() === $class->getFileName() // Exclude methods inherited from traits + && ! ($method->isConstructor() && ! $method->isPublic()), // Exclude non-public constructors + )); $getSortValue = function (ReflectionMethod $method) { $prefix = $method->isPrivate() ? '2' : ($method->isProtected() ? '1' : '0'); diff --git a/tests/SpecTests/ClientSideEncryption/FunctionalTestCase.php b/tests/SpecTests/ClientSideEncryption/FunctionalTestCase.php index 8290e8e8b..58c2aa24d 100644 --- a/tests/SpecTests/ClientSideEncryption/FunctionalTestCase.php +++ b/tests/SpecTests/ClientSideEncryption/FunctionalTestCase.php @@ -25,19 +25,6 @@ public function setUp(): void parent::setUp(); $this->skipIfClientSideEncryptionIsNotSupported(); - - if (! static::isCryptSharedLibAvailable() && ! static::isMongocryptdAvailable()) { - $this->markTestSkipped('Neither crypt_shared nor mongocryptd are available'); - } - } - - public static function createTestClient(?string $uri = null, array $options = [], array $driverOptions = []): Client - { - if (isset($driverOptions['autoEncryption']) && getenv('CRYPT_SHARED_LIB_PATH')) { - $driverOptions['autoEncryption']['extraOptions']['cryptSharedLibPath'] = getenv('CRYPT_SHARED_LIB_PATH'); - } - - return parent::createTestClient($uri, $options, $driverOptions); } protected static function getAWSCredentials(): array diff --git a/tests/SpecTests/ClientSideEncryption/Prose21_AutomaticDataEncryptionKeysTest.php b/tests/SpecTests/ClientSideEncryption/Prose21_AutomaticDataEncryptionKeysTest.php index 30bd1b415..ee4d61b1a 100644 --- a/tests/SpecTests/ClientSideEncryption/Prose21_AutomaticDataEncryptionKeysTest.php +++ b/tests/SpecTests/ClientSideEncryption/Prose21_AutomaticDataEncryptionKeysTest.php @@ -68,7 +68,7 @@ public function tearDown(): void #[DataProvider('provideKmsProviderAndMasterKey')] public function testCase1_SimpleCreationAndValidation(string $kmsProvider, ?array $masterKey): void { - [$result, $encryptedFields] = $this->database->createEncryptedCollection( + $encryptedFields = $this->database->createEncryptedCollection( $this->getCollectionName(), $this->clientEncryption, $kmsProvider, @@ -76,7 +76,6 @@ public function testCase1_SimpleCreationAndValidation(string $kmsProvider, ?arra ['encryptedFields' => ['fields' => [['path' => 'ssn', 'bsonType' => 'string', 'keyId' => null]]]], ); - $this->assertCommandSucceeded($result); $this->assertInstanceOf(Binary::class, $encryptedFields['fields'][0]['keyId'] ?? null); $this->expectException(BulkWriteException::class); @@ -139,7 +138,7 @@ public function testCase3_InvalidKeyId(string $kmsProvider, ?array $masterKey): #[DataProvider('provideKmsProviderAndMasterKey')] public function testCase4_InsertEncryptedValue(string $kmsProvider, ?array $masterKey): void { - [$result, $encryptedFields] = $this->database->createEncryptedCollection( + $encryptedFields = $this->database->createEncryptedCollection( $this->getCollectionName(), $this->clientEncryption, $kmsProvider, @@ -147,7 +146,6 @@ public function testCase4_InsertEncryptedValue(string $kmsProvider, ?array $mast ['encryptedFields' => ['fields' => [['path' => 'ssn', 'bsonType' => 'string', 'keyId' => null]]]], ); - $this->assertCommandSucceeded($result); $this->assertInstanceOf(Binary::class, $encryptedFields['fields'][0]['keyId'] ?? null); $encrypted = $this->clientEncryption->encrypt('123-45-6789', [ diff --git a/tests/SpecTests/ClientSideEncryption/Prose25_LookupTest.php b/tests/SpecTests/ClientSideEncryption/Prose25_LookupTest.php new file mode 100644 index 000000000..6abfbda13 --- /dev/null +++ b/tests/SpecTests/ClientSideEncryption/Prose25_LookupTest.php @@ -0,0 +1,372 @@ +isStandalone()) { + $this->markTestSkipped('Lookup tests require replica sets'); + } + + $this->skipIfServerVersion('<', '7.0.0', 'Lookup encryption tests require MongoDB 7.0 or later'); + + $key1Document = $this->decodeJson(file_get_contents(self::$dataDir . '/key-doc.json')); + $this->key1Id = $key1Document->_id; + + $encryptedClient = $this->getEncryptedClient(); + + // Drop the key vault collection and insert key1Document with a majority write concern + self::insertKeyVaultData($encryptedClient, [$key1Document]); + + $this->refreshCollections($encryptedClient); + } + + private function getEncryptedClient(): Client + { + $autoEncryptionOpts = [ + 'keyVaultNamespace' => 'keyvault.datakeys', + 'kmsProviders' => ['local' => ['key' => new Binary(base64_decode(self::LOCAL_MASTERKEY))]], + ]; + + return self::createTestClient(null, [], [ + 'autoEncryption' => $autoEncryptionOpts, + /* libmongocrypt caches results from listCollections. Use a new + * client in each test to ensure its encryptedFields is applied. */ + 'disableClientPersistence' => true, + ]); + } + + private function refreshCollections(Client $client): void + { + $encryptedDb = $client->getDatabase(self::getDatabaseName()); + $unencryptedDb = self::createTestClient()->getDatabase(self::getDatabaseName()); + + $optionsMap = [ + self::COLL_CSFLE => [ + 'validator' => [ + '$jsonSchema' => $this->decodeJson(file_get_contents(self::$dataDir . '/schema-csfle.json')), + ], + ], + self::COLL_CSFLE2 => [ + 'validator' => [ + '$jsonSchema' => $this->decodeJson(file_get_contents(self::$dataDir . '/schema-csfle2.json')), + ], + ], + self::COLL_QE => [ + 'encryptedFields' => $this->decodeJson(file_get_contents(self::$dataDir . '/schema-qe.json')), + ], + self::COLL_QE2 => [ + 'encryptedFields' => $this->decodeJson(file_get_contents(self::$dataDir . '/schema-qe2.json')), + ], + self::COLL_NO_SCHEMA => [], + self::COLL_NO_SCHEMA2 => [], + ]; + + foreach ($optionsMap as $collectionName => $options) { + $encryptedDb->dropCollection($collectionName); + $encryptedDb->createCollection($collectionName, $options); + + $collection = $unencryptedDb->getCollection($collectionName); + + $result = $encryptedDb->getCollection($collectionName)->insertOne([$collectionName => $collectionName]); + + if ($options) { + $document = $collection->findOne(['_id' => $result->getInsertedId()]); + $this->assertInstanceOf(Binary::class, $document->{$collectionName}); + } + } + } + + private function assertPipelineReturnsSingleDocument(string $collection, array $pipeline, array $expected): void + { + $this->skipIfServerVersion('<', '8.1.0', 'Lookup test case requires server version 8.1.0 or later'); + $this->skipIfClientSideEncryptionIsNotSupported(); + + $cursor = $this + ->getEncryptedClient() + ->getCollection(self::getDatabaseName(), $collection) + ->aggregate($pipeline); + + $cursor->rewind(); + $this->assertMatchesDocument( + $expected, + $cursor->current(), + ); + $this->assertNull($cursor->next()); + } + + public function testCase1_CsfleJoinsNoSchema(): void + { + $pipeline = [ + [ + '$match' => ['csfle' => 'csfle'], + ], + [ + '$lookup' => [ + 'from' => 'no_schema', + 'as' => 'matched', + 'pipeline' => [ + [ + '$match' => ['no_schema' => 'no_schema'], + ], + [ + '$project' => ['_id' => 0], + ], + ], + ], + ], + [ + '$project' => ['_id' => 0], + ], + ]; + $expected = [ + 'csfle' => 'csfle', + 'matched' => [ + ['no_schema' => 'no_schema'], + ], + ]; + + $this->assertPipelineReturnsSingleDocument(self::COLL_CSFLE, $pipeline, $expected); + } + + public function testCase2_QeJoinsNoSchema(): void + { + $pipeline = [ + [ + '$match' => ['qe' => 'qe'], + ], + [ + '$lookup' => [ + 'from' => 'no_schema', + 'as' => 'matched', + 'pipeline' => [ + [ + '$match' => ['no_schema' => 'no_schema'], + ], + [ + '$project' => [ + '_id' => 0, + '__safeContent__' => 0, + ], + ], + ], + ], + ], + [ + '$project' => [ + '_id' => 0, + '__safeContent__' => 0, + ], + ], + ]; + $expected = [ + 'qe' => 'qe', + 'matched' => [ + ['no_schema' => 'no_schema'], + ], + ]; + + $this->assertPipelineReturnsSingleDocument(self::COLL_QE, $pipeline, $expected); + } + + public function testCase3_NoSchemaJoinsCsfle(): void + { + $pipeline = [['$match' => ['no_schema' => 'no_schema']], + [ + '$lookup' => [ + 'from' => 'csfle', + 'as' => 'matched', + 'pipeline' => [ + [ + '$match' => ['csfle' => 'csfle'], + ], + [ + '$project' => ['_id' => 0], + ], + ], + ], + ], + ['$project' => ['_id' => 0]], + ]; + $expected = ['no_schema' => 'no_schema', 'matched' => [['csfle' => 'csfle']]]; + + $this->assertPipelineReturnsSingleDocument(self::COLL_NO_SCHEMA, $pipeline, $expected); + } + + public function testCase4_NoSchemaJoinsQe(): void + { + $pipeline = [ + [ + '$match' => ['no_schema' => 'no_schema'], + ], + [ + '$lookup' => [ + 'from' => 'qe', + 'as' => 'matched', + 'pipeline' => [ + [ + '$match' => ['qe' => 'qe'], + ], + [ + '$project' => [ + '_id' => 0, + '__safeContent__' => 0, + ], + ], + ], + ], + ], + [ + '$project' => ['_id' => 0], + ], + ]; + $expected = [ + 'no_schema' => 'no_schema', + 'matched' => [ + ['qe' => 'qe'], + ], + ]; + + $this->assertPipelineReturnsSingleDocument(self::COLL_NO_SCHEMA, $pipeline, $expected); + } + + public function testCase5_CsfleJoinsCsfle2(): void + { + $pipeline = [ + ['$match' => ['csfle' => 'csfle']], + [ + '$lookup' => [ + 'from' => 'csfle2', + 'as' => 'matched', + 'pipeline' => [ + [ + '$match' => ['csfle2' => 'csfle2'], + ], + [ + '$project' => ['_id' => 0], + ], + ], + ], + ], + ['$project' => ['_id' => 0]], + ]; + $expected = ['csfle' => 'csfle', 'matched' => [['csfle2' => 'csfle2']]]; + + $this->assertPipelineReturnsSingleDocument(self::COLL_CSFLE, $pipeline, $expected); + } + + public function testCase6_QeJoinsQe2(): void + { + $pipeline = [ + ['$match' => ['qe' => 'qe']], + [ + '$lookup' => [ + 'from' => 'qe2', + 'as' => 'matched', + 'pipeline' => [ + [ + '$match' => ['qe2' => 'qe2'], + ], + [ + '$project' => [ + '_id' => 0, + '__safeContent__' => 0, + ], + ], + ], + ], + ], + ['$project' => ['_id' => 0, '__safeContent__' => 0]], + ]; + $expected = ['qe' => 'qe', 'matched' => [['qe2' => 'qe2']]]; + + $this->assertPipelineReturnsSingleDocument(self::COLL_QE, $pipeline, $expected); + } + + public function testCase7_NoSchemaJoinsNoSchema2(): void + { + $pipeline = [ + ['$match' => ['no_schema' => 'no_schema']], + [ + '$lookup' => [ + 'from' => 'no_schema2', + 'as' => 'matched', + 'pipeline' => [ + ['$match' => ['no_schema2' => 'no_schema2']], + ['$project' => ['_id' => 0]], + ], + ], + ], + ['$project' => ['_id' => 0]], + ]; + $expected = ['no_schema' => 'no_schema', 'matched' => [['no_schema2' => 'no_schema2']]]; + + $this->assertPipelineReturnsSingleDocument(self::COLL_NO_SCHEMA, $pipeline, $expected); + } + + public function testCase8_CsfleJoinsQeFails(): void + { + $this->skipIfServerVersion('<', '8.1.0', 'Lookup test case requires server version 8.1.0 or later'); + $this->skipIfClientSideEncryptionIsNotSupported(); + + $this->expectExceptionMessage('not supported'); + + $this->getEncryptedClient() + ->getCollection(self::getDatabaseName(), self::COLL_CSFLE) + ->aggregate([ + [ + '$match' => ['csfle' => 'qe'], + ], + [ + '$lookup' => [ + 'from' => 'qe', + 'as' => 'matched', + 'pipeline' => [ + [ + '$match' => ['qe' => 'qe'], + ], + [ + '$project' => ['_id' => 0], + ], + ], + ], + ], + [ + '$project' => ['_id' => 0], + ], + ]); + } + + public function testCase9_TestErrorWithLessThan8_1(): void + { + $this->markTestSkipped('Depends on PHPC-2616 to determine crypt shared version.'); + } +} diff --git a/tests/SpecTests/ClientSideEncryptionSpecTest.php b/tests/SpecTests/ClientSideEncryptionSpecTest.php index 7032b8eae..585883ae0 100644 --- a/tests/SpecTests/ClientSideEncryptionSpecTest.php +++ b/tests/SpecTests/ClientSideEncryptionSpecTest.php @@ -142,15 +142,6 @@ public static function assertCommandMatches(stdClass $expected, stdClass $actual static::assertDocumentsMatch($expected, $actual); } - public static function createTestClient(?string $uri = null, array $options = [], array $driverOptions = []): Client - { - if (isset($driverOptions['autoEncryption']) && getenv('CRYPT_SHARED_LIB_PATH')) { - $driverOptions['autoEncryption']['extraOptions']['cryptSharedLibPath'] = getenv('CRYPT_SHARED_LIB_PATH'); - } - - return parent::createTestClient($uri, $options, $driverOptions); - } - /** * Execute an individual test case from the specification. * @@ -620,7 +611,7 @@ public function testViewsAreProhibited(): void $previous = $e->getPrevious(); $this->assertInstanceOf(EncryptionException::class, $previous); - $this->assertSame('cannot auto encrypt a view', $previous->getMessage()); + $this->assertStringContainsString('cannot auto encrypt a view', $previous->getMessage()); } } @@ -1017,7 +1008,7 @@ public function testBypassSpawningMongocryptdViaBypassAutoEncryption(): void /** * Prose test 8: Bypass spawning mongocryptd (via bypassQueryAnalysis) * - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#via-bypassqueryanalysis + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#via-bypassqueryanalysis */ public function testBypassSpawningMongocryptdViaBypassQueryAnalysis(): void { @@ -1051,7 +1042,7 @@ public function testBypassSpawningMongocryptdViaBypassQueryAnalysis(): void /** * Prose test 10: KMS TLS Tests (Invalid KMS Certificate) * - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#invalid-kms-certificate + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#invalid-kms-certificate */ public function testInvalidKmsCertificate(): void { @@ -1079,7 +1070,7 @@ public function testInvalidKmsCertificate(): void /** * Prose test 10: KMS TLS Tests (Invalid Hostname in KMS Certificate) * - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#invalid-hostname-in-kms-certificate + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#invalid-hostname-in-kms-certificate */ public function testInvalidHostnameInKmsCertificate(): void { @@ -1107,7 +1098,7 @@ public function testInvalidHostnameInKmsCertificate(): void /** * Prose test 11: KMS TLS Options * - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#kms-tls-options-tests + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#kms-tls-options-tests */ #[DataProvider('provideKmsTlsOptionsTests')] public function testKmsTlsOptions(Closure $test): void @@ -1190,7 +1181,7 @@ public static function provideKmsTlsOptionsTests() // Note: expected exception messages below assume OpenSSL is used - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-1-aws + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-1-aws yield 'AWS: client_encryption_no_client_cert' => [ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, ClientEncryption $clientEncryptionWithTls, ClientEncryption $clientEncryptionExpired, ClientEncryption $clientEncryptionInvalidHostname) use ($awsMasterKey): void { $test->expectException(ConnectionException::class); @@ -1223,7 +1214,7 @@ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, Cli }, ]; - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-2-azure + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-2-azure yield 'Azure: client_encryption_no_client_cert' => [ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, ClientEncryption $clientEncryptionWithTls, ClientEncryption $clientEncryptionExpired, ClientEncryption $clientEncryptionInvalidHostname) use ($azureMasterKey): void { $test->expectException(ConnectionException::class); @@ -1256,7 +1247,7 @@ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, Cli }, ]; - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-3-gcp + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-3-gcp yield 'GCP: client_encryption_no_client_cert' => [ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, ClientEncryption $clientEncryptionWithTls, ClientEncryption $clientEncryptionExpired, ClientEncryption $clientEncryptionInvalidHostname) use ($gcpMasterKey): void { $test->expectException(ConnectionException::class); @@ -1289,7 +1280,7 @@ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, Cli }, ]; - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-4-kmip + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-4-kmip yield 'KMIP: client_encryption_no_client_cert' => [ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, ClientEncryption $clientEncryptionWithTls, ClientEncryption $clientEncryptionExpired, ClientEncryption $clientEncryptionInvalidHostname) use ($kmipMasterKey): void { $test->expectException(ConnectionException::class); @@ -1325,7 +1316,7 @@ static function (self $test, ClientEncryption $clientEncryptionNoClientCert, Cli /** * Prose test 12: Explicit Encryption * - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#explicit-encryption + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#explicit-encryption */ #[DataProvider('provideExplicitEncryptionTests')] public function testExplicitEncryption(Closure $test): void @@ -1374,7 +1365,7 @@ public function testExplicitEncryption(Closure $test): void public static function provideExplicitEncryptionTests() { - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-1-can-insert-encrypted-indexed-and-find + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-1-can-insert-encrypted-indexed-and-find yield 'Case 1: can insert encrypted indexed and find' => [ static function (self $test, ClientEncryption $clientEncryption, Client $encryptedClient, Client $keyVaultClient, Binary $key1Id): void { $value = 'encrypted indexed value'; @@ -1402,7 +1393,7 @@ static function (self $test, ClientEncryption $clientEncryption, Client $encrypt }, ]; - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-2-can-insert-encrypted-indexed-and-find-with-non-zero-contention + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-2-can-insert-encrypted-indexed-and-find-with-non-zero-contention yield 'Case 2: can insert encrypted indexed and find with non-zero contention' => [ static function (self $test, ClientEncryption $clientEncryption, Client $encryptedClient, Client $keyVaultClient, Binary $key1Id): void { $value = 'encrypted indexed value'; @@ -1451,7 +1442,7 @@ static function (self $test, ClientEncryption $clientEncryption, Client $encrypt }, ]; - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-3-can-insert-encrypted-unindexed + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-3-can-insert-encrypted-unindexed yield 'Case 3: can insert encrypted unindexed' => [ static function (self $test, ClientEncryption $clientEncryption, Client $encryptedClient, Client $keyVaultClient, Binary $key1Id): void { $value = 'encrypted unindexed value'; @@ -1471,7 +1462,7 @@ static function (self $test, ClientEncryption $clientEncryption, Client $encrypt }, ]; - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-4-can-roundtrip-encrypted-indexed + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-4-can-roundtrip-encrypted-indexed yield 'Case 4: can roundtrip encrypted indexed' => [ static function (self $test, ClientEncryption $clientEncryption, Client $encryptedClient, Client $keyVaultClient, Binary $key1Id): void { $value = 'encrypted indexed value'; @@ -1486,7 +1477,7 @@ static function (self $test, ClientEncryption $clientEncryption, Client $encrypt }, ]; - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-5-can-roundtrip-encrypted-unindexed + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-5-can-roundtrip-encrypted-unindexed yield 'Case 5: can roundtrip encrypted unindexed' => [ static function (self $test, ClientEncryption $clientEncryption, Client $encryptedClient, Client $keyVaultClient, Binary $key1Id): void { $value = 'encrypted unindexed value'; @@ -1504,7 +1495,7 @@ static function (self $test, ClientEncryption $clientEncryption, Client $encrypt /** * Prose test 13: Unique Index on keyAltNames * - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#unique-index-on-keyaltnames + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#unique-index-on-keyaltnames */ #[DataProvider('provideUniqueIndexOnKeyAltNamesTests')] public function testUniqueIndexOnKeyAltNames(Closure $test): void @@ -1537,7 +1528,7 @@ public function testUniqueIndexOnKeyAltNames(Closure $test): void public static function provideUniqueIndexOnKeyAltNamesTests() { - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-1-createdatakey + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-1-createdatakey yield 'Case 1: createDataKey()' => [ static function (self $test, Client $client, ClientEncryption $clientEncryption): void { $clientEncryption->createDataKey('local', ['keyAltNames' => ['abc']]); @@ -1558,7 +1549,7 @@ static function (self $test, Client $client, ClientEncryption $clientEncryption) }, ]; - // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#case-2-addkeyaltname + // See: https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#case-2-addkeyaltname yield 'Case 2: addKeyAltName()' => [ static function (self $test, Client $client, ClientEncryption $clientEncryption): void { $keyId = $clientEncryption->createDataKey('local'); @@ -1783,7 +1774,7 @@ public function testOnDemandAwsCredentials(bool $shouldSucceed): void /** * Prose test 16: RewrapManyDataKey * - * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.rst#rewrap + * @see https://github.com/mongodb/specifications/blob/master/source/client-side-encryption/tests/README.md#rewrap */ #[DataProvider('provideRewrapManyDataKeySrcAndDstProviders')] public function testRewrapManyDataKey(string $srcProvider, string $dstProvider): void diff --git a/tests/SpecTests/Context.php b/tests/SpecTests/Context.php index c3704900f..4d2db90cb 100644 --- a/tests/SpecTests/Context.php +++ b/tests/SpecTests/Context.php @@ -105,11 +105,6 @@ public static function fromClientSideEncryption(stdClass $test, $databaseName, $ $autoEncryptionOptions['tlsOptions']->kmip = self::getKmsTlsOptions(); } - - // Intentionally ignore empty values for CRYPT_SHARED_LIB_PATH - if (getenv('CRYPT_SHARED_LIB_PATH')) { - $autoEncryptionOptions['extraOptions']['cryptSharedLibPath'] = getenv('CRYPT_SHARED_LIB_PATH'); - } } if (isset($test->outcome->collection->name)) { diff --git a/tests/SpecTests/Crud/Prose11_BulkWriteBatchSplitsWhenNamespaceExceedsMessageSizeTest.php b/tests/SpecTests/Crud/Prose11_BulkWriteBatchSplitsWhenNamespaceExceedsMessageSizeTest.php new file mode 100644 index 000000000..ae6a46d5d --- /dev/null +++ b/tests/SpecTests/Crud/Prose11_BulkWriteBatchSplitsWhenNamespaceExceedsMessageSizeTest.php @@ -0,0 +1,123 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + + $this->client = self::createTestClient(); + + $hello = $this->getPrimaryServer()->getInfo(); + self::assertIsInt($maxBsonObjectSize = $hello['maxBsonObjectSize'] ?? null); + self::assertIsInt($maxMessageSizeBytes = $hello['maxMessageSizeBytes'] ?? null); + + $opsBytes = $maxMessageSizeBytes - 1122; + $this->numModels = (int) ($opsBytes / $maxBsonObjectSize); + $remainderBytes = $opsBytes % $maxBsonObjectSize; + + // Use namespaces specific to the test, as they are relevant to batch calculations + $this->dropCollection('db', 'coll'); + $collection = $this->client->selectCollection('db', 'coll'); + + $this->bulkWrite = ClientBulkWrite::createWithCollection($collection); + + for ($i = 0; $i < $this->numModels; ++$i) { + $this->bulkWrite->insertOne(['a' => str_repeat('b', $maxBsonObjectSize - 57)]); + } + + if ($remainderBytes >= 217) { + ++$this->numModels; + $this->bulkWrite->insertOne(['a' => str_repeat('b', $remainderBytes - 57)]); + } + } + + public function testNoBatchSplittingRequired(): void + { + $subscriber = $this->createSubscriber(); + $this->client->addSubscriber($subscriber); + + $this->bulkWrite->insertOne(['a' => 'b']); + + $result = $this->client->bulkWrite($this->bulkWrite); + + self::assertSame($this->numModels + 1, $result->getInsertedCount()); + self::assertCount(1, $subscriber->commandStartedEvents); + $command = $subscriber->commandStartedEvents[0]->getCommand(); + self::assertCount($this->numModels + 1, $command->ops); + self::assertCount(1, $command->nsInfo); + self::assertSame('db.coll', $command->nsInfo[0]->ns ?? null); + } + + public function testBatchSplittingRequired(): void + { + $subscriber = $this->createSubscriber(); + $this->client->addSubscriber($subscriber); + + $secondCollectionName = str_repeat('c', 200); + $this->dropCollection('db', $secondCollectionName); + $secondCollection = $this->client->selectCollection('db', $secondCollectionName); + $this->bulkWrite->withCollection($secondCollection)->insertOne(['a' => 'b']); + + $result = $this->client->bulkWrite($this->bulkWrite); + + self::assertSame($this->numModels + 1, $result->getInsertedCount()); + self::assertCount(2, $subscriber->commandStartedEvents); + [$firstEvent, $secondEvent] = $subscriber->commandStartedEvents; + + $firstCommand = $firstEvent->getCommand(); + self::assertCount($this->numModels, $firstCommand->ops); + self::assertCount(1, $firstCommand->nsInfo); + self::assertSame('db.coll', $firstCommand->nsInfo[0]->ns ?? null); + + $secondCommand = $secondEvent->getCommand(); + self::assertCount(1, $secondCommand->ops); + self::assertCount(1, $secondCommand->nsInfo); + self::assertSame($secondCollection->getNamespace(), $secondCommand->nsInfo[0]->ns ?? null); + } + + private function createSubscriber(): CommandSubscriber + { + return new class implements CommandSubscriber { + public array $commandStartedEvents = []; + + public function commandStarted(CommandStartedEvent $event): void + { + if ($event->getCommandName() === 'bulkWrite') { + $this->commandStartedEvents[] = $event; + } + } + + public function commandSucceeded(CommandSucceededEvent $event): void + { + } + + public function commandFailed(CommandFailedEvent $event): void + { + } + }; + } +} diff --git a/tests/SpecTests/Crud/Prose12_BulkWriteExceedsMaxMessageSizeBytesTest.php b/tests/SpecTests/Crud/Prose12_BulkWriteExceedsMaxMessageSizeBytesTest.php new file mode 100644 index 000000000..4fbe5adcd --- /dev/null +++ b/tests/SpecTests/Crud/Prose12_BulkWriteExceedsMaxMessageSizeBytesTest.php @@ -0,0 +1,63 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + } + + public function testDocumentTooLarge(): void + { + $client = self::createTestClient(); + + $maxMessageSizeBytes = $this->getPrimaryServer()->getInfo()['maxMessageSizeBytes'] ?? null; + self::assertIsInt($maxMessageSizeBytes); + + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + $bulkWrite = ClientBulkWrite::createWithCollection($collection); + $bulkWrite->insertOne(['a' => str_repeat('b', $maxMessageSizeBytes)]); + + try { + $client->bulkWrite($bulkWrite); + self::fail('Exception was not thrown'); + } catch (InvalidArgumentException $e) { + self::assertStringContainsString('unable to send document', $e->getMessage()); + } + } + + public function testNamespaceTooLarge(): void + { + $client = self::createTestClient(); + + $maxMessageSizeBytes = $this->getPrimaryServer()->getInfo()['maxMessageSizeBytes'] ?? null; + self::assertIsInt($maxMessageSizeBytes); + + $collectionName = str_repeat('c', $maxMessageSizeBytes); + $collection = $client->selectCollection($this->getDatabaseName(), $collectionName); + $bulkWrite = ClientBulkWrite::createWithCollection($collection); + $bulkWrite->insertOne(['a' => 'b']); + + try { + $client->bulkWrite($bulkWrite); + self::fail('Exception was not thrown'); + } catch (InvalidArgumentException $e) { + self::assertStringContainsString('unable to send document', $e->getMessage()); + } + } +} diff --git a/tests/SpecTests/Crud/Prose13_BulkWriteUnsupportedForAutoEncryptionTest.php b/tests/SpecTests/Crud/Prose13_BulkWriteUnsupportedForAutoEncryptionTest.php new file mode 100644 index 000000000..c59078f5f --- /dev/null +++ b/tests/SpecTests/Crud/Prose13_BulkWriteUnsupportedForAutoEncryptionTest.php @@ -0,0 +1,40 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + + $this->skipIfClientSideEncryptionIsNotSupported(); + + $client = self::createTestClient(null, [], [ + 'autoEncryption' => [ + 'keyVaultNamespace' => $this->getNamespace(), + 'kmsProviders' => ['aws' => ['accessKeyId' => 'foo', 'secretAccessKey' => 'bar']], + ], + ]); + + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + $bulkWrite = ClientBulkWrite::createWithCollection($collection); + $bulkWrite->insertOne(['a' => 'b']); + + try { + $client->bulkWrite($bulkWrite); + self::fail('InvalidArgumentException was not thrown'); + } catch (InvalidArgumentException $e) { + self::assertStringContainsString('bulkWrite does not currently support automatic encryption', $e->getMessage()); + } + } +} diff --git a/tests/SpecTests/Crud/Prose15_BulkWriteUnacknowledgedWriteConcernTest.php b/tests/SpecTests/Crud/Prose15_BulkWriteUnacknowledgedWriteConcernTest.php new file mode 100644 index 000000000..f7aa6b5bc --- /dev/null +++ b/tests/SpecTests/Crud/Prose15_BulkWriteUnacknowledgedWriteConcernTest.php @@ -0,0 +1,83 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + + $client = self::createTestClient(); + + $hello = $this->getPrimaryServer()->getInfo(); + self::assertIsInt($maxBsonObjectSize = $hello['maxBsonObjectSize'] ?? null); + self::assertIsInt($maxMessageSizeBytes = $hello['maxMessageSizeBytes'] ?? null); + + // Explicitly create the collection to work around SERVER-95537 + $this->createCollection($this->getDatabaseName(), $this->getCollectionName()); + + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + $bulkWrite = ClientBulkWrite::createWithCollection($collection, ['ordered' => false]); + + $numModels = (int) ($maxMessageSizeBytes / $maxBsonObjectSize) + 1; + + for ($i = 0; $i < $numModels; ++$i) { + $bulkWrite->insertOne(['a' => str_repeat('b', $maxBsonObjectSize - 500)]); + } + + $subscriber = new class implements CommandSubscriber { + public array $commandStartedEvents = []; + + public function commandStarted(CommandStartedEvent $event): void + { + if ($event->getCommandName() === 'bulkWrite') { + $this->commandStartedEvents[] = $event; + } + } + + public function commandSucceeded(CommandSucceededEvent $event): void + { + } + + public function commandFailed(CommandFailedEvent $event): void + { + } + }; + + $client->addSubscriber($subscriber); + + $result = $client->bulkWrite($bulkWrite, ['writeConcern' => new WriteConcern(0)]); + + self::assertFalse($result->isAcknowledged()); + self::assertCount(2, $subscriber->commandStartedEvents); + [$firstEvent, $secondEvent] = $subscriber->commandStartedEvents; + + $firstCommand = $firstEvent->getCommand(); + self::assertIsArray($firstCommand->ops ?? null); + self::assertCount($numModels - 1, $firstCommand->ops); + self::assertSame(0, $firstCommand->writeConcern->w ?? null); + + $secondCommand = $secondEvent->getCommand(); + self::assertIsArray($secondCommand->ops ?? null); + self::assertCount(1, $secondCommand->ops); + self::assertSame(0, $secondCommand->writeConcern->w ?? null); + + self::assertSame($numModels, $collection->countDocuments()); + } +} diff --git a/tests/SpecTests/Crud/Prose3_BulkWriteSplitsOnMaxWriteBatchSizeTest.php b/tests/SpecTests/Crud/Prose3_BulkWriteSplitsOnMaxWriteBatchSizeTest.php new file mode 100644 index 000000000..4173bffcf --- /dev/null +++ b/tests/SpecTests/Crud/Prose3_BulkWriteSplitsOnMaxWriteBatchSizeTest.php @@ -0,0 +1,68 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + + $client = self::createTestClient(); + + $maxWriteBatchSize = $this->getPrimaryServer()->getInfo()['maxWriteBatchSize'] ?? null; + self::assertIsInt($maxWriteBatchSize); + + $this->dropCollection($this->getDatabaseName(), $this->getCollectionName()); + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + $bulkWrite = ClientBulkWrite::createWithCollection($collection); + + for ($i = 0; $i < $maxWriteBatchSize + 1; ++$i) { + $bulkWrite->insertOne(['a' => 'b']); + } + + $subscriber = new class implements CommandSubscriber { + public array $commandStartedEvents = []; + + public function commandStarted(CommandStartedEvent $event): void + { + if ($event->getCommandName() === 'bulkWrite') { + $this->commandStartedEvents[] = $event; + } + } + + public function commandSucceeded(CommandSucceededEvent $event): void + { + } + + public function commandFailed(CommandFailedEvent $event): void + { + } + }; + + $client->addSubscriber($subscriber); + + $result = $client->bulkWrite($bulkWrite); + + self::assertSame($maxWriteBatchSize + 1, $result->getInsertedCount()); + self::assertCount(2, $subscriber->commandStartedEvents); + [$firstEvent, $secondEvent] = $subscriber->commandStartedEvents; + self::assertIsArray($firstCommandOps = $firstEvent->getCommand()->ops ?? null); + self::assertCount($maxWriteBatchSize, $firstCommandOps); + self::assertIsArray($secondCommandOps = $secondEvent->getCommand()->ops ?? null); + self::assertCount(1, $secondCommandOps); + self::assertEquals($firstEvent->getOperationId(), $secondEvent->getOperationId()); + } +} diff --git a/tests/SpecTests/Crud/Prose4_BulkWriteSplitsOnMaxMessageSizeBytesTest.php b/tests/SpecTests/Crud/Prose4_BulkWriteSplitsOnMaxMessageSizeBytesTest.php new file mode 100644 index 000000000..74678cedc --- /dev/null +++ b/tests/SpecTests/Crud/Prose4_BulkWriteSplitsOnMaxMessageSizeBytesTest.php @@ -0,0 +1,74 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + + $client = self::createTestClient(); + + $hello = $this->getPrimaryServer()->getInfo(); + self::assertIsInt($maxBsonObjectSize = $hello['maxBsonObjectSize'] ?? null); + self::assertIsInt($maxMessageSizeBytes = $hello['maxMessageSizeBytes'] ?? null); + + $numModels = (int) ($maxMessageSizeBytes / $maxBsonObjectSize + 1); + $document = ['a' => str_repeat('b', $maxBsonObjectSize - 500)]; + + $this->dropCollection($this->getDatabaseName(), $this->getCollectionName()); + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + $bulkWrite = ClientBulkWrite::createWithCollection($collection); + + for ($i = 0; $i < $numModels; ++$i) { + $bulkWrite->insertOne($document); + } + + $subscriber = new class implements CommandSubscriber { + public array $commandStartedEvents = []; + + public function commandStarted(CommandStartedEvent $event): void + { + if ($event->getCommandName() === 'bulkWrite') { + $this->commandStartedEvents[] = $event; + } + } + + public function commandSucceeded(CommandSucceededEvent $event): void + { + } + + public function commandFailed(CommandFailedEvent $event): void + { + } + }; + + $client->addSubscriber($subscriber); + + $result = $client->bulkWrite($bulkWrite); + + self::assertSame($numModels, $result->getInsertedCount()); + self::assertCount(2, $subscriber->commandStartedEvents); + [$firstEvent, $secondEvent] = $subscriber->commandStartedEvents; + self::assertIsArray($firstCommandOps = $firstEvent->getCommand()->ops ?? null); + self::assertCount($numModels - 1, $firstCommandOps); + self::assertIsArray($secondCommandOps = $secondEvent->getCommand()->ops ?? null); + self::assertCount(1, $secondCommandOps); + self::assertEquals($firstEvent->getOperationId(), $secondEvent->getOperationId()); + } +} diff --git a/tests/SpecTests/Crud/Prose5_BulkWriteCollectsWriteConcernErrorsAcrossBatchesTest.php b/tests/SpecTests/Crud/Prose5_BulkWriteCollectsWriteConcernErrorsAcrossBatchesTest.php new file mode 100644 index 000000000..8a0943db7 --- /dev/null +++ b/tests/SpecTests/Crud/Prose5_BulkWriteCollectsWriteConcernErrorsAcrossBatchesTest.php @@ -0,0 +1,81 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + + $client = self::createTestClient(null, ['retryWrites' => false]); + + $maxWriteBatchSize = $this->getPrimaryServer()->getInfo()['maxWriteBatchSize'] ?? null; + self::assertIsInt($maxWriteBatchSize); + + $this->configureFailPoint([ + 'configureFailPoint' => 'failCommand', + 'mode' => ['times' => 2], + 'data' => [ + 'failCommands' => ['bulkWrite'], + 'writeConcernError' => [ + 'code' => 91, // ShutdownInProgress + 'errmsg' => 'Replication is being shut down', + ], + ], + ]); + + $this->dropCollection($this->getDatabaseName(), $this->getCollectionName()); + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + $bulkWrite = ClientBulkWrite::createWithCollection($collection); + + for ($i = 0; $i < $maxWriteBatchSize + 1; ++$i) { + $bulkWrite->insertOne(['a' => 'b']); + } + + $subscriber = new class implements CommandSubscriber { + public int $numBulkWriteObserved = 0; + + public function commandStarted(CommandStartedEvent $event): void + { + if ($event->getCommandName() === 'bulkWrite') { + ++$this->numBulkWriteObserved; + } + } + + public function commandSucceeded(CommandSucceededEvent $event): void + { + } + + public function commandFailed(CommandFailedEvent $event): void + { + } + }; + + $client->addSubscriber($subscriber); + + try { + $client->bulkWrite($bulkWrite); + self::fail('BulkWriteCommandException was not thrown'); + } catch (BulkWriteCommandException $e) { + self::assertCount(2, $e->getWriteConcernErrors()); + $partialResult = $e->getPartialResult(); + self::assertNotNull($partialResult); + self::assertSame($maxWriteBatchSize + 1, $partialResult->getInsertedCount()); + self::assertSame(2, $subscriber->numBulkWriteObserved); + } + } +} diff --git a/tests/SpecTests/Crud/Prose6_BulkWriteHandlesWriteErrorsAcrossBatchesTest.php b/tests/SpecTests/Crud/Prose6_BulkWriteHandlesWriteErrorsAcrossBatchesTest.php new file mode 100644 index 000000000..8ea8bcf05 --- /dev/null +++ b/tests/SpecTests/Crud/Prose6_BulkWriteHandlesWriteErrorsAcrossBatchesTest.php @@ -0,0 +1,106 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + } + + public function testOrdered(): void + { + $client = self::createTestClient(); + + $maxWriteBatchSize = $this->getPrimaryServer()->getInfo()['maxWriteBatchSize'] ?? null; + self::assertIsInt($maxWriteBatchSize); + + $this->dropCollection($this->getDatabaseName(), $this->getCollectionName()); + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + $collection->insertOne(['_id' => 1]); + + $bulkWrite = ClientBulkWrite::createWithCollection($collection, ['ordered' => true]); + + for ($i = 0; $i < $maxWriteBatchSize + 1; ++$i) { + $bulkWrite->insertOne(['_id' => 1]); + } + + $subscriber = $this->createSubscriber(); + $client->addSubscriber($subscriber); + + try { + $client->bulkWrite($bulkWrite); + self::fail('BulkWriteCommandException was not thrown'); + } catch (BulkWriteCommandException $e) { + self::assertCount(1, $e->getWriteErrors()); + self::assertSame(1, $subscriber->numBulkWriteObserved); + } + } + + public function testUnordered(): void + { + $client = self::createTestClient(); + + $maxWriteBatchSize = $this->getPrimaryServer()->getInfo()['maxWriteBatchSize'] ?? null; + self::assertIsInt($maxWriteBatchSize); + + $this->dropCollection($this->getDatabaseName(), $this->getCollectionName()); + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + $collection->insertOne(['_id' => 1]); + + $bulkWrite = ClientBulkWrite::createWithCollection($collection, ['ordered' => false]); + + for ($i = 0; $i < $maxWriteBatchSize + 1; ++$i) { + $bulkWrite->insertOne(['_id' => 1]); + } + + $subscriber = $this->createSubscriber(); + $client->addSubscriber($subscriber); + + try { + $client->bulkWrite($bulkWrite); + self::fail('BulkWriteCommandException was not thrown'); + } catch (BulkWriteCommandException $e) { + self::assertCount($maxWriteBatchSize + 1, $e->getWriteErrors()); + self::assertSame(2, $subscriber->numBulkWriteObserved); + } + } + + private function createSubscriber(): CommandSubscriber + { + return new class implements CommandSubscriber { + public int $numBulkWriteObserved = 0; + + public function commandStarted(CommandStartedEvent $event): void + { + if ($event->getCommandName() === 'bulkWrite') { + ++$this->numBulkWriteObserved; + } + } + + public function commandSucceeded(CommandSucceededEvent $event): void + { + } + + public function commandFailed(CommandFailedEvent $event): void + { + } + }; + } +} diff --git a/tests/SpecTests/Crud/Prose7_BulkWriteHandlesCursorRequiringGetMoreTest.php b/tests/SpecTests/Crud/Prose7_BulkWriteHandlesCursorRequiringGetMoreTest.php new file mode 100644 index 000000000..29149babe --- /dev/null +++ b/tests/SpecTests/Crud/Prose7_BulkWriteHandlesCursorRequiringGetMoreTest.php @@ -0,0 +1,72 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + + $client = self::createTestClient(); + + $maxBsonObjectSize = $this->getPrimaryServer()->getInfo()['maxBsonObjectSize'] ?? null; + self::assertIsInt($maxBsonObjectSize); + + $this->dropCollection($this->getDatabaseName(), $this->getCollectionName()); + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + + $bulkWrite = ClientBulkWrite::createWithCollection($collection, ['verboseResults' => true]); + $bulkWrite->updateOne( + ['_id' => str_repeat('a', (int) ($maxBsonObjectSize / 2))], + ['$set' => ['x' => 1]], + ['upsert' => true], + ); + $bulkWrite->updateOne( + ['_id' => str_repeat('b', (int) ($maxBsonObjectSize / 2))], + ['$set' => ['x' => 1]], + ['upsert' => true], + ); + + $subscriber = new class implements CommandSubscriber { + public int $numGetMoreObserved = 0; + + public function commandStarted(CommandStartedEvent $event): void + { + if ($event->getCommandName() === 'getMore') { + ++$this->numGetMoreObserved; + } + } + + public function commandSucceeded(CommandSucceededEvent $event): void + { + } + + public function commandFailed(CommandFailedEvent $event): void + { + } + }; + + $client->addSubscriber($subscriber); + + $result = $client->bulkWrite($bulkWrite); + + self::assertSame(2, $result->getUpsertedCount()); + self::assertCount(2, $result->getUpdateResults()); + self::assertSame(1, $subscriber->numGetMoreObserved); + } +} diff --git a/tests/SpecTests/Crud/Prose8_BulkWriteHandlesCursorRequiringGetMoreWithinTransactionTest.php b/tests/SpecTests/Crud/Prose8_BulkWriteHandlesCursorRequiringGetMoreWithinTransactionTest.php new file mode 100644 index 000000000..3c4d64d98 --- /dev/null +++ b/tests/SpecTests/Crud/Prose8_BulkWriteHandlesCursorRequiringGetMoreWithinTransactionTest.php @@ -0,0 +1,78 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + $this->skipIfTransactionsAreNotSupported(); + + $client = self::createTestClient(); + + $maxBsonObjectSize = $this->getPrimaryServer()->getInfo()['maxBsonObjectSize'] ?? null; + self::assertIsInt($maxBsonObjectSize); + + $this->dropCollection($this->getDatabaseName(), $this->getCollectionName()); + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + + $bulkWrite = ClientBulkWrite::createWithCollection($collection, ['verboseResults' => true]); + $bulkWrite->updateOne( + ['_id' => str_repeat('a', (int) ($maxBsonObjectSize / 2))], + ['$set' => ['x' => 1]], + ['upsert' => true], + ); + $bulkWrite->updateOne( + ['_id' => str_repeat('b', (int) ($maxBsonObjectSize / 2))], + ['$set' => ['x' => 1]], + ['upsert' => true], + ); + + $subscriber = new class implements CommandSubscriber { + public int $numGetMoreObserved = 0; + + public function commandStarted(CommandStartedEvent $event): void + { + if ($event->getCommandName() === 'getMore') { + ++$this->numGetMoreObserved; + } + } + + public function commandSucceeded(CommandSucceededEvent $event): void + { + } + + public function commandFailed(CommandFailedEvent $event): void + { + } + }; + + $client->addSubscriber($subscriber); + + $session = $client->startSession(); + $session->startTransaction(); + + /* Note: the prose test does not call for committing the transaction. + * The transaction will be aborted when the Session object is freed. */ + $result = $client->bulkWrite($bulkWrite, ['session' => $session]); + + self::assertSame(2, $result->getUpsertedCount()); + self::assertCount(2, $result->getUpdateResults()); + self::assertSame(1, $subscriber->numGetMoreObserved); + } +} diff --git a/tests/SpecTests/Crud/Prose9_BulkWriteHandlesGetMoreErrorTest.php b/tests/SpecTests/Crud/Prose9_BulkWriteHandlesGetMoreErrorTest.php new file mode 100644 index 000000000..903bf486b --- /dev/null +++ b/tests/SpecTests/Crud/Prose9_BulkWriteHandlesGetMoreErrorTest.php @@ -0,0 +1,100 @@ +skipIfServerVersion('<', '8.0', 'bulkWrite command is not supported'); + + $client = self::createTestClient(); + + $maxBsonObjectSize = $this->getPrimaryServer()->getInfo()['maxBsonObjectSize'] ?? null; + self::assertIsInt($maxBsonObjectSize); + + $this->configureFailPoint([ + 'configureFailPoint' => 'failCommand', + 'mode' => ['times' => 1], + 'data' => [ + 'failCommands' => ['getMore'], + 'errorCode' => self::UNKNOWN_ERROR, + ], + ]); + + $this->dropCollection($this->getDatabaseName(), $this->getCollectionName()); + $collection = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName()); + + $bulkWrite = ClientBulkWrite::createWithCollection($collection, ['verboseResults' => true]); + $bulkWrite->updateOne( + ['_id' => str_repeat('a', (int) ($maxBsonObjectSize / 2))], + ['$set' => ['x' => 1]], + ['upsert' => true], + ); + $bulkWrite->updateOne( + ['_id' => str_repeat('b', (int) ($maxBsonObjectSize / 2))], + ['$set' => ['x' => 1]], + ['upsert' => true], + ); + + $subscriber = new class implements CommandSubscriber { + public int $numGetMoreObserved = 0; + public int $numKillCursorsObserved = 0; + + public function commandStarted(CommandStartedEvent $event): void + { + if ($event->getCommandName() === 'getMore') { + ++$this->numGetMoreObserved; + } elseif ($event->getCommandName() === 'killCursors') { + ++$this->numKillCursorsObserved; + } + } + + public function commandSucceeded(CommandSucceededEvent $event): void + { + } + + public function commandFailed(CommandFailedEvent $event): void + { + } + }; + + $client->addSubscriber($subscriber); + + try { + $client->bulkWrite($bulkWrite); + self::fail('BulkWriteCommandException was not thrown'); + } catch (BulkWriteCommandException $e) { + $errorReply = $e->getErrorReply(); + $this->assertNotNull($errorReply); + $this->assertSame(self::UNKNOWN_ERROR, $errorReply['code'] ?? null); + + // PHPC will also apply the top-level error code to BulkWriteCommandException + $this->assertSame(self::UNKNOWN_ERROR, $e->getCode()); + + $partialResult = $e->getPartialResult(); + self::assertNotNull($partialResult); + self::assertSame(2, $partialResult->getUpsertedCount()); + self::assertCount(1, $partialResult->getUpdateResults()); + self::assertSame(1, $subscriber->numGetMoreObserved); + self::assertSame(1, $subscriber->numKillCursorsObserved); + } + } +} diff --git a/tests/SpecTests/FunctionalTestCase.php b/tests/SpecTests/FunctionalTestCase.php index 69ffcf3cf..6ee6d0e46 100644 --- a/tests/SpecTests/FunctionalTestCase.php +++ b/tests/SpecTests/FunctionalTestCase.php @@ -97,7 +97,7 @@ public static function assertDocumentsMatch(array|object $expectedDocument, arra * * Note: this method may modify the $expected object. * - * @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.rst#null-values + * @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.md#null-values * @see https://github.com/mongodb/specifications/blob/09ee1ebc481f1502e3246971a9419e484d736207/source/command-monitoring/tests/README.rst#additional-values */ protected static function assertCommandOmittedFields(stdClass $expected, stdClass $actual): void diff --git a/tests/SpecTests/Operation.php b/tests/SpecTests/Operation.php index 3672314cf..d605a52aa 100644 --- a/tests/SpecTests/Operation.php +++ b/tests/SpecTests/Operation.php @@ -12,9 +12,9 @@ use MongoDB\Driver\Server; use MongoDB\Driver\Session; use MongoDB\GridFS\Bucket; -use MongoDB\MapReduceResult; use MongoDB\Operation\FindOneAndReplace; use MongoDB\Operation\FindOneAndUpdate; +use PHPUnit\Framework\Assert; use stdClass; use function array_diff_key; @@ -183,10 +183,6 @@ public function assert(FunctionalTestCase $test, Context $context, bool $bubbleE * is not used (e.g. Command Monitoring spec). */ if ($result instanceof Cursor) { $result = $result->toArray(); - } elseif ($result instanceof MapReduceResult) { - /* For mapReduce operations, we ignore the mapReduce metadata - * and only return the result iterator for evaluation. */ - $result = iterator_to_array($result->getIterator()); } } catch (Exception $e) { $exception = $e; @@ -411,12 +407,8 @@ private function executeForCollection(Collection $collection, Context $context): return $collection->listIndexes($args); case 'mapReduce': - return $collection->mapReduce( - $args['map'], - $args['reduce'], - $args['out'], - array_diff_key($args, ['map' => 1, 'reduce' => 1, 'out' => 1]), - ); + Assert::markTestSkipped('mapReduce is not supported'); + break; case 'watch': return $collection->watch( diff --git a/tests/SpecTests/ResultExpectation.php b/tests/SpecTests/ResultExpectation.php index 1a28ba123..b4db23cca 100644 --- a/tests/SpecTests/ResultExpectation.php +++ b/tests/SpecTests/ResultExpectation.php @@ -328,7 +328,7 @@ private static function isArrayOfObjects($array) /** * Determines whether the result is actually an error expectation. * - * @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.rst#test-format + * @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.md#test-format */ private static function isErrorResult(mixed $result): bool { diff --git a/tests/SpecTests/SearchIndexSpecTest.php b/tests/SpecTests/SearchIndexSpecTest.php index 4fff82ef7..6f8d3b144 100644 --- a/tests/SpecTests/SearchIndexSpecTest.php +++ b/tests/SpecTests/SearchIndexSpecTest.php @@ -19,7 +19,7 @@ /** * Functional tests for the Atlas Search index management. * - * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#search-index-management-helpers + * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.md#search-index-management-helpers */ #[Group('atlas')] class SearchIndexSpecTest extends FunctionalTestCase @@ -37,7 +37,7 @@ public function setUp(): void /** * Case 1: Driver can successfully create and list search indexes * - * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#case-1-driver-can-successfully-create-and-list-search-indexes + * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.md#case-1-driver-can-successfully-create-and-list-search-indexes */ public function testCreateAndListSearchIndexes(): void { @@ -61,7 +61,7 @@ public function testCreateAndListSearchIndexes(): void /** * Case 2: Driver can successfully create multiple indexes in batch * - * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#case-2-driver-can-successfully-create-multiple-indexes-in-batch + * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.md#case-2-driver-can-successfully-create-multiple-indexes-in-batch */ public function testCreateMultipleIndexesInBatch(): void { @@ -88,7 +88,7 @@ public function testCreateMultipleIndexesInBatch(): void /** * Case 3: Driver can successfully drop search indexes * - * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#case-3-driver-can-successfully-drop-search-indexes + * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.md#case-3-driver-can-successfully-drop-search-indexes */ public function testDropSearchIndexes(): void { @@ -114,7 +114,7 @@ public function testDropSearchIndexes(): void /** * Case 4: Driver can update a search index * - * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#case-4-driver-can-update-a-search-index + * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.md#case-4-driver-can-update-a-search-index */ public function testUpdateSearchIndex(): void { @@ -144,7 +144,7 @@ public function testUpdateSearchIndex(): void /** * Case 5: dropSearchIndex suppresses namespace not found errors * - * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.rst#case-5-dropsearchindex-suppresses-namespace-not-found-errors + * @see https://github.com/mongodb/specifications/blob/master/source/index-management/tests/README.md#case-5-dropsearchindex-suppresses-namespace-not-found-errors */ public function testDropSearchIndexSuppressNamespaceNotFoundError(): void { diff --git a/tests/SpecTests/TransactionsSpecTest.php b/tests/SpecTests/TransactionsSpecTest.php index 8053bd4ac..148f29d0f 100644 --- a/tests/SpecTests/TransactionsSpecTest.php +++ b/tests/SpecTests/TransactionsSpecTest.php @@ -10,7 +10,7 @@ /** * Transactions spec prose tests. * - * @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.rst#mongos-pinning-prose-tests + * @see https://github.com/mongodb/specifications/blob/master/source/transactions/tests/README.md#mongos-pinning-prose-tests */ class TransactionsSpecTest extends FunctionalTestCase { diff --git a/tests/UnifiedSpecTests/CollectionData.php b/tests/UnifiedSpecTests/CollectionData.php index 3ac6525f9..b7ca2f353 100644 --- a/tests/UnifiedSpecTests/CollectionData.php +++ b/tests/UnifiedSpecTests/CollectionData.php @@ -14,8 +14,10 @@ use function PHPUnit\Framework\assertContainsOnly; use function PHPUnit\Framework\assertIsArray; +use function PHPUnit\Framework\assertIsObject; use function PHPUnit\Framework\assertIsString; use function PHPUnit\Framework\assertNotNull; +use function PHPUnit\Framework\assertObjectNotHasProperty; use function PHPUnit\Framework\assertThat; use function sprintf; @@ -27,6 +29,8 @@ class CollectionData private array $documents; + private array $createOptions = []; + public function __construct(stdClass $o) { assertIsString($o->collectionName); @@ -38,6 +42,15 @@ public function __construct(stdClass $o) assertIsArray($o->documents); assertContainsOnly('object', $o->documents); $this->documents = $o->documents; + + if (isset($o->createOptions)) { + assertIsObject($o->createOptions); + /* The writeConcern option is prohibited here, as prepareInitialData() applies w:majority. Since a session + * option would be ignored by prepareInitialData() we can assert that it is also omitted. */ + assertObjectNotHasProperty('writeConcern', $o->createOptions); + assertObjectNotHasProperty('session', $o->createOptions); + $this->createOptions = (array) $o->createOptions; + } } public function prepareInitialData(Client $client, ?Session $session = null): void @@ -49,13 +62,13 @@ public function prepareInitialData(Client $client, ?Session $session = null): vo $database->dropCollection($this->collectionName, ['session' => $session]); - if (empty($this->documents)) { - $database->createCollection($this->collectionName, ['session' => $session]); - - return; + if (empty($this->documents) || ! empty($this->createOptions)) { + $database->createCollection($this->collectionName, ['session' => $session] + $this->createOptions); } - $database->selectCollection($this->collectionName)->insertMany($this->documents, ['session' => $session]); + if (! empty($this->documents)) { + $database->selectCollection($this->collectionName)->insertMany($this->documents, ['session' => $session]); + } } public function assertOutcome(Client $client): void diff --git a/tests/UnifiedSpecTests/Constraint/Matches.php b/tests/UnifiedSpecTests/Constraint/Matches.php index b793614a4..a6bda1420 100644 --- a/tests/UnifiedSpecTests/Constraint/Matches.php +++ b/tests/UnifiedSpecTests/Constraint/Matches.php @@ -4,6 +4,8 @@ use LogicException; use MongoDB\BSON\Document; +use MongoDB\BSON\Int64; +use MongoDB\BSON\PackedArray; use MongoDB\BSON\Serializable; use MongoDB\BSON\Type; use MongoDB\Model\BSONArray; @@ -30,10 +32,12 @@ use function PHPUnit\Framework\assertIsBool; use function PHPUnit\Framework\assertIsString; use function PHPUnit\Framework\assertJson; +use function PHPUnit\Framework\assertLessThanOrEqual; use function PHPUnit\Framework\assertMatchesRegularExpression; use function PHPUnit\Framework\assertNotNull; use function PHPUnit\Framework\assertStringStartsWith; use function PHPUnit\Framework\assertThat; +use function PHPUnit\Framework\assertTrue; use function PHPUnit\Framework\containsOnly; use function PHPUnit\Framework\isInstanceOf; use function PHPUnit\Framework\isType; @@ -352,6 +356,14 @@ private function assertMatchesOperator(BSONDocument $operator, $actual, string $ return; } + if ($name === '$$lte') { + assertTrue(self::isNumeric($operator['$$lte']), '$$lte requires number'); + assertTrue(self::isNumeric($actual), '$actual operand for $$lte should be a number'); + assertLessThanOrEqual($operator['$$lte'], $actual); + + return; + } + throw new LogicException('unsupported operator: ' . $name); } @@ -446,8 +458,14 @@ private static function prepare(mixed $bson): mixed return self::prepare($bson->bsonSerialize()); } - /* Serializable has already been handled, so any remaining instances of - * Type will not serialize as BSON arrays or objects */ + // Recurse on the PHP representation of Document and PackedArray types + if ($bson instanceof Document || $bson instanceof PackedArray) { + return self::prepare($bson->toPHP()); + } + + /* Serializable, Document, and PackedArray have already been handled. + * Any remaining Type instances will not serialize as BSON arrays or + * objects. */ if ($bson instanceof Type) { return $bson; } diff --git a/tests/UnifiedSpecTests/Context.php b/tests/UnifiedSpecTests/Context.php index fde8e3ee4..0faa0611f 100644 --- a/tests/UnifiedSpecTests/Context.php +++ b/tests/UnifiedSpecTests/Context.php @@ -492,6 +492,10 @@ private static function getEnv(string $name): string private static function prepareCollectionOrDatabaseOptions(array $options): array { + if (array_key_exists('timeoutMS', $options)) { + Assert::markTestIncomplete('CSOT is not yet implemented (PHPC-1760)'); + } + Util::assertHasOnlyKeys($options, ['readConcern', 'readPreference', 'writeConcern']); return Util::prepareCommonOptions($options); @@ -509,10 +513,6 @@ private static function prepareBucketOptions(array $options): array assertIsInt($options['chunkSizeBytes']); } - if (array_key_exists('disableMD5', $options)) { - assertIsBool($options['disableMD5']); - } - return Util::prepareCommonOptions($options); } diff --git a/tests/UnifiedSpecTests/EventObserver.php b/tests/UnifiedSpecTests/EventObserver.php index 767216643..aa057fbfd 100644 --- a/tests/UnifiedSpecTests/EventObserver.php +++ b/tests/UnifiedSpecTests/EventObserver.php @@ -42,7 +42,7 @@ final class EventObserver implements CommandSubscriber * These commands are always considered sensitive (i.e. command and reply * documents should be redacted). * - * @see https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.rst#security + * @see https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.md#security */ private static array $sensitiveCommands = [ 'authenticate' => 1, @@ -60,7 +60,7 @@ final class EventObserver implements CommandSubscriber * These commands are only considered sensitive when the command or reply * document includes a speculativeAuthenticate field. * - * @see https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.rst#security + * @see https://github.com/mongodb/specifications/blob/master/source/command-monitoring/command-monitoring.md#security */ private static array $sensitiveCommandsWithSpeculativeAuthenticate = [ 'ismaster' => 1, diff --git a/tests/UnifiedSpecTests/ExpectedError.php b/tests/UnifiedSpecTests/ExpectedError.php index 9d598fa1f..092701479 100644 --- a/tests/UnifiedSpecTests/ExpectedError.php +++ b/tests/UnifiedSpecTests/ExpectedError.php @@ -2,6 +2,7 @@ namespace MongoDB\Tests\UnifiedSpecTests; +use MongoDB\Driver\Exception\BulkWriteCommandException; use MongoDB\Driver\Exception\BulkWriteException; use MongoDB\Driver\Exception\CommandException; use MongoDB\Driver\Exception\ExecutionTimeoutException; @@ -12,6 +13,7 @@ use stdClass; use Throwable; +use function count; use function PHPUnit\Framework\assertArrayHasKey; use function PHPUnit\Framework\assertContainsOnly; use function PHPUnit\Framework\assertCount; @@ -56,7 +58,7 @@ final class ExpectedError private ?string $codeName = null; - private ?Matches $matchesResultDocument = null; + private ?Matches $matchesErrorResponse = null; private array $includedLabels = []; @@ -64,6 +66,10 @@ final class ExpectedError private ?ExpectedResult $expectedResult = null; + private ?array $writeErrors = null; + + private ?array $writeConcernErrors = null; + public function __construct(?stdClass $o, EntityMap $entityMap) { if ($o === null) { @@ -81,6 +87,10 @@ public function __construct(?stdClass $o, EntityMap $entityMap) $this->isClientError = $o->isClientError; } + if (property_exists($o, 'isTimeoutError')) { + Assert::markTestIncomplete('CSOT is not yet implemented (PHPC-1760)'); + } + if (isset($o->errorContains)) { assertIsString($o->errorContains); $this->messageContains = $o->errorContains; @@ -98,7 +108,7 @@ public function __construct(?stdClass $o, EntityMap $entityMap) if (isset($o->errorResponse)) { assertIsObject($o->errorResponse); - $this->matchesResultDocument = new Matches($o->errorResponse, $entityMap); + $this->matchesErrorResponse = new Matches($o->errorResponse, $entityMap); } if (isset($o->errorLabelsContain)) { @@ -116,6 +126,24 @@ public function __construct(?stdClass $o, EntityMap $entityMap) if (property_exists($o, 'expectResult')) { $this->expectedResult = new ExpectedResult($o, $entityMap); } + + if (isset($o->writeErrors)) { + assertIsObject($o->writeErrors); + assertContainsOnly('object', (array) $o->writeErrors); + + foreach ($o->writeErrors as $i => $writeError) { + $this->writeErrors[$i] = new Matches($writeError, $entityMap); + } + } + + if (isset($o->writeConcernErrors)) { + assertIsArray($o->writeConcernErrors); + assertContainsOnly('object', $o->writeConcernErrors); + + foreach ($o->writeConcernErrors as $i => $writeConcernError) { + $this->writeConcernErrors[$i] = new Matches($writeConcernError, $entityMap); + } + } } /** @@ -155,15 +183,21 @@ public function assert(?Throwable $e = null): void $this->assertCodeName($e); } - if (isset($this->matchesResultDocument)) { - assertThat($e, logicalOr(isInstanceOf(CommandException::class), isInstanceOf(BulkWriteException::class))); + if (isset($this->matchesErrorResponse)) { + assertThat($e, logicalOr( + isInstanceOf(CommandException::class), + isInstanceOf(BulkWriteException::class), + isInstanceOf(BulkWriteCommandException::class), + )); if ($e instanceof CommandException) { - assertThat($e->getResultDocument(), $this->matchesResultDocument, 'CommandException result document matches'); + assertThat($e->getResultDocument(), $this->matchesErrorResponse, 'CommandException result document matches expected errorResponse'); + } elseif ($e instanceof BulkWriteCommandException) { + assertThat($e->getErrorReply(), $this->matchesErrorResponse, 'BulkWriteCommandException error reply matches expected errorResponse'); } elseif ($e instanceof BulkWriteException) { $writeErrors = $e->getWriteResult()->getErrorReplies(); assertCount(1, $writeErrors); - assertThat($writeErrors[0], $this->matchesResultDocument, 'BulkWriteException result document matches'); + assertThat($writeErrors[0], $this->matchesErrorResponse, 'BulkWriteException first error reply matches expected errorResponse'); } } @@ -180,16 +214,34 @@ public function assert(?Throwable $e = null): void } if (isset($this->expectedResult)) { - assertInstanceOf(BulkWriteException::class, $e); - $this->expectedResult->assert($e->getWriteResult()); + assertThat($e, logicalOr( + isInstanceOf(BulkWriteException::class), + isInstanceOf(BulkWriteCommandException::class), + )); + + if ($e instanceof BulkWriteCommandException) { + $this->expectedResult->assert($e->getPartialResult()); + } elseif ($e instanceof BulkWriteException) { + $this->expectedResult->assert($e->getWriteResult()); + } + } + + if (isset($this->writeErrors)) { + assertInstanceOf(BulkWriteCommandException::class, $e); + $this->assertWriteErrors($e->getWriteErrors()); + } + + if (isset($this->writeConcernErrors)) { + assertInstanceOf(BulkWriteCommandException::class, $e); + $this->assertWriteConcernErrors($e->getWriteConcernErrors()); } } private function assertIsClientError(Throwable $e): void { - /* Note: BulkWriteException may proxy a previous exception. Unwrap it - * to check the original error. */ - if ($e instanceof BulkWriteException && $e->getPrevious() !== null) { + /* Note: BulkWriteException and BulkWriteCommandException may proxy a + * previous exception. Unwrap it to check the original error. */ + if (($e instanceof BulkWriteException || $e instanceof BulkWriteCommandException) && $e->getPrevious() !== null) { $e = $e->getPrevious(); } @@ -226,4 +278,47 @@ private function assertCodeName(ServerException $e): void assertObjectHasProperty('codeName', $result); assertSame($this->codeName, $result->codeName); } + + private function assertWriteErrors(array $writeErrors): void + { + assertCount(count($this->writeErrors), $writeErrors); + + foreach ($this->writeErrors as $i => $matchesWriteError) { + assertArrayHasKey($i, $writeErrors); + $writeError = $writeErrors[$i]; + + // Not required by the spec test, but asserts PHPC correctness + assertSame((int) $i, $writeError->getIndex()); + + /* Convert the WriteError into a document for matching. These + * field names are derived from the CRUD spec. */ + $writeErrorDocument = [ + 'code' => $writeError->getCode(), + 'message' => $writeError->getMessage(), + 'details' => $writeError->getInfo(), + ]; + + assertThat($writeErrorDocument, $matchesWriteError); + } + } + + private function assertWriteConcernErrors(array $writeConcernErrors): void + { + assertCount(count($this->writeConcernErrors), $writeConcernErrors); + + foreach ($this->writeConcernErrors as $i => $matchesWriteConcernError) { + assertArrayHasKey($i, $writeConcernErrors); + $writeConcernError = $writeConcernErrors[$i]; + + /* Convert the WriteConcernError into a document for matching. + * These field names are derived from the CRUD spec. */ + $writeConcernErrorDocument = [ + 'code' => $writeConcernError->getCode(), + 'message' => $writeConcernError->getMessage(), + 'details' => $writeConcernError->getInfo(), + ]; + + assertThat($writeConcernErrorDocument, $matchesWriteConcernError); + } + } } diff --git a/tests/UnifiedSpecTests/ExpectedResult.php b/tests/UnifiedSpecTests/ExpectedResult.php index 5edc6e3ce..29871c289 100644 --- a/tests/UnifiedSpecTests/ExpectedResult.php +++ b/tests/UnifiedSpecTests/ExpectedResult.php @@ -4,6 +4,7 @@ use MongoDB\BulkWriteResult; use MongoDB\DeleteResult; +use MongoDB\Driver\BulkWriteCommandResult; use MongoDB\Driver\WriteResult; use MongoDB\InsertManyResult; use MongoDB\InsertOneResult; @@ -11,6 +12,7 @@ use MongoDB\UpdateResult; use stdClass; +use function array_filter; use function is_object; use function PHPUnit\Framework\assertThat; use function property_exists; @@ -57,6 +59,10 @@ private static function prepare($value) return $value; } + if ($value instanceof BulkWriteCommandResult) { + return self::prepareBulkWriteCommandResult($value); + } + if ( $value instanceof BulkWriteResult || $value instanceof WriteResult || @@ -71,7 +77,37 @@ private static function prepare($value) return $value; } - private static function prepareWriteResult($value) + private static function prepareBulkWriteCommandResult(BulkWriteCommandResult $result): array + { + $retval = ['acknowledged' => $result->isAcknowledged()]; + + if (! $retval['acknowledged']) { + return $retval; + } + + $retval = [ + 'deletedCount' => $result->getDeletedCount(), + 'insertedCount' => $result->getInsertedCount(), + 'matchedCount' => $result->getMatchedCount(), + 'modifiedCount' => $result->getModifiedCount(), + 'upsertedCount' => $result->getUpsertedCount(), + ]; + + /* Tests use $$unsetOrMatches to expect either no key or an empty + * document when verboseResults=false, so filter out null values. */ + $retval += array_filter( + [ + 'deleteResults' => $result->getDeleteResults()?->toPHP(), + 'insertResults' => $result->getInsertResults()?->toPHP(), + 'updateResults' => $result->getUpdateResults()?->toPHP(), + ], + fn ($value) => $value !== null, + ); + + return $retval; + } + + private static function prepareWriteResult($value): array { $result = ['acknowledged' => $value->isAcknowledged()]; diff --git a/tests/UnifiedSpecTests/Operation.php b/tests/UnifiedSpecTests/Operation.php index d2550f498..8cb4509dd 100644 --- a/tests/UnifiedSpecTests/Operation.php +++ b/tests/UnifiedSpecTests/Operation.php @@ -3,11 +3,11 @@ namespace MongoDB\Tests\UnifiedSpecTests; use Error; -use MongoDB\BSON\Javascript; use MongoDB\ChangeStream; use MongoDB\Client; use MongoDB\Collection; use MongoDB\Database; +use MongoDB\Driver\BulkWriteCommand; use MongoDB\Driver\ClientEncryption; use MongoDB\Driver\Cursor; use MongoDB\Driver\Server; @@ -88,10 +88,7 @@ final class Operation 'assertNumberConnectionsCheckedOut' => 'PHP does not implement CMAP', 'createEntities' => 'createEntities is not implemented (PHPC-1760)', ], - Client::class => [ - 'clientBulkWrite' => 'clientBulkWrite is not implemented (PHPLIB-847)', - 'listDatabaseObjects' => 'listDatabaseObjects is not implemented', - ], + Client::class => ['listDatabaseObjects' => 'listDatabaseObjects is not implemented'], Cursor::class => ['iterateOnce' => 'iterateOnce is not implemented (PHPC-1760)'], Database::class => [ 'createCommandCursor' => 'commandCursor API is not yet implemented (PHPLIB-1077)', @@ -258,6 +255,18 @@ private function executeForClient(Client $client) Util::assertArgumentsBySchema(Client::class, $this->name, $args); switch ($this->name) { + case 'clientBulkWrite': + assertArrayHasKey('models', $args); + assertIsArray($args['models']); + + // Options for ClientBulkWriteCommand and Server::executeBulkWriteCommand() will be mixed + $options = array_diff_key($args, ['models' => 1]); + + return $client->bulkWrite( + self::prepareBulkWriteCommand($args['models'], $options), + $options, + ); + case 'createChangeStream': assertArrayHasKey('pipeline', $args); assertIsArray($args['pipeline']); @@ -526,19 +535,8 @@ private function executeForCollection(Collection $collection) ); case 'mapReduce': - assertArrayHasKey('map', $args); - assertArrayHasKey('reduce', $args); - assertArrayHasKey('out', $args); - assertInstanceOf(Javascript::class, $args['map']); - assertInstanceOf(Javascript::class, $args['reduce']); - assertThat($args['out'], logicalOr(new IsType('string'), new IsType('array'), new IsType('object'))); - - return iterator_to_array($collection->mapReduce( - $args['map'], - $args['reduce'], - $args['out'], - array_diff_key($args, ['map' => 1, 'reduce' => 1, 'out' => 1]), - )); + Assert::markTestSkipped('mapReduce operation is not supported'); + break; case 'rename': assertArrayHasKey('to', $args); @@ -1013,6 +1011,82 @@ private function skipIfOperationIsNotSupported(string $executingObjectName): voi Assert::markTestSkipped($skipReason); } + private static function prepareBulkWriteCommand(array $models, array $options): BulkWriteCommand + { + $bulk = new BulkWriteCommand($options); + + foreach ($models as $model) { + $model = (array) $model; + assertCount(1, $model); + + $type = key($model); + $args = current($model); + assertIsObject($args); + $args = (array) $args; + + assertArrayHasKey('namespace', $args); + assertIsString($args['namespace']); + + switch ($type) { + case 'deleteMany': + case 'deleteOne': + assertArrayHasKey('filter', $args); + assertInstanceOf(stdClass::class, $args['filter']); + + $bulk->{$type}( + $args['namespace'], + $args['filter'], + array_diff_key($args, ['namespace' => 1, 'filter' => 1]), + ); + break; + + case 'insertOne': + assertArrayHasKey('document', $args); + assertInstanceOf(stdClass::class, $args['document']); + + $bulk->insertOne( + $args['namespace'], + $args['document'], + ); + break; + + case 'replaceOne': + assertArrayHasKey('filter', $args); + assertArrayHasKey('replacement', $args); + assertInstanceOf(stdClass::class, $args['filter']); + assertInstanceOf(stdClass::class, $args['replacement']); + + $bulk->replaceOne( + $args['namespace'], + $args['filter'], + $args['replacement'], + array_diff_key($args, ['namespace' => 1, 'filter' => 1, 'replacement' => 1]), + ); + break; + + case 'updateMany': + case 'updateOne': + assertArrayHasKey('filter', $args); + assertArrayHasKey('update', $args); + assertInstanceOf(stdClass::class, $args['filter']); + assertThat($args['update'], logicalOr(new IsType('array'), new IsType('object'))); + + $bulk->{$type}( + $args['namespace'], + $args['filter'], + $args['update'], + array_diff_key($args, ['namespace' => 1, 'filter' => 1, 'update' => 1]), + ); + break; + + default: + Assert::fail('Unsupported bulk write model: ' . $type); + } + } + + return $bulk; + } + private static function prepareBulkWriteRequest(stdClass $request): array { $request = (array) $request; @@ -1038,6 +1112,7 @@ private static function prepareBulkWriteRequest(stdClass $request): array case 'insertOne': assertArrayHasKey('document', $args); + assertInstanceOf(stdClass::class, $args['document']); return ['insertOne' => [$args['document']]]; diff --git a/tests/UnifiedSpecTests/UnifiedSpecTest.php b/tests/UnifiedSpecTests/UnifiedSpecTest.php index fa302acf1..cbf504bc5 100644 --- a/tests/UnifiedSpecTests/UnifiedSpecTest.php +++ b/tests/UnifiedSpecTests/UnifiedSpecTest.php @@ -13,11 +13,12 @@ use function array_flip; use function glob; use function str_starts_with; +use function strtolower; /** * Unified test format spec tests. * - * @see https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.rst + * @see https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md */ class UnifiedSpecTest extends FunctionalTestCase { @@ -29,6 +30,8 @@ class UnifiedSpecTest extends FunctionalTestCase * @var array */ private static array $incompleteTestGroups = [ + // Spec tests for named KMS providers depends on unimplemented functionality from UTF schema 1.18 + 'client-side-encryption/namedKMS' => 'UTF schema 1.18 is not supported (PHPLIB-1328)', // Many load balancer tests use CMAP events and/or assertNumberConnectionsCheckedOut 'load-balancers/cursors are correctly pinned to connections for load-balanced clusters' => 'PHPC does not implement CMAP', 'load-balancers/transactions are correctly pinned to connections for load-balanced clusters' => 'PHPC does not implement CMAP', @@ -38,6 +41,34 @@ class UnifiedSpecTest extends FunctionalTestCase 'retryable-reads/retryable reads handshake failures' => 'Handshakes are not retried (CDRIVER-4532)', 'retryable-writes/retryable writes handshake failures' => 'Handshakes are not retried (CDRIVER-4532)', 'crud/bypassDocumentValidation' => 'bypassDocumentValidation is handled by libmongoc (PHPLIB-1576)', + // The rawData option will not be implemented + 'collection-management/listCollections-rawData' => 'rawData option will not be implemented', + 'crud/aggregate-rawData' => 'rawData option will not be implemented', + 'crud/BulkWrite deleteMany-rawData' => 'rawData option will not be implemented', + 'crud/BulkWrite deleteOne-rawData' => 'rawData option will not be implemented', + 'crud/BulkWrite replaceOne-rawData' => 'rawData option will not be implemented', + 'crud/BulkWrite updateMany-rawData' => 'rawData option will not be implemented', + 'crud/BulkWrite updateOne-rawData' => 'rawData option will not be implemented', + 'crud/client bulkWrite delete-rawData' => 'rawData option will not be implemented', + 'crud/client bulkWrite replaceOne-rawData' => 'rawData option will not be implemented', + 'crud/client bulkWrite update-rawData' => 'rawData option will not be implemented', + 'crud/count-rawData' => 'rawData option will not be implemented', + 'crud/countDocuments-rawData' => 'rawData option will not be implemented', + 'crud/db-aggregate-rawData' => 'rawData option will not be implemented', + 'crud/deleteMany-rawData' => 'rawData option will not be implemented', + 'crud/deleteOne-rawData' => 'rawData option will not be implemented', + 'crud/distinct-rawData' => 'rawData option will not be implemented', + 'crud/estimatedDocumentCount-rawData' => 'rawData option will not be implemented', + 'crud/find-rawData' => 'rawData option will not be implemented', + 'crud/findOneAndDelete-rawData' => 'rawData option will not be implemented', + 'crud/findOneAndReplace-rawData' => 'rawData option will not be implemented', + 'crud/findOneAndUpdate-rawData' => 'rawData option will not be implemented', + 'crud/insertMany-rawData' => 'rawData option will not be implemented', + 'crud/insertOne-rawData' => 'rawData option will not be implemented', + 'crud/replaceOne-rawData' => 'rawData option will not be implemented', + 'crud/updateMany-rawData' => 'rawData option will not be implemented', + 'crud/updateOne-rawData' => 'rawData option will not be implemented', + 'index-management/index management-rawData' => 'rawData option will not be implemented', ]; /** @var array */ @@ -53,9 +84,6 @@ class UnifiedSpecTest extends FunctionalTestCase 'valid-pass/entity-client-cmap-events: events are captured during an operation' => 'PHPC does not implement CMAP', 'valid-pass/expectedEventsForClient-eventType: eventType can be set to command and cmap' => 'PHPC does not implement CMAP', 'valid-pass/expectedEventsForClient-eventType: eventType defaults to command if unset' => 'PHPC does not implement CMAP', - // CSOT is not yet implemented (PHPC-1760) - 'valid-pass/collectionData-createOptions: collection is created with the correct options' => 'CSOT is not yet implemented (PHPC-1760)', - 'valid-pass/operator-lte: special lte matching operator' => 'CSOT is not yet implemented (PHPC-1760)', // libmongoc always adds readConcern to aggregate command 'index-management/search index operations ignore read and write concern: listSearchIndexes ignores read and write concern' => 'libmongoc appends readConcern to aggregate command', // Uses an invalid object name @@ -108,7 +136,7 @@ public function setUp(): void } foreach (self::$incompleteTestGroups as $testGroup => $reason) { - if (str_starts_with($this->dataDescription(), $testGroup)) { + if (str_starts_with(strtolower($this->dataDescription()), strtolower($testGroup))) { $this->markTestIncomplete($reason); } } diff --git a/tests/UnifiedSpecTests/UnifiedTestCase.php b/tests/UnifiedSpecTests/UnifiedTestCase.php index 435400fb5..9b12445d7 100644 --- a/tests/UnifiedSpecTests/UnifiedTestCase.php +++ b/tests/UnifiedSpecTests/UnifiedTestCase.php @@ -20,7 +20,7 @@ * within a JSON object conforming to the unified test format's JSON schema. * This test case may be executed by UnifiedTestRunner::run(). * - * @see https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.rst + * @see https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md */ final class UnifiedTestCase implements IteratorAggregate { diff --git a/tests/UnifiedSpecTests/UnifiedTestRunner.php b/tests/UnifiedSpecTests/UnifiedTestRunner.php index f6cba51a3..3069636b7 100644 --- a/tests/UnifiedSpecTests/UnifiedTestRunner.php +++ b/tests/UnifiedSpecTests/UnifiedTestRunner.php @@ -43,7 +43,7 @@ /** * Unified test runner. * - * @see https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.rst + * @see https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md */ final class UnifiedTestRunner { @@ -55,13 +55,18 @@ final class UnifiedTestRunner /** * Support for the following schema versions is incomplete: * - * - 1.9: Only createEntities operation is implemented + * - 1.9: collectionOrDatabaseOptions.timeoutMS and expectedError.isTimeoutError are not implemented * - 1.10: Not implemented * - 1.11: Not implemented, but CMAP is not applicable * - 1.13: Only $$matchAsDocument and $$matchAsRoot is implemented * - 1.14: Not implemented + * - 1.16: Not implemented + * - 1.17: Not implemented + * - 1.18: Not implemented + * - 1.19: Not implemented + * - 1.20: Not implemented */ - public const MAX_SCHEMA_VERSION = '1.15'; + public const MAX_SCHEMA_VERSION = '1.21'; private Client $internalClient; @@ -451,7 +456,7 @@ private function isAdvanceClusterTimeNeeded(array $operations): bool /** * Work around potential error executing distinct on sharded clusters. * - * @see https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.rst#staledbversion-errors-on-sharded-clusters + * @see https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md#staledbversion-errors-on-sharded-clusters */ private function preventStaleDbVersionError(array $operations, Context $context): void { diff --git a/tests/UnifiedSpecTests/Util.php b/tests/UnifiedSpecTests/Util.php index d508d7d65..e134cb109 100644 --- a/tests/UnifiedSpecTests/Util.php +++ b/tests/UnifiedSpecTests/Util.php @@ -58,6 +58,7 @@ final class Util 'loop' => ['operations', 'storeErrorsAsEntity', 'storeFailuresAsEntity', 'storeSuccessesAsEntity', 'storeIterationsAsEntity'], ], Client::class => [ + 'clientBulkWrite' => ['models', 'bypassDocumentValidation', 'comment', 'let', 'ordered', 'session', 'verboseResults', 'writeConcern'], 'createChangeStream' => ['pipeline', 'session', 'fullDocument', 'resumeAfter', 'startAfter', 'startAtOperationTime', 'batchSize', 'collation', 'maxAwaitTimeMS', 'showExpandedEvents'], 'listDatabaseNames' => ['authorizedDatabases', 'filter', 'maxTimeMS', 'session'], 'listDatabases' => ['authorizedDatabases', 'filter', 'maxTimeMS', 'session'], @@ -75,7 +76,7 @@ final class Util Database::class => [ 'aggregate' => ['pipeline', 'session', 'allowDiskUse', 'batchSize', 'bypassDocumentValidation', 'collation', 'comment', 'explain', 'hint', 'let', 'maxAwaitTimeMS', 'maxTimeMS'], 'createChangeStream' => ['pipeline', 'session', 'fullDocument', 'resumeAfter', 'startAfter', 'startAtOperationTime', 'batchSize', 'collation', 'maxAwaitTimeMS', 'showExpandedEvents'], - 'createCollection' => ['collection', 'session', 'autoIndexId', 'capped', 'changeStreamPreAndPostImages', 'clusteredIndex', 'collation', 'expireAfterSeconds', 'flags', 'indexOptionDefaults', 'max', 'maxTimeMS', 'pipeline', 'size', 'storageEngine', 'timeseries', 'validationAction', 'validationLevel', 'validator', 'viewOn'], + 'createCollection' => ['collection', 'session', 'capped', 'changeStreamPreAndPostImages', 'clusteredIndex', 'collation', 'expireAfterSeconds', 'indexOptionDefaults', 'max', 'maxTimeMS', 'pipeline', 'size', 'storageEngine', 'timeseries', 'validationAction', 'validationLevel', 'validator', 'viewOn'], 'dropCollection' => ['collection', 'session'], 'listCollectionNames' => ['authorizedCollections', 'filter', 'maxTimeMS', 'session'], 'listCollections' => ['authorizedCollections', 'filter', 'maxTimeMS', 'session'], @@ -87,7 +88,7 @@ final class Util 'aggregate' => ['pipeline', 'session', 'allowDiskUse', 'batchSize', 'bypassDocumentValidation', 'collation', 'comment', 'explain', 'hint', 'let', 'maxAwaitTimeMS', 'maxTimeMS'], 'bulkWrite' => ['let', 'requests', 'session', 'ordered', 'bypassDocumentValidation', 'comment'], 'createChangeStream' => ['pipeline', 'session', 'fullDocument', 'fullDocumentBeforeChange', 'resumeAfter', 'startAfter', 'startAtOperationTime', 'batchSize', 'collation', 'maxAwaitTimeMS', 'comment', 'showExpandedEvents'], - 'createFindCursor' => ['filter', 'session', 'allowDiskUse', 'allowPartialResults', 'batchSize', 'collation', 'comment', 'cursorType', 'hint', 'limit', 'max', 'maxAwaitTimeMS', 'maxScan', 'maxTimeMS', 'min', 'modifiers', 'noCursorTimeout', 'oplogReplay', 'projection', 'returnKey', 'showRecordId', 'skip', 'snapshot', 'sort'], + 'createFindCursor' => ['filter', 'session', 'allowDiskUse', 'allowPartialResults', 'batchSize', 'collation', 'comment', 'cursorType', 'hint', 'limit', 'max', 'maxAwaitTimeMS', 'maxTimeMS', 'min', 'noCursorTimeout', 'projection', 'returnKey', 'showRecordId', 'skip', 'sort'], 'createIndex' => ['keys', 'comment', 'commitQuorum', 'maxTimeMS', 'name', 'session', 'unique'], 'createSearchIndex' => ['model'], 'createSearchIndexes' => ['models'], @@ -101,8 +102,8 @@ final class Util 'distinct' => ['fieldName', 'filter', 'session', 'collation', 'maxTimeMS', 'comment', 'hint'], 'drop' => ['session', 'comment'], 'dropSearchIndex' => ['name'], - 'find' => ['let', 'filter', 'session', 'allowDiskUse', 'allowPartialResults', 'batchSize', 'collation', 'comment', 'cursorType', 'hint', 'limit', 'max', 'maxAwaitTimeMS', 'maxScan', 'maxTimeMS', 'min', 'modifiers', 'noCursorTimeout', 'oplogReplay', 'projection', 'returnKey', 'showRecordId', 'skip', 'snapshot', 'sort'], - 'findOne' => ['let', 'filter', 'session', 'allowDiskUse', 'allowPartialResults', 'batchSize', 'collation', 'comment', 'cursorType', 'hint', 'max', 'maxAwaitTimeMS', 'maxScan', 'maxTimeMS', 'min', 'modifiers', 'noCursorTimeout', 'oplogReplay', 'projection', 'returnKey', 'showRecordId', 'skip', 'snapshot', 'sort'], + 'find' => ['let', 'filter', 'session', 'allowDiskUse', 'allowPartialResults', 'batchSize', 'collation', 'comment', 'cursorType', 'hint', 'limit', 'max', 'maxAwaitTimeMS', 'maxTimeMS', 'min', 'noCursorTimeout', 'projection', 'returnKey', 'showRecordId', 'skip', 'sort'], + 'findOne' => ['let', 'filter', 'session', 'allowDiskUse', 'allowPartialResults', 'batchSize', 'collation', 'comment', 'cursorType', 'hint', 'max', 'maxAwaitTimeMS', 'maxTimeMS', 'min', 'noCursorTimeout', 'projection', 'returnKey', 'showRecordId', 'skip', 'sort'], 'findOneAndReplace' => ['let', 'returnDocument', 'filter', 'replacement', 'session', 'projection', 'returnDocument', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'maxTimeMS', 'new', 'remove', 'sort', 'comment'], 'rename' => ['to', 'comment', 'dropTarget'], 'replaceOne' => ['let', 'filter', 'replacement', 'session', 'upsert', 'arrayFilters', 'bypassDocumentValidation', 'collation', 'hint', 'comment', 'sort'], @@ -137,8 +138,9 @@ final class Util 'download' => ['id'], 'rename' => ['id', 'newFilename'], 'renameByName' => ['filename', 'newFilename'], - 'uploadWithId' => ['id', 'filename', 'source', 'chunkSizeBytes', 'disableMD5', 'contentType', 'metadata'], - 'upload' => ['filename', 'source', 'chunkSizeBytes', 'disableMD5', 'contentType', 'metadata'], + // "disableMD5" is ignored but allowed for backward compatibility + 'uploadWithId' => ['id', 'filename', 'source', 'chunkSizeBytes', 'disableMD5', 'metadata'], + 'upload' => ['filename', 'source', 'chunkSizeBytes', 'disableMD5', 'metadata'], ], ]; diff --git a/tests/drivers-evergreen-tools b/tests/drivers-evergreen-tools index a332144cf..f8ab2a54f 160000 --- a/tests/drivers-evergreen-tools +++ b/tests/drivers-evergreen-tools @@ -1 +1 @@ -Subproject commit a332144cfc785ab178be3d9d62e645cb79b5f81e +Subproject commit f8ab2a54f774cab1e92bcf222949181baf8b2f1c diff --git a/tests/specifications b/tests/specifications index 0aee4aad0..d41d48b90 160000 --- a/tests/specifications +++ b/tests/specifications @@ -1 +1 @@ -Subproject commit 0aee4aad0bc6710a8fae5910c36d41b8a60a0688 +Subproject commit d41d48b90ef22aedae934dd22f3a21c65b5a13bc diff --git a/tools/connect.php b/tools/connect.php index 2d0ed1ff4..1b7dfa567 100644 --- a/tools/connect.php +++ b/tools/connect.php @@ -9,7 +9,7 @@ function getHosts(string $uri): array $parsed = parse_url($uri); if (isset($parsed['scheme']) && $parsed['scheme'] !== 'mongodb') { - // TODO: Resolve SRV records (https://github.com/mongodb/specifications/blob/master/source/initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.rst) + // TODO: Resolve SRV records (https://github.com/mongodb/specifications/blob/master/source/initial-dns-seedlist-discovery/initial-dns-seedlist-discovery.md) throw new RuntimeException('Unsupported scheme: ' . $parsed['scheme']); }