Ver Fonte

Merge branch '1.0.x'

* 1.0.x:
  Add changelog entry
  Harden detection for numeric arrays
  Ensure string field names for legacy projection
  Add tests for projection with numeric keys
  Harden projection tests
Andreas Braun há 9 anos atrás
pai
commit
53ed7f6c3c

+ 3 - 1
CHANGELOG-1.0.md

@@ -3,13 +3,15 @@ CHANGELOG
 
 This changelog references the relevant changes done in minor version updates.
 
-1.0.9 (????-??-??)
+1.0.9 (2017-01-29)
 ------------------
 
 All issues and pull requests under this release may be found under the
 [1.0.9](https://github.com/alcaeus/mongo-php-adapter/issues?q=milestone%3A1.0.9)
 milestone.
 
+ * [#157](https://github.com/alcaeus/mongo-php-adapter/pull/157) fixes a regression
+ introduced in 1.0.8 when using query projection with numeric field names.
  * [#155](https://github.com/alcaeus/mongo-php-adapter/pull/155) fixes the handling
  of BSON types when converting legacy types to BSON types.
  * [#154](https://github.com/alcaeus/mongo-php-adapter/pull/154) makes the `options`

+ 25 - 2
lib/Alcaeus/MongoDbAdapter/TypeConverter.php

@@ -98,6 +98,8 @@ class TypeConverter
      *
      * @param array $fields
      * @return array
+     *
+     * @throws \MongoException
      */
     public static function convertProjection($fields)
     {
@@ -105,7 +107,21 @@ class TypeConverter
             return [];
         }
 
-        $projection = TypeConverter::isNumericArray($fields) ? array_fill_keys($fields, true) : $fields;
+        if (! TypeConverter::isNumericArray($fields)) {
+            $projection = TypeConverter::fromLegacy($fields);
+        } else {
+            $projection = array_fill_keys(
+                array_map(function ($field) {
+                    if (!is_string($field)) {
+                        throw new \MongoException('field names must be strings', 8);
+                    }
+
+                    return $field;
+                }, $fields),
+                true
+            );
+        }
+
         return TypeConverter::fromLegacy($projection);
     }
 
@@ -122,7 +138,14 @@ class TypeConverter
      */
     public static function isNumericArray(array $array)
     {
-        return $array === [] || is_numeric(array_keys($array)[0]);
+        if ($array === []) {
+            return true;
+        }
+
+        $keys = array_keys($array);
+        // array_keys gives us a clean numeric array with keys, so we expect an
+        // array like [0 => 0, 1 => 1, 2 => 2, ..., n => n]
+        return array_values($keys) === array_keys($keys);
     }
 
     /**

+ 63 - 5
tests/Alcaeus/MongoDbAdapter/Mongo/MongoCollectionTest.php

@@ -450,34 +450,92 @@ class MongoCollectionTest extends TestCase
         $this->assertInstanceOf('MongoCursor', $collection->find());
     }
 
-    public function testFindWithProjection()
+    /**
+     * @dataProvider dataFindWithProjection
+     */
+    public function testFindWithProjection($projection)
     {
         $document = ['foo' => 'foo', 'bar' => 'bar'];
         $this->getCollection()->insert($document);
         unset($document['_id']);
         $this->getCollection()->insert($document);
 
-        $cursor = $this->getCollection()->find(['foo' => 'foo'], ['bar' => true]);
+        $cursor = $this->getCollection()->find(['foo' => 'foo'], $projection);
         foreach ($cursor as $document) {
             $this->assertCount(2, $document);
+            $this->assertArrayHasKey('_id', $document);
             $this->assertArraySubset(['bar' => 'bar'], $document);
         }
     }
 
-    public function testFindWithLegacyProjection()
+    public static function dataFindWithProjection()
+    {
+        return [
+            'projection' => [['bar' => true]],
+            'intProjection' => [['bar' => 1]],
+            'legacyProjection' => [['bar']],
+        ];
+    }
+
+    /**
+     * @dataProvider dataFindWithProjectionAndNumericKeys
+     */
+    public function testFindWithProjectionAndNumericKeys($data, $projection, $expected)
+    {
+        $this->getCollection()->insert($data);
+
+        $document = $this->getCollection()->findOne([], $projection);
+        unset($document['_id']);
+        $this->assertSame($expected, $document);
+    }
+
+    public static function dataFindWithProjectionAndNumericKeys()
+    {
+        return [
+            'sequentialIntegersStartingWithOne' => [
+                ['0' => 'foo', '1' => 'bar', '2' => 'foobar'],
+                [1 => true, 2 => true],
+                ['1' => 'bar', '2' => 'foobar'],
+            ],
+            'nonSequentialIntegers' => [
+                ['0' => 'foo', '1' => 'bar', '2' => 'foobar', '3' => 'barfoo'],
+                [1 => true, 3 => true],
+                ['1' => 'bar', '3' => 'barfoo'],
+            ]
+        ];
+    }
+
+    public function testFindWithProjectionAndSequentialNumericKeys()
+    {
+        $this->setExpectedException(\MongoException::class, 'field names must be strings', 8);
+        $this->getCollection()->findOne([], [true, false]);
+    }
+
+    /**
+     * @dataProvider dataFindWithProjectionExcludeId
+     */
+    public function testFindWithProjectionExcludeId($projection)
     {
         $document = ['foo' => 'foo', 'bar' => 'bar'];
         $this->getCollection()->insert($document);
         unset($document['_id']);
         $this->getCollection()->insert($document);
 
-        $cursor = $this->getCollection()->find(['foo' => 'foo'], ['bar']);
+        $cursor = $this->getCollection()->find(['foo' => 'foo'], $projection);
         foreach ($cursor as $document) {
-            $this->assertCount(2, $document);
+            $this->assertCount(1, $document);
+            $this->assertArrayNotHasKey('_id', $document);
             $this->assertArraySubset(['bar' => 'bar'], $document);
         }
     }
 
+    public static function dataFindWithProjectionExcludeId()
+    {
+        return [
+            'projection' => [['_id' => false, 'bar' => true]],
+            'intProjection' => [['_id' => 0, 'bar' => 1]],
+        ];
+    }
 
     public function testCount()
     {

+ 19 - 0
tests/Alcaeus/MongoDbAdapter/TypeConverterTest.php

@@ -40,4 +40,23 @@ class TypeConverterTest extends TestCase
             ],
         ];
     }
+
+    /**
+     * @dataProvider dataIsNumericArray
+     */
+    public function testIsNumericArray($expected, $array)
+    {
+        $this->assertSame($expected, TypeConverter::isNumericArray($array));
+    }
+
+    public static function dataIsNumericArray()
+    {
+        return [
+            'emptyArray' => [true, []],
+            'arrayWithSequentialIndices' => [true, [1, 2, 3]],
+            'arrayWithSequentialIndicesOneBased' => [false, [1 => 1, 2, 3]],
+            'arrayWithStringKeys' => [false, ['foo' => 'bar']],
+            'arrayWithRandomNumbers' => [false, [15 => 'foo', 20 => 'bar']],
+        ];
+    }
 }