diff --git a/examples/atlas-search.php b/examples/atlas-search.php new file mode 100644 index 000000000..3f675c47e --- /dev/null +++ b/examples/atlas-search.php @@ -0,0 +1,131 @@ +selectCollection('sample_airbnb', 'listingsAndReviews'); + +$count = $collection->estimatedDocumentCount(); +if ($count === 0) { + echo 'This example requires the sample_airbnb database with the listingsAndReviews collection.', "\n"; + echo 'Load the sample dataset in your MongoDB Atlas cluster before running this example:', "\n"; + echo ' https://www.mongodb.com/docs/atlas/sample-data/', "\n"; + exit(1); +} + +// Delete the index if it already exists +$indexes = iterator_to_array($collection->listSearchIndexes()); +foreach ($indexes as $index) { + if ($index->name === 'default') { + echo "The index already exists. Dropping it.\n"; + $collection->dropSearchIndex($index->name); + + // Wait for the index to be deleted. + wait(function () use ($collection) { + foreach ($collection->listSearchIndexes() as $index) { + if ($index->name === 'default') { + echo '.'; + + return false; + } + } + + return true; + }); + } +} + +// Create the search index +echo "\nCreating the index.\n"; +$collection->createSearchIndex( + /* The index definition requires a mapping + * See: https://www.mongodb.com/docs/atlas/atlas-search/define-field-mappings/ */ + ['mappings' => ['dynamic' => true]], + // "default" is the default index name, this config can be omitted. + ['name' => 'default'], +); + +// Wait for the index to be ready. +wait(function () use ($collection) { + foreach ($collection->listSearchIndexes() as $index) { + if ($index->name === 'default') { + echo '.'; + + return $index->queryable; + } + } + + return false; +}); + +// Perform a text search +echo "\n", 'Performing a text search...', "\n"; +$results = $collection->aggregate([ + [ + '$search' => [ + 'index' => 'default', + 'text' => [ + 'query' => 'view beach ocean', + 'path' => ['name'], + ], + ], + ], + ['$project' => ['name' => 1, 'description' => 1]], + ['$limit' => 10], +])->toArray(); + +foreach ($results as $document) { + echo ' - ', $document['name'], "\n"; +} + +echo "\n", 'Enjoy MongoDB Atlas Search!', "\n\n"; + +/** + * This function waits until the callback returns true or the timeout is reached. + */ +function wait(Closure $callback): void +{ + $timeout = hrtime()[0] + WAIT_TIMEOUT_SEC; + while (hrtime()[0] < $timeout) { + if ($callback()) { + return; + } + + sleep(5); + } + + throw new RuntimeException('Time out'); +} diff --git a/psalm-baseline.xml b/psalm-baseline.xml index f1db3e9fa..c26c4f6bf 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -5,6 +5,30 @@ decrypt($document->encryptedField)]]> + + + + name]]> + + + + + + $document + $index + $index + $index + + + name]]> + name]]> + name]]> + queryable]]> + + + $uri + + self::CURSOR_NOT_FOUND diff --git a/tests/ExamplesTest.php b/tests/ExamplesTest.php index 4c58bcc71..1ba3ceaab 100644 --- a/tests/ExamplesTest.php +++ b/tests/ExamplesTest.php @@ -3,6 +3,9 @@ namespace MongoDB\Tests; use Generator; +use MongoDB\Client; + +use function getenv; /** @runTestsInSeparateProcesses */ final class ExamplesTest extends FunctionalTestCase @@ -179,6 +182,37 @@ public static function provideExamples(): Generator ]; } + /** + * MongoDB Atlas Search example requires a MongoDB Atlas M10+ cluster with MongoDB 7.0+ and sample data loaded. + * Tips for insiders: if using a cloud-dev server, append ".mongodb.net" to the MONGODB_URI. + */ + public function testAtlasSearch(): void + { + $uri = getenv('MONGODB_URI') ?? ''; + if (! self::isAtlas($uri)) { + $this->markTestSkipped('Atlas Search examples are only supported on MongoDB Atlas'); + } + + $this->skipIfServerVersion('<', '7.0', 'Atlas Search examples require MongoDB 7.0 or later'); + + $client = new Client($uri); + $collection = $client->selectCollection('sample_airbnb', 'listingsAndReviews'); + $count = $collection->estimatedDocumentCount(); + if ($count === 0) { + $this->markTestSkipped('Atlas Search examples require the sample_airbnb database with the listingsAndReviews collection'); + } + + // Clean variables to avoid conflict with example + unset($uri, $client, $collection, $count); + + require __DIR__ . '/../examples/atlas-search.php'; + + $output = $this->getActualOutputForAssertion(); + $this->assertStringContainsString("\nCreating the index.\n...", $output); + $this->assertStringContainsString("\nPerforming a text search...\n - ", $output); + $this->assertStringContainsString("\nEnjoy MongoDB Atlas Search!\n", $output); + } + public function testChangeStream(): void { $this->skipIfChangeStreamIsNotSupported();