Unverified Commit e3a832db authored by Stephan de Souza's avatar Stephan de Souza Committed by GitHub

Merge branch 'master' into patch-1

parents 5b42dcc1 20d05ad5
name: CI
on:
push:
branches:
tags:
pull_request:
jobs:
build:
runs-on: ${{matrix.os}}
strategy:
matrix:
php: [7.1, 7.2, 7.3, 7.4]
os: ['ubuntu-latest']
services:
mongo:
image: mongo
ports:
- 27017:27017
mysql:
image: mysql:5.7
ports:
- 3307:3306
env:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
MYSQL_DATABASE: 'unittest'
MYSQL_ROOT_PASSWORD:
name: PHP ${{ matrix.php }} Test ${{ matrix.env }}
steps:
- uses: actions/checkout@v1
- name: Show php version
run: php${{ matrix.php }} -v && composer -V
- name: Debug if needed
run: if [[ "$DEBUG" == "true" ]]; then docker version && env; fi
env:
DEBUG: ${{secrets.DEBUG}}
- name: Get Composer Cache Directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ matrix.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ matrix.os }}-composer-
- name: Install dependencies
run: |
composer install --no-interaction
- name: Generating code coverage
run: |
mkdir -p build/logs
./vendor/bin/phpunit --coverage-clover build/logs/clover.xml
env:
MONGO_HOST: 0.0.0.0
MYSQL_HOST: 0.0.0.0
MYSQL_PORT: 3307
- name: Send coveralls
run: vendor/bin/php-coveralls -v
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
preset: laravel
language: minimal
matrix:
include:
- name: "7.1"
env: PHP_VERSION=7.1
- name: "7.2"
env: PHP_VERSION=7.2
- name: "7.3"
env: PHP_VERSION=7.3
services:
- docker
cache:
directories:
- $HOME/.composer/cache
install:
- docker version
- sudo pip install docker-compose
- docker-compose version
- docker-compose build --build-arg PHP_VERSION=${PHP_VERSION}
- docker-compose run --rm tests composer install --no-interaction
script:
- docker-compose run --rm tests ./vendor/bin/phpunit --coverage-clover ./clover.xml
Laravel MongoDB
===============
[![Latest Stable Version](http://img.shields.io/github/release/jenssegers/laravel-mongodb.svg)](https://packagist.org/packages/jenssegers/mongodb) [![Total Downloads](http://img.shields.io/packagist/dm/jenssegers/mongodb.svg)](https://packagist.org/packages/jenssegers/mongodb) [![Build Status](http://img.shields.io/travis/jenssegers/laravel-mongodb.svg)](https://travis-ci.org/jenssegers/laravel-mongodb) [![Coverage Status](http://img.shields.io/coveralls/jenssegers/laravel-mongodb.svg)](https://coveralls.io/r/jenssegers/laravel-mongodb?branch=master) [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/jenssegers)
[![Latest Stable Version](http://img.shields.io/github/release/jenssegers/laravel-mongodb.svg)](https://packagist.org/packages/jenssegers/mongodb) [![Total Downloads](http://img.shields.io/packagist/dm/jenssegers/mongodb.svg)](https://packagist.org/packages/jenssegers/mongodb) [![Build Status](https://img.shields.io/github/workflow/status/jenssegers/laravel-mongodb/CI)](https://github.com/jenssegers/laravel-mongodb/actions) [![Coverage Status](https://coveralls.io/repos/github/jenssegers/laravel-mongodb/badge.svg?branch=master)](https://coveralls.io/github/jenssegers/laravel-mongodb?branch=master) [![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://www.paypal.me/jenssegers)
An Eloquent model and Query builder with support for MongoDB, using the original Laravel API. *This library extends the original Laravel classes, so it uses exactly the same methods.*
......@@ -15,7 +15,6 @@ Table of contents
* [Query Builder](#query-builder)
* [Schema](#schema)
* [Extensions](#extensions)
* [Troubleshooting](#troubleshooting)
* [Examples](#examples)
Installation
......@@ -45,6 +44,7 @@ composer require jenssegers/mongodb
5.6.x | 3.4.x
5.7.x | 3.4.x
5.8.x | 3.5.x
6.0.x | 3.6.x
And add the service provider in `config/app.php`:
......@@ -153,8 +153,8 @@ You can connect to multiple servers or replica sets with the following configura
'username' => env('DB_USERNAME'),
'password' => env('DB_PASSWORD'),
'options' => [
'replicaSet' => 'replicaSetName'
]
'replicaSet' => 'replicaSetName'
]
],
```
......@@ -253,7 +253,18 @@ Schema::create('users', function($collection)
$collection->unique('email');
});
```
You can also pass all the parameters specified in the MongoDB docs [here](https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#options-for-all-index-types) in the `$options` parameter. For example:
```
Schema::create('users', function($collection)
{
$collection->index('username',null,null,[
'sparse' => true,
'unique' => true,
'background' => true
]);
});
```
Supported operations are:
- create and drop
......@@ -263,7 +274,7 @@ Supported operations are:
- unique
- background, sparse, expire, geospatial (MongoDB specific)
All other (unsupported) operations are implemented as dummy pass-through methods, because MongoDB does not use a predefined schema. Read more about the schema builder on http://laravel.com/docs/schema
All other (unsupported) operations are implemented as dummy pass-through methods, because MongoDB does not use a predefined schema. Read more about the schema builder on https://laravel.com/docs/6.0/migrations#tables
### Geospatial indexes
......@@ -312,6 +323,7 @@ If you want to use MongoDB as your database backend, change the driver in `confi
'queue' => 'default',
'expire' => 60,
],
]
```
If you want to use MongoDB to handle failed jobs, change the database in `config/queue.php`:
......@@ -320,7 +332,7 @@ If you want to use MongoDB to handle failed jobs, change the database in `config
'failed' => [
'database' => 'mongodb',
'table' => 'failed_jobs',
],
],
```
And add the service provider in `config/app.php`:
......@@ -549,13 +561,13 @@ User::where('name', 'regex', new \MongoDB\BSON\Regex("/.*doe/i"))->get();
**NOTE:** you can also use the Laravel regexp operations. These are a bit more flexible and will automatically convert your regular expression string to a MongoDB\BSON\Regex object.
```php
User::where('name', 'regexp', '/.*doe/i'))->get();
User::where('name', 'regexp', '/.*doe/i')->get();
```
And the inverse:
```php
User::where('name', 'not regexp', '/.*doe/i'))->get();
User::where('name', 'not regexp', '/.*doe/i')->get();
```
**Type**
......@@ -601,15 +613,15 @@ $users = User::where('location', 'geoWithin', [
[
-0.1450383,
51.5069158,
],
],
[
-0.1367563,
51.5100913,
],
],
[
-0.1270247,
51.5013233,
],
],
[
-0.1450383,
51.5069158,
......@@ -693,7 +705,7 @@ For more information about model manipulation, check http://laravel.com/docs/elo
### Dates
Eloquent allows you to work with Carbon/DateTime objects instead of MongoDate objects. Internally, these dates will be converted to MongoDate objects when saved to the database. If you wish to use this functionality on non-default date fields, you will need to manually specify them as described here: http://laravel.com/docs/eloquent#date-mutators
Eloquent allows you to work with Carbon/DateTime objects instead of MongoDate objects. Internally, these dates will be converted to MongoDate objects when saved to the database. If you wish to use this functionality on non-default date fields, you will need to manually specify them as described here: https://laravel.com/docs/5.0/eloquent#date-mutators
Example:
......@@ -770,7 +782,7 @@ class User extends Eloquent {
```
Other relations are not yet supported, but may be added in the future. Read more about these relations on http://laravel.com/docs/eloquent#relationships
Other relations are not yet supported, but may be added in the future. Read more about these relations on https://laravel.com/docs/master/eloquent-relationships
### EmbedsMany Relations
......@@ -969,7 +981,7 @@ $cursor = DB::collection('users')->raw(function($collection)
Optional: if you don't pass a closure to the raw method, the internal MongoCollection object will be accessible:
```php
$model = User::raw()->findOne(['age' => array('$lt' => 18)]);
$model = User::raw()->findOne(['age' => ['$lt' => 18]]);
```
The internal MongoClient and MongoDB objects can be accessed like this:
......@@ -1063,7 +1075,7 @@ You may easily cache the results of a query using the remember method:
$users = User::remember(10)->get();
```
*From: http://laravel.com/docs/queries#caching-queries*
*From: https://laravel.com/docs/4.2/queries#caching-queries*
### Query Logging
......@@ -1073,4 +1085,4 @@ By default, Laravel keeps a log in memory of all queries that have been run for
DB::connection()->disableQueryLog();
```
*From: http://laravel.com/docs/database#query-logging*
*From: https://laravel.com/docs/4.2/database#query-logging*
......@@ -29,9 +29,16 @@
"phpunit/phpunit": "^6.0|^7.0|^8.0",
"orchestra/testbench": "^3.1|^4.0",
"mockery/mockery": "^1.0",
"satooshi/php-coveralls": "^2.0",
"doctrine/dbal": "^2.5"
"php-coveralls/php-coveralls": "dev-add-support-for-github-actions",
"doctrine/dbal": "^2.5",
"phpunit/phpcov": "5.0.0"
},
"repositories": [
{
"type": "vcs",
"url": "https://github.com/Smolevich/php-coveralls"
}
],
"autoload": {
"psr-0": {
"Jenssegers\\Mongodb": "src/"
......
......@@ -40,11 +40,17 @@
<file>tests/ValidationTest.php</file>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
<php>
<env name="MONGO_HOST" value="mongodb"/>
<env name="MONGO_DATABASE" value="unittest"/>
<env name="MONGO_PORT" value="27017"/>
<env name="MYSQL_HOST" value="mysql"/>
<env name="MYSQL_PORT" value="3306"/>
<env name="MYSQL_DATABASE" value="unittest"/>
<env name="MYSQL_USERNAME" value="root"/>
<env name="QUEUE_CONNECTION" value="database"/>
......
......@@ -25,6 +25,23 @@ class DatabaseTokenRepository extends BaseDatabaseTokenRepository
* @inheritdoc
*/
protected function tokenExpired($createdAt)
{
$createdAt = $this->convertDateTime($createdAt);
return parent::tokenExpired($createdAt);
}
/**
* @inheritdoc
*/
protected function tokenRecentlyCreated($createdAt)
{
$createdAt = $this->convertDateTime($createdAt);
return parent::tokenRecentlyCreated($createdAt);
}
private function convertDateTime($createdAt)
{
// Convert UTCDateTime to a date string.
if ($createdAt instanceof UTCDateTime) {
......@@ -37,6 +54,6 @@ class DatabaseTokenRepository extends BaseDatabaseTokenRepository
$createdAt = $date->format('Y-m-d H:i:s');
}
return parent::tokenExpired($createdAt);
return $createdAt;
}
}
......@@ -118,7 +118,7 @@ abstract class Model extends BaseModel
*/
public function freshTimestamp()
{
return new UTCDateTime(time() * 1000);
return new UTCDateTime(Carbon::now());
}
/**
......
......@@ -2,6 +2,7 @@
namespace Jenssegers\Mongodb;
use DB;
use Illuminate\Queue\QueueServiceProvider;
use Jenssegers\Mongodb\Queue\Failed\MongoFailedJobProvider;
......@@ -13,7 +14,7 @@ class MongodbQueueServiceProvider extends QueueServiceProvider
protected function registerFailedJobServices()
{
// Add compatible queue failer if mongodb is configured.
if (config('queue.failed.database') == 'mongodb') {
if (DB::connection(config('queue.failed.database'))->getDriverName() == 'mongodb') {
$this->app->singleton('queue.failer', function ($app) {
return new MongoFailedJobProvider($app['db'], config('queue.failed.database'), config('queue.failed.table'));
});
......
......@@ -232,7 +232,7 @@ class Builder extends BaseBuilder
$wheres = $this->compileWheres();
// Use MongoDB's aggregation framework when using grouping or aggregation functions.
if ($this->groups || $this->aggregate || $this->paginating) {
if ($this->groups || $this->aggregate) {
$group = [];
$unwinds = [];
......@@ -267,24 +267,34 @@ class Builder extends BaseBuilder
$column = implode('.', $splitColumns);
}
// Translate count into sum.
if ($function == 'count') {
// Null coalense only > 7.2
$aggregations = blank($this->aggregate['columns']) ? [] : $this->aggregate['columns'];
if (in_array('*', $aggregations) && $function == 'count') {
// When ORM is paginating, count doesnt need a aggregation, just a cursor operation
// elseif added to use this only in pagination
// https://docs.mongodb.com/manual/reference/method/cursor.count/
// count method returns int
$totalResults = $this->collection->count($wheres);
// Preserving format expected by framework
$results = [
[
'_id' => null,
'aggregate' => $totalResults
]
];
return $this->useCollections ? new Collection($results) : $results;
} elseif ($function == 'count') {
// Translate count into sum.
$group['aggregate'] = ['$sum' => 1];
} // Pass other functions directly.
else {
} else {
$group['aggregate'] = ['$' . $function => '$' . $column];
}
}
}
// When using pagination, we limit the number of returned columns
// by adding a projection.
if ($this->paginating) {
foreach ($this->columns as $column) {
$this->projections[$column] = 1;
}
}
// The _id field is mandatory when using grouping.
if ($group && empty($group['_id'])) {
$group['_id'] = null;
......
......@@ -46,6 +46,10 @@ class MongoFailedJobProvider extends DatabaseFailedJobProvider
{
$job = $this->getTable()->find($id);
if (!$job) {
return;
}
$job['id'] = (string) $job['_id'];
return (object) $job;
......
......@@ -64,7 +64,7 @@ class MongoQueue extends DatabaseQueue
$job = $this->database->getCollection($this->table)->findOneAndUpdate(
[
'queue' => $this->getQueue($queue),
'reserved' => 0,
'reserved' => ['$ne' => 1],
'available_at' => ['$lte' => Carbon::now()->getTimestamp()],
],
[
......@@ -72,6 +72,9 @@ class MongoQueue extends DatabaseQueue
'reserved' => 1,
'reserved_at' => Carbon::now()->getTimestamp(),
],
'$inc' => [
'attempts' => 1,
],
],
[
'returnDocument' => FindOneAndUpdate::RETURN_DOCUMENT_AFTER,
......@@ -94,24 +97,15 @@ class MongoQueue extends DatabaseQueue
protected function releaseJobsThatHaveBeenReservedTooLong($queue)
{
$expiration = Carbon::now()->subSeconds($this->retryAfter)->getTimestamp();
$now = time();
$reserved = $this->database->collection($this->table)
->where('queue', $this->getQueue($queue))
->where(function ($query) use ($expiration, $now) {
// Check for available jobs
$query->where(function ($query) use ($now) {
$query->whereNull('reserved_at');
$query->where('available_at', '<=', $now);
});
// Check for jobs that are reserved but have expired
$query->orWhere('reserved_at', '<=', $expiration);
})->get();
->whereNotNull('reserved_at')
->where('reserved_at', '<=', $expiration)
->get();
foreach ($reserved as $job) {
$attempts = $job['attempts'] + 1;
$this->releaseJob($job['_id'], $attempts);
$this->releaseJob($job['_id'], $job['attempts']);
}
}
......
......@@ -74,6 +74,54 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint
* @inheritdoc
*/
public function dropIndex($indexOrColumns = null)
{
$indexOrColumns = $this->transformColumns($indexOrColumns);
$this->collection->dropIndex($indexOrColumns);
return $this;
}
/**
* Indicate that the given index should be dropped, but do not fail if it didn't exist.
*
* @param string|array $indexOrColumns
* @return Blueprint
*/
public function dropIndexIfExists($indexOrColumns = null)
{
if ($this->hasIndex($indexOrColumns)) {
$this->dropIndex($indexOrColumns);
}
return $this;
}
/**
* Check whether the given index exists.
*
* @param string|array $indexOrColumns
* @return bool
*/
public function hasIndex($indexOrColumns = null)
{
$indexOrColumns = $this->transformColumns($indexOrColumns);
foreach ($this->collection->listIndexes() as $index) {
if (is_array($indexOrColumns) && in_array($index->getName(), $indexOrColumns)) {
return true;
}
if (is_string($indexOrColumns) && $index->getName() == $indexOrColumns) {
return true;
}
}
return false;
}
/**
* @param string|array $indexOrColumns
* @return string
*/
protected function transformColumns($indexOrColumns)
{
if (is_array($indexOrColumns)) {
$indexOrColumns = $this->fluent($indexOrColumns);
......@@ -85,12 +133,9 @@ class Blueprint extends \Illuminate\Database\Schema\Blueprint
$transform[$column] = $column . '_1';
}
$indexOrColumns = join('_', $transform);
$indexOrColumns = implode('_', $transform);
}
$this->collection->dropIndex($indexOrColumns);
return $this;
return $indexOrColumns;
}
/**
......
......@@ -28,6 +28,7 @@ class AuthTest extends TestCase
public function testRemindOld()
{
if (Application::VERSION >= '5.2') {
$this->expectNotToPerformAssertions();
return;
}
......
......@@ -434,6 +434,16 @@ class ModelTest extends TestCase
$this->assertEquals((string) $user->getAttribute('entry.date')->format('Y-m-d H:i:s'), $data['entry']['date']);
}
public function testCarbonDateMockingWorks()
{
$fakeDate = \Carbon\Carbon::createFromDate(2000, 01, 01);
Carbon::setTestNow($fakeDate);
$item = Item::create(['name' => 'sword']);
$this->assertLessThan(1, $fakeDate->diffInSeconds($item->created_at));
}
public function testIdAttribute(): void
{
/** @var User $user */
......
<?php
declare(strict_types=1);
use Jenssegers\Mongodb\Queue\Failed\MongoFailedJobProvider;
class QueueTest extends TestCase
{
public function setUp(): void
......@@ -55,4 +57,38 @@ class QueueTest extends TestCase
$job->delete();
$this->assertEquals(0, Queue::getDatabase()->table(Config::get('queue.connections.database.table'))->count());
}
public function testFailQueueJob(): void
{
$provider = app('queue.failer');
$this->assertInstanceOf(MongoFailedJobProvider::class, $provider);
}
public function testFindFailJobNull(): void
{
Config::set('queue.failed.database', 'mongodb');
$provider = app('queue.failer');
$this->assertNull($provider->find(1));
}
public function testIncrementAttempts(): void
{
$job_id = Queue::push('test1', ['action' => 'QueueJobExpired'], 'test');
$this->assertNotNull($job_id);
$job_id = Queue::push('test2', ['action' => 'QueueJobExpired'], 'test');
$this->assertNotNull($job_id);
$job = Queue::pop('test');
$this->assertEquals(1, $job->attempts());
$job->delete();
$others_jobs = Queue::getDatabase()
->table(Config::get('queue.connections.database.table'))
->get();
$this->assertCount(1, $others_jobs);
$this->assertEquals(0, $others_jobs[0]['attempts']);
}
}
......@@ -147,6 +147,76 @@ class SchemaTest extends TestCase
$this->assertFalse($index);
}
public function testDropIndexIfExists(): void
{
Schema::collection('newcollection', function (Blueprint $collection) {
$collection->unique('uniquekey');
$collection->dropIndexIfExists('uniquekey_1');
});
$index = $this->getIndex('newcollection', 'uniquekey');
$this->assertEquals(null, $index);
Schema::collection('newcollection', function (Blueprint $collection) {
$collection->unique('uniquekey');
$collection->dropIndexIfExists(['uniquekey']);
});
$index = $this->getIndex('newcollection', 'uniquekey');
$this->assertEquals(null, $index);
Schema::collection('newcollection', function (Blueprint $collection) {
$collection->index(['field_a', 'field_b']);
});
$index = $this->getIndex('newcollection', 'field_a_1_field_b_1');
$this->assertNotNull($index);
Schema::collection('newcollection', function (Blueprint $collection) {
$collection->dropIndexIfExists(['field_a', 'field_b']);
});
$index = $this->getIndex('newcollection', 'field_a_1_field_b_1');
$this->assertFalse($index);
Schema::collection('newcollection', function (Blueprint $collection) {
$collection->index(['field_a', 'field_b'], 'custom_index_name');
});
$index = $this->getIndex('newcollection', 'custom_index_name');
$this->assertNotNull($index);
Schema::collection('newcollection', function (Blueprint $collection) {
$collection->dropIndexIfExists('custom_index_name');
});
$index = $this->getIndex('newcollection', 'custom_index_name');
$this->assertFalse($index);
}
public function testHasIndex(): void
{
$instance = $this;
Schema::collection('newcollection', function (Blueprint $collection) use ($instance) {
$collection->index('myhaskey1');
$instance->assertTrue($collection->hasIndex('myhaskey1_1'));
$instance->assertFalse($collection->hasIndex('myhaskey1'));
});
Schema::collection('newcollection', function (Blueprint $collection) use ($instance) {
$collection->index('myhaskey2');
$instance->assertTrue($collection->hasIndex(['myhaskey2']));
$instance->assertFalse($collection->hasIndex(['myhaskey2_1']));
});
Schema::collection('newcollection', function (Blueprint $collection) use ($instance) {
$collection->index(['field_a', 'field_b']);
$instance->assertTrue($collection->hasIndex(['field_a_1_field_b']));
$instance->assertFalse($collection->hasIndex(['field_a_1_field_b_1']));
});
}
public function testBackground(): void
{
Schema::collection('newcollection', function ($collection) {
......@@ -230,6 +300,7 @@ class SchemaTest extends TestCase
$collection->boolean('activated')->default(0);
$collection->integer('user_id')->unsigned();
});
$this->expectNotToPerformAssertions();
}
public function testSparseUnique(): void
......
......@@ -28,6 +28,7 @@ class TestCase extends Orchestra\Testbench\TestCase
{
return [
Jenssegers\Mongodb\MongodbServiceProvider::class,
Jenssegers\Mongodb\MongodbQueueServiceProvider::class,
Jenssegers\Mongodb\Auth\PasswordResetServiceProvider::class,
Jenssegers\Mongodb\Validation\ValidationServiceProvider::class,
];
......@@ -50,6 +51,7 @@ class TestCase extends Orchestra\Testbench\TestCase
$app['config']->set('database.default', 'mongodb');
$app['config']->set('database.connections.mysql', $config['connections']['mysql']);
$app['config']->set('database.connections.mongodb', $config['connections']['mongodb']);
$app['config']->set('database.connections.mongodb2', $config['connections']['mongodb']);
$app['config']->set('database.connections.dsn_mongodb', $config['connections']['dsn_mongodb']);
$app['config']->set('auth.model', 'User');
......@@ -63,5 +65,6 @@ class TestCase extends Orchestra\Testbench\TestCase
'queue' => 'default',
'expire' => 60,
]);
$app['config']->set('queue.failed.database', 'mongodb2');
}
}
......@@ -2,6 +2,7 @@
$mongoHost = env('MONGO_HOST', 'mongodb');
$mongoPort = env('MONGO_PORT') ? (int) env('MONGO_PORT') : 27017;
$mysqlPort = env('MYSQL_PORT') ? (int) env('MYSQL_PORT') : 3306;
return [
......@@ -23,6 +24,7 @@ return [
'mysql' => [
'driver' => 'mysql',
'host' => env('MYSQL_HOST', 'mysql'),
'port' => $mysqlPort,
'database' => env('MYSQL_DATABASE', 'unittest'),
'username' => env('MYSQL_USERNAME', 'root'),
'password' => env('MYSQL_PASSWORD', ''),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment