├── .coveralls.yml ├── .gitignore ├── .scrutinizer.yml ├── .sensiolabs.yml ├── .styleci.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── nitpick.json ├── phpcs.xml ├── phpmd.xml ├── phpunit.xml ├── src ├── Cache │ ├── CouchbaseLock.php │ ├── CouchbaseStore.php │ └── MemcachedBucketStore.php ├── Console │ ├── DesignCreatorCommand.php │ ├── IndexCreatorCommand.php │ ├── IndexFinderCommand.php │ ├── IndexRemoverCommand.php │ ├── PrimaryIndexCreatorCommand.php │ ├── PrimaryIndexRemoverCommand.php │ └── QueueCreatorCommand.php ├── ConsoleServiceProvider.php ├── CouchbaseServiceProvider.php ├── Database │ ├── Connectable.php │ ├── CouchbaseConnection.php │ └── CouchbaseConnector.php ├── Design │ └── AbstractDocument.php ├── Events │ ├── QueryPrepared.php │ ├── ResultReturning.php │ └── ViewQuerying.php ├── Exceptions │ ├── FlushException.php │ └── NotSupportedException.php ├── MemcachedConnector.php ├── Migrations │ └── CouchbaseMigrationRepository.php ├── Query │ ├── Builder.php │ ├── Grammar.php │ ├── Processor.php │ └── View.php ├── Queue │ ├── CouchbaseConnector.php │ └── CouchbaseQueue.php ├── Schema │ ├── Blueprint.php │ ├── Builder.php │ └── NotSupportedTrait.php ├── config │ └── couchbase.php ├── resources │ └── sample.ddoc └── transfer.php └── tests ├── Console ├── DesignCreatorCommandTest.php ├── IndexCreatorCommandTest.php ├── IndexFinderCommandTest.php ├── IndexRemoverCommandTest.php ├── PrimaryIndexCreatorCommandTest.php └── PrimaryIndexRemoverCommandTest.php ├── CouchbaseConnectorTest.php ├── CouchbaseGrammerTest.php ├── CouchbaseReconnectionTest.php ├── CouchbaseStoreSerializeTest.php ├── CouchbaseStoreTest.php ├── CouchbaseTestCase.php ├── DatabaseTest.php ├── DeleteQueryTest.php ├── DesignDocumentTest.php ├── MemcachedBucketTest.php ├── MemcachedConnectorTest.php ├── MockApplication.php ├── ProviderTest.php ├── Query └── ViewTest.php ├── Queue └── QueueCouchbaseConnectorTest.php ├── Schema ├── BlueprintTest.php └── BuilderTest.php ├── TaggingCacheStoreTest.php ├── UpdateQueryTest.php ├── bin └── memcached.sh ├── config ├── app.php ├── cache.php ├── couchbase.php ├── database.php ├── queue.php └── session.php ├── logs └── .gitignore └── resources └── sample.ddoc /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | coverage_clover: tests/build/clover.xml 3 | json_path: tests/build/coveralls-upload.json 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | composer.phar 4 | ocular.phar 5 | coverage.clover 6 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | paths: 3 | - src/* 4 | excluded_paths: 5 | - tests/* 6 | tools: 7 | php_code_sniffer: true 8 | php_cpd: true 9 | php_loc: true 10 | php_mess_detector: true 11 | php_pdepend: true 12 | php_analyzer: true 13 | sensiolabs_security_checker: true 14 | php_code_sniffer: 15 | config: 16 | standard: "PSR2" 17 | php_cs_fixer: 18 | config: { level: psr2 } 19 | external_code_coverage: 20 | timeout: 600 # Timeout in seconds. 21 | runs: 2 22 | checks: 23 | php: 24 | code_rating: true 25 | duplication: true 26 | -------------------------------------------------------------------------------- /.sensiolabs.yml: -------------------------------------------------------------------------------- 1 | global_exclude_dirs: 2 | - vendor 3 | - tests 4 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr2 2 | 3 | finder: 4 | exclude: 5 | - tests 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: php 3 | dist: trusty 4 | 5 | php: 6 | - 7.1 7 | 8 | before_script: 9 | - chmod -R 755 tests/bin/memcached.sh 10 | - tests/bin/memcached.sh 11 | - wget http://packages.couchbase.com/releases/couchbase-release/couchbase-release-1.0-4-amd64.deb 12 | - sudo dpkg -i couchbase-release-1.0-4-amd64.deb 13 | - sudo apt-get update 14 | - sudo apt-get install python libcouchbase-dev libcouchbase2-bin build-essential libssl-dev python-dev python-pip python-httplib2 15 | - wget https://packages.couchbase.com/releases/5.0.1/couchbase-server-community_5.0.1-ubuntu14.04_amd64.deb 16 | - sudo dpkg -i couchbase-server-community_5.0.1-ubuntu14.04_amd64.deb 17 | # Will install or upgrade packages 18 | - sudo apt-get update 19 | - sudo apt-get install debian-archive-keyring 20 | - sudo apt-get update 21 | #- sudo apt-get upgrade 22 | #- sudo apt-get install libcouchbase-dev libcouchbase2-bin build-essential libssl-dev 23 | - pecl install pcs-beta 24 | - pecl install igbinary 25 | - pecl install couchbase 26 | - cd $home/opt/couchbase 27 | - ./bin/couchbase-server -- -noinput -detached 28 | - sleep 20 29 | - ./bin/couchbase-cli cluster-init -c 127.0.0.1:8091 --cluster-username=Administrator --cluster-password=Administrator --cluster-port=8091 --cluster-index-ramsize=512 --cluster-ramsize=512 30 | #--services=data,query,index 31 | - ./bin/couchbase-cli rebalance -c 127.0.0.1:8091 -u Administrator -p Administrator 32 | - curl -u Administrator:Administrator -v -X POST 'http://127.0.0.1:8091/pools/default/buckets' -d name=testing -d ramQuotaMB=128 -d bucketType=couchbase -d flushEnabled=1 -d evictionPolicy=fullEviction -d authType=none -d saslPassword=none 33 | - sleep 10 34 | - curl -u Administrator:Administrator -v -X POST 'http://127.0.0.1:8091/pools/default/buckets' -d name=index_testing -d ramQuotaMB=128 -d bucketType=couchbase -d flushEnabled=1 -d evictionPolicy=fullEviction -d authType=none -d saslPassword=none 35 | - sleep 10 36 | - curl -u Administrator:Administrator -v -X POST 'http://127.0.0.1:8091/pools/default/buckets' -d name=memcache-couch -d ramQuotaMB=128 -d bucketType=memcached -d flushEnabled=1 -d evictionPolicy=fullEviction -d authType=none -d proxyPort=11255 -d saslPassword=none 37 | - sleep 10 38 | - ./bin/couchbase-cli user-manage -c localhost -u Administrator -p Administrator --set --rbac-username Administrator --rbac-password Administrator --rbac-name "Administrator" --roles bucket_full_access[*],ro_admin --auth-domain local 39 | - ./bin/cbq -e http://127.0.0.1:8091 --script "CREATE PRIMARY INDEX ON \`testing\`" -u=Administrator -p=Administrator 40 | - cd $TRAVIS_BUILD_DIR 41 | - composer self-update 42 | - composer install --prefer-source 43 | script: 44 | - chmod -R 777 tests/logs 45 | - ./vendor/bin/phpunit 46 | - chmod 777 tests/logs/clover.xml 47 | after_success: 48 | - if [[ ${TRAVIS_PHP_VERSION:0:3} == "7.1" ]]; then travis_retry php vendor/bin/coveralls; fi 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2018 Yuuki Takezawa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel-Couchbase 2 | for Laravel 5.1.*(higher) 3 | 4 | cache, session, database, queue extension package 5 | *required ext-couchbase* 6 | 7 | [![Build Status](https://img.shields.io/scrutinizer/build/g/ytake/Laravel-Couchbase/master.svg?style=flat-square)](https://travis-ci.org/ytake/Laravel-Couchbase) 8 | [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/ytake/Laravel-Couchbase/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/ytake/Laravel-Couchbase/?branch=master) 9 | [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/ytake/Laravel-Couchbase/master.svg?style=flat-square)](https://scrutinizer-ci.com/g/ytake/Laravel-Couchbase/?branch=masnter) 10 | [![StyleCI](https://styleci.io/repos/45177780/shield)](https://styleci.io/repos/45177780) 11 | 12 | [![Packagist](https://img.shields.io/packagist/dt/ytake/laravel-couchbase.svg?style=flat-square)](https://packagist.org/packages/ytake/laravel-couchbase) 13 | [![Packagist](https://img.shields.io/packagist/v/ytake/laravel-couchbase.svg?style=flat-square)](https://packagist.org/packages/ytake/laravel-couchbase) 14 | [![Packagist](https://img.shields.io/packagist/l/ytake/laravel-couchbase.svg?style=flat-square)](https://packagist.org/packages/ytake/laravel-couchbase) 15 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/e775c5758be749868fcd8ac6680dfc69)](https://www.codacy.com/app/yuuki-takezawa/Laravel-Couchbase?utm_source=github.com&utm_medium=referral&utm_content=ytake/Laravel-Couchbase&utm_campaign=Badge_Grade) 16 | 17 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/944f9bc0-7ee6-4f5f-b371-8ec216ea317e/mini.png)](https://insight.sensiolabs.com/projects/944f9bc0-7ee6-4f5f-b371-8ec216ea317e) 18 | 19 | ## Notice 20 | Supported Auto-Discovery, Design Document, Cache Lock (Laravel5.5) 21 | 22 | | Laravel version | Laravel-Couchbase version | ext-couchbase | 23 | | ------------- | ------------- | ------------------| 24 | | Laravel 5.6 | ^1.1 | >=2.3.2 | 25 | | Laravel 5.5 | ^1.0 | >=2.3.2 | 26 | | Laravel 5.4 | ^0.7 | ^2.2.2 | 27 | | Laravel 5.3 | ^0.6 | ^2.2.2 | 28 | | Laravel 5.2 | ^0.5 | ^2.2.2 | 29 | | Laravel 5.1 | ^0.4 | ^2.2.2 | 30 | 31 | ### Deprecated 32 | 33 | *not recommended* couchbase-memcached driver `couchbase session driver` 34 | 35 | ## install 36 | 37 | ```bash 38 | $ composer require ytake/laravel-couchbase 39 | ``` 40 | 41 | or your config/app.php 42 | 43 | ```php 44 | 'providers' => [ 45 | // added service provider 46 | \Ytake\LaravelCouchbase\CouchbaseServiceProvider::class, 47 | \Ytake\LaravelCouchbase\ConsoleServiceProvider::class, 48 | ] 49 | ``` 50 | 51 | ## usage 52 | ### database extension 53 | 54 | add database driver(config/database.php) 55 | 56 | ```php 57 | 58 | 'couchbase' => [ 59 | 'driver' => 'couchbase', 60 | 'host' => 'couchbase://127.0.0.1', 61 | 'user' => 'userName', // optional administrator 62 | 'password' => 'password', // optional administrator 63 | // optional configuration / management operations against a bucket. 64 | 'administrator' => [ 65 | 'user' => 'Administrator', 66 | 'password' => 'password', 67 | ], 68 | ], 69 | ``` 70 | 71 | case cluster 72 | 73 | ```php 74 | 75 | 'couchbase' => [ 76 | 'driver' => 'couchbase', 77 | 'host' => 'couchbase://127.0.0.1,192.168.1.2', 78 | 'user' => 'userName', // optional administrator 79 | 'password' => 'password', // optional administrator 80 | ], 81 | ``` 82 | 83 | choose bucket `table()` method 84 | or 85 | 86 | basic usage `bucket()` method 87 | 88 | N1QL supported(upsert enabled) 89 | 90 | see http://developer.couchbase.com/documentation/server/4.1/n1ql/n1ql-language-reference/index.html 91 | 92 | #### SELECT 93 | 94 | ```php 95 | // service container access 96 | $this->app['db']->connection('couchbase') 97 | ->table('testing')->where('whereKey', 'value')->first(); 98 | 99 | // use DB facades 100 | \DB::connection('couchbase') 101 | ->table('testing')->where('whereKey', 'value')->get(); 102 | ``` 103 | 104 | #### INSERT / UPSERT 105 | 106 | ```php 107 | $value = [ 108 | 'click' => 'to edit', 109 | 'content' => 'testing' 110 | ]; 111 | $key = 'insert:and:delete'; 112 | 113 | $result = \DB::connection('couchbase') 114 | ->table('testing')->key($key)->insert($value); 115 | 116 | \DB::connection('couchbase') 117 | ->table('testing')->key($key)->upsert([ 118 | 'click' => 'to edit', 119 | 'content' => 'testing for upsert', 120 | ]); 121 | ``` 122 | 123 | #### DELETE / UPDATE 124 | 125 | ```php 126 | \DB::connection('couchbase') 127 | ->table('testing')->key($key)->where('clicking', 'to edit')->delete(); 128 | 129 | \DB::connection('couchbase') 130 | ->table('testing')->key($key) 131 | ->where('click', 'to edit')->update( 132 | ['click' => 'testing edit'] 133 | ); 134 | ``` 135 | 136 | ##### execute queries 137 | example) 138 | 139 | ````php 140 | "delete from testing USE KEYS "delete" RETURNING *" 141 | "update testing USE KEYS "insert" set click = ? where click = ? RETURNING *" 142 | ```` 143 | 144 | #### returning 145 | 146 | default * 147 | 148 | ```php 149 | \DB::connection('couchbase') 150 | ->table('testing') 151 | ->where('id', 1) 152 | ->offset($from)->limit($perPage) 153 | ->orderBy('created_at', $sort) 154 | ->returning(['id', 'name'])->get(); 155 | ``` 156 | 157 | #### View Query 158 | 159 | ```php 160 | $view = \DB::view("testing"); 161 | $result = $view->execute($view->from("dev_testing", "testing")); 162 | ``` 163 | 164 | ### cache extension 165 | #### for bucket type couchbase 166 | 167 | *config/cache.php* 168 | 169 | ```php 170 | 'couchbase' => [ 171 | 'driver' => 'couchbase', 172 | 'bucket' => 'session' 173 | ], 174 | ``` 175 | 176 | #### for bucket type memcached 177 | 178 | ```php 179 | 'couchbase-memcached' => [ 180 | 'driver' => 'couchbase-memcached', 181 | 'servers' => [ 182 | [ 183 | 'host' => '127.0.0.1', 184 | 'port' => 11255, 185 | 'weight' => 100, 186 | 'bucket' => 'memcached-bucket-name', 187 | 'option' => [ 188 | // curl option 189 | ], 190 | ], 191 | ], 192 | ], 193 | ``` 194 | 195 | *not supported* 196 | 197 | ### couchbase bucket, use bucket password 198 | 199 | *config/cache.php* 200 | 201 | ```php 202 | 'couchbase' => [ 203 | 'driver' => 'couchbase', 204 | 'bucket' => 'session', 205 | 'bucket_password' => 'your bucket password' 206 | ], 207 | 208 | ``` 209 | 210 | ### session extension 211 | 212 | .env etc.. 213 | 214 | specify couchbase driver 215 | 216 | ### consistency 217 | default :CouchbaseN1qlQuery::NOT_BOUNDED 218 | 219 | ```php 220 | $this->app['db']->connection('couchbase') 221 | ->consistency(\CouchbaseN1qlQuery::REQUEST_PLUS) 222 | ->table('testing') 223 | ->where('id', 1) 224 | ->returning(['id', 'name'])->get(); 225 | ``` 226 | 227 | #### callable consistency 228 | 229 | ```php 230 | $this->app['db']->connection('couchbase') 231 | ->callableConsistency(\CouchbaseN1qlQuery::REQUEST_PLUS, function ($con) { 232 | return $con->table('testing')->where('id', 1)->returning(['id', 'name'])->get(); 233 | }); 234 | ``` 235 | 236 | ### Event 237 | for N1QL 238 | 239 | | events | description | 240 | | ------------- | ------------- | 241 | | \Ytake\LaravelCouchbase\Events\QueryPrepared | get n1ql query | 242 | | \Ytake\LaravelCouchbase\Events\ResultReturning | get all property from returned result | 243 | | \Ytake\LaravelCouchbase\Events\ViewQuerying | for view query (request uri) | 244 | 245 | ### Schema / Migrations 246 | The database driver also has (limited) schema builder support. 247 | easily manipulate indexes(primary and secondary) 248 | 249 | ```php 250 | use Ytake\LaravelCouchbase\Schema\Blueprint as CouchbaseBlueprint; 251 | 252 | \Schema::create('samples', function (CouchbaseBlueprint $table) { 253 | $table->primaryIndex(); // primary index 254 | $table->index(['message', 'identifier'], 'sample_secondary_index'); // secondary index 255 | // dropped 256 | $table->dropIndex('sample_secondary_index'); 257 | $table->dropPrimary(); 258 | }); 259 | ``` 260 | 261 | Supported operations: 262 | 263 | - create and drop 264 | - index and dropIndex (primary index and secondary index) 265 | 266 | ### Artisan 267 | for couchbase manipulate indexes 268 | 269 | | commands | description | 270 | | ------------- | ------------- | 271 | | couchbase:create-index | Create a secondary index for the current bucket. | 272 | | couchbase:create-primary-index | Create a primary N1QL index for the current bucket. | 273 | | couchbase:drop-index | Drop the given secondary index associated with the current bucket. | 274 | | couchbase:drop-primary-index | Drop the given primary index associated with the current bucket. | 275 | | couchbase:indexes | List all N1QL indexes that are registered for the current bucket. | 276 | | couchbase:create-queue-index | Create primary index, secondary indexes for the queue jobs couchbase bucket. | 277 | | couchbase:create-design | Inserts design document and fails if it is exist already. for MapReduce views | 278 | 279 | `-h` more information. 280 | 281 | #### create design 282 | 283 | config/couchbase.php 284 | 285 | ```php 286 | return [ 287 | 'design' => [ 288 | 'Your Design Document Name' => [ 289 | 'views' => [ 290 | 'Your View Name' => [ 291 | 'map' => file_get_contents(__DIR__ . '/../resources/sample.ddoc'), 292 | ], 293 | ], 294 | ], 295 | ] 296 | ]; 297 | 298 | ``` 299 | 300 | ## Queue 301 | 302 | Change the the driver in config/queue.php: 303 | 304 | ```php 305 | 'connections' => [ 306 | 'couchbase' => [ 307 | 'driver' => 'couchbase', 308 | 'bucket' => 'jobs', 309 | 'queue' => 'default', 310 | 'retry_after' => 90, 311 | ], 312 | ], 313 | ``` 314 | 315 | example 316 | 317 | ```bash 318 | php artisan queue:work couchbase --queue=send_email 319 | ``` 320 | 321 | ## hacking 322 | 323 | To run tests there are should be following buckets created on local Couchbase cluster: 324 | 325 | ``` php 326 | $cluster = new CouchbaseCluster('couchbase://127.0.0.1'); 327 | $clusterManager = $cluster->manager('Administrator', 'password'); 328 | $clusterManager->createBucket('testing', ['bucketType' => 'couchbase', 'saslPassword' => '', 'flushEnabled' => true]); 329 | $clusterManager->createBucket('memcache-couch', ['bucketType' => 'memcached', 'saslPassword' => '', 'flushEnabled' => true]); 330 | sleep(5); 331 | $bucketManager = $cluster->openBucket('testing')->manager(); 332 | $bucketManager->createN1qlPrimaryIndex(); 333 | ``` 334 | 335 | Also tests are expecting regular Memcached daemon listening on port 11255. 336 | 337 | ## soon 338 | - authintication driver 339 | - Eloquent support 340 | 341 | ## Couchbase Document 342 | 343 | [REST API / Creating and Editing Buckets](https://developer.couchbase.com/documentation/server/current/rest-api/rest-bucket-create.html) 344 | [couchbase-cli / user-manage](https://developer.couchbase.com/documentation/server/5.1/cli/cbcli/couchbase-cli-user-manage.html) 345 | [Authentication](https://developer.couchbase.com/documentation/server/5.1/security/security-authentication.html) 346 | [Authorization API](https://developer.couchbase.com/documentation/server/5.1/rest-api/rest-authorization.html) 347 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ytake/laravel-couchbase", 3 | "description": "Couchbase providers for Laravel", 4 | "keywords": [ 5 | "laravel", 6 | "couchbase", 7 | "database", 8 | "session", 9 | "cache", 10 | "queue" 11 | ], 12 | "authors": [ 13 | { 14 | "name": "Yuuki Takezawa", 15 | "email": "yuuki.takezawa@comnect.jp.net" 16 | } 17 | ], 18 | "license": "MIT", 19 | "require": { 20 | "php": "^7.1.3", 21 | "ext-couchbase": ">=2.3.2", 22 | "illuminate/support": "5.6.*", 23 | "illuminate/config": "5.6.*", 24 | "illuminate/console": "5.6.*", 25 | "illuminate/events": "5.6.*", 26 | "illuminate/cache": "5.6.*", 27 | "illuminate/session": "5.6.*", 28 | "illuminate/database": "5.6.*", 29 | "illuminate/encryption": "5.6.*", 30 | "illuminate/queue": "5.6.*", 31 | "illuminate/contracts": "5.6.*", 32 | "illuminate/container": "5.6.*" 33 | }, 34 | "require-dev": { 35 | "symfony/framework-bundle": "^4.0", 36 | "symfony/console": "^4.0", 37 | "phpunit/phpunit": "^6.0", 38 | "satooshi/php-coveralls": "*", 39 | "phploc/phploc": "*", 40 | "pdepend/pdepend" : "^2.2.4", 41 | "phpmd/phpmd": "@stable", 42 | "friendsofphp/php-cs-fixer": "^2.0" 43 | }, 44 | "autoload": { 45 | "psr-4": { 46 | "Ytake\\LaravelCouchbase\\": "src" 47 | }, 48 | "files": [ 49 | "src/transfer.php" 50 | ] 51 | }, 52 | "autoload-dev": { 53 | "classmap": [ 54 | "tests/CouchbaseTestCase.php", 55 | "tests/MockApplication.php" 56 | ] 57 | }, 58 | "scripts": { 59 | "test": [ 60 | "php vendor/bin/phpunit" 61 | ], 62 | "cs": [ 63 | "php vendor/bin/php-cs-fixer fix" 64 | ], 65 | "scrutinizer_test": [ 66 | "php vendor/bin/phpunit --coverage-clover=coverage.clover", 67 | "wget https://scrutinizer-ci.com/ocular.phar", 68 | "php ocular.phar code-coverage:upload --format=php-clover coverage.clover" 69 | ] 70 | }, 71 | "minimum-stability": "stable", 72 | "extra": { 73 | "laravel": { 74 | "providers": [ 75 | "Ytake\\LaravelCouchbase\\CouchbaseServiceProvider", 76 | "Ytake\\LaravelCouchbase\\ConsoleServiceProvider" 77 | ] 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /nitpick.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": [ 3 | "tests/*" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /phpmd.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | 20 | ./src 21 | 22 | ./src/CouchbaseServiceProvider.php 23 | ./src/ConsoleServiceProvider.php 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Cache/CouchbaseLock.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class CouchbaseLock extends Lock implements Lockable 28 | { 29 | /** @var Bucket */ 30 | protected $bucket; 31 | 32 | /** 33 | * CouchbaseLock constructor. 34 | * 35 | * @param Bucket $bucket 36 | * @param string $name 37 | * @param int $seconds 38 | */ 39 | public function __construct(Bucket $bucket, string $name, int $seconds) 40 | { 41 | parent::__construct($name, $seconds); 42 | 43 | $this->bucket = $bucket; 44 | } 45 | 46 | /** 47 | * @return bool 48 | */ 49 | public function acquire() 50 | { 51 | try { 52 | $result = $this->bucket->insert($this->name, 1, ['expiry' => $this->seconds]); 53 | if ($result instanceof Document) { 54 | return true; 55 | } 56 | } catch (CouchbaseException $e) { 57 | return false; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function release() 67 | { 68 | $this->bucket->remove($this->name); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Cache/CouchbaseStore.php: -------------------------------------------------------------------------------- 1 | 29 | */ 30 | class CouchbaseStore extends TaggableStore implements Store 31 | { 32 | use RetrievesMultipleKeys; 33 | 34 | /** @var string */ 35 | protected $prefix; 36 | 37 | /** @var Bucket */ 38 | protected $bucket; 39 | 40 | /** @var Cluster */ 41 | protected $cluster; 42 | 43 | /** 44 | * CouchbaseStore constructor. 45 | * 46 | * @param Cluster $cluster 47 | * @param string $bucket 48 | * @param string $password 49 | * @param string|null $prefix 50 | * @param string $serialize 51 | */ 52 | public function __construct( 53 | Cluster $cluster, 54 | string $bucket, 55 | string $password = '', 56 | string $prefix = null, 57 | string $serialize = 'php' 58 | ) { 59 | $this->cluster = $cluster; 60 | $this->setBucket($bucket, $password, $serialize); 61 | $this->setPrefix($prefix); 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function get($key) 68 | { 69 | try { 70 | $result = $this->bucket->get($this->resolveKey($key)); 71 | 72 | return $this->getMetaDoc($result); 73 | } catch (CouchbaseException $e) { 74 | return; 75 | } 76 | } 77 | 78 | /** 79 | * Store an item in the cache if the key doesn't exist. 80 | * 81 | * @param string|array $key 82 | * @param mixed $value 83 | * @param int $minutes 84 | * 85 | * @return bool 86 | */ 87 | public function add($key, $value, $minutes = 0): bool 88 | { 89 | $options = ($minutes === 0) ? [] : ['expiry' => ($minutes * 60)]; 90 | try { 91 | $this->bucket->insert($this->resolveKey($key), $value, $options); 92 | 93 | return true; 94 | } catch (CouchbaseException $e) { 95 | return false; 96 | } 97 | } 98 | 99 | /** 100 | * {@inheritdoc} 101 | */ 102 | public function put($key, $value, $minutes) 103 | { 104 | $this->bucket->upsert($this->resolveKey($key), $value, ['expiry' => $minutes * 60]); 105 | } 106 | 107 | /** 108 | * {@inheritdoc} 109 | */ 110 | public function increment($key, $value = 1) 111 | { 112 | return $this->bucket 113 | ->counter($this->resolveKey($key), $value, ['initial' => abs($value)])->value; 114 | } 115 | 116 | /** 117 | * {@inheritdoc} 118 | */ 119 | public function decrement($key, $value = 1) 120 | { 121 | return $this->bucket 122 | ->counter($this->resolveKey($key), (0 - abs($value)), ['initial' => (0 - abs($value))])->value; 123 | } 124 | 125 | /** 126 | * {@inheritdoc} 127 | */ 128 | public function forever($key, $value) 129 | { 130 | try { 131 | $this->bucket->insert($this->resolveKey($key), $value); 132 | } catch (CouchbaseException $e) { 133 | // bucket->insert when called from resetTag in TagSet can throw CAS exceptions, ignore.\ 134 | $this->bucket->upsert($this->resolveKey($key), $value); 135 | } 136 | } 137 | 138 | /** 139 | * {@inheritdoc} 140 | */ 141 | public function forget($key) 142 | { 143 | try { 144 | $this->bucket->remove($this->resolveKey($key)); 145 | } catch (\Exception $e) { 146 | // Ignore exceptions from remove 147 | } 148 | } 149 | 150 | /** 151 | * flush bucket. 152 | * 153 | * @throws FlushException 154 | * @codeCoverageIgnore 155 | */ 156 | public function flush() 157 | { 158 | $result = $this->bucket->manager()->flush(); 159 | if (isset($result['_'])) { 160 | throw new FlushException($result); 161 | } 162 | } 163 | 164 | /** 165 | * {@inheritdoc} 166 | */ 167 | public function getPrefix() 168 | { 169 | return $this->prefix; 170 | } 171 | 172 | /** 173 | * Set the cache key prefix. 174 | * 175 | * @param string $prefix 176 | */ 177 | public function setPrefix(string $prefix) 178 | { 179 | $this->prefix = !empty($prefix) ? $prefix . ':' : ''; 180 | } 181 | 182 | /** 183 | * @param string $bucket 184 | * @param string $password 185 | * @param string $serialize 186 | * 187 | * @return CouchbaseStore 188 | */ 189 | public function setBucket(string $bucket, string $password = '', string $serialize = 'php'): CouchbaseStore 190 | { 191 | $this->bucket = $this->cluster->openBucket($bucket, $password); 192 | if ($serialize === 'php') { 193 | $this->bucket->setTranscoder('couchbase_php_serialize_encoder', 'couchbase_default_decoder'); 194 | } 195 | 196 | return $this; 197 | } 198 | 199 | /** 200 | * @param $keys 201 | * 202 | * @return array|string 203 | */ 204 | private function resolveKey($keys) 205 | { 206 | if (is_array($keys)) { 207 | $result = []; 208 | foreach ($keys as $key) { 209 | $result[] = $this->prefix . $key; 210 | } 211 | 212 | return $result; 213 | } 214 | 215 | return $this->prefix . $keys; 216 | } 217 | 218 | /** 219 | * @param $meta 220 | * 221 | * @return array|null 222 | */ 223 | protected function getMetaDoc($meta) 224 | { 225 | if ($meta instanceof \Couchbase\Document) { 226 | return $meta->value; 227 | } 228 | if (is_array($meta)) { 229 | $result = []; 230 | foreach ($meta as $row) { 231 | $result[] = $this->getMetaDoc($row); 232 | } 233 | 234 | return $result; 235 | } 236 | 237 | return null; 238 | } 239 | 240 | /** 241 | * Get a lock instance. 242 | * 243 | * @param string $name 244 | * @param int $seconds 245 | * @return \Illuminate\Contracts\Cache\Lock 246 | */ 247 | public function lock(string $name, int $seconds = 0): Lock 248 | { 249 | return new CouchbaseLock($this->bucket, $this->prefix.$name, $seconds); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /src/Cache/MemcachedBucketStore.php: -------------------------------------------------------------------------------- 1 | 22 | * @deprecated 23 | */ 24 | class MemcachedBucketStore extends MemcachedStore 25 | { 26 | /** @var string[] */ 27 | protected $servers; 28 | 29 | /** @var array */ 30 | protected $options = [ 31 | CURLOPT_RETURNTRANSFER => false, 32 | CURLOPT_SSL_VERIFYPEER => false, 33 | ]; 34 | 35 | /** @var int */ 36 | protected $port = 8091; 37 | 38 | /** @var int */ 39 | protected $timeout = 1; 40 | 41 | /** @var string */ 42 | protected $flushEndpoint = ':%s/pools/default/buckets/%s/controller/doFlush'; 43 | 44 | /** @var array */ 45 | protected $sasl = []; 46 | 47 | /** 48 | * MemcachedBucketStore constructor. 49 | * 50 | * @param \Memcached $memcached 51 | * @param string $prefix 52 | * @param array $servers 53 | * @param array $sasl 54 | */ 55 | public function __construct( 56 | \Memcached $memcached, 57 | string $prefix = '', 58 | array $servers, 59 | array $sasl = [] 60 | ) { 61 | parent::__construct($memcached, $prefix); 62 | $this->servers = $servers; 63 | $this->sasl = $sasl; 64 | } 65 | 66 | /** 67 | * Increment the value of an item in the cache. 68 | * 69 | * @param string $key 70 | * @param mixed $value 71 | * 72 | * @return int|bool 73 | */ 74 | public function increment($key, $value = 1) 75 | { 76 | if ($integer = $this->get($key)) { 77 | $this->put($key, $integer + $value, 0); 78 | 79 | return $integer + $value; 80 | } 81 | 82 | $this->put($key, $value, 0); 83 | 84 | return $value; 85 | } 86 | 87 | /** 88 | * Decrement the value of an item in the cache. 89 | * 90 | * @param string $key 91 | * @param mixed $value 92 | * 93 | * @return int|bool 94 | */ 95 | public function decrement($key, $value = 1) 96 | { 97 | $decrement = 0; 98 | if ($integer = $this->get($key)) { 99 | $decrement = $integer - $value; 100 | if ($decrement <= 0) { 101 | $decrement = 0; 102 | } 103 | $this->put($key, $decrement, 0); 104 | 105 | return $decrement; 106 | } 107 | 108 | $this->put($key, $decrement, 0); 109 | 110 | return $decrement; 111 | } 112 | 113 | /** 114 | * {@inheritdoc} 115 | */ 116 | public function flush() 117 | { 118 | $handler = curl_multi_init(); 119 | foreach ($this->servers as $server) { 120 | $initialize = curl_init(); 121 | $configureOption = (isset($server['options'])) ? $server['options'] : []; 122 | 123 | $options = array_replace($this->options, [ 124 | CURLOPT_POST => true, 125 | CURLOPT_URL => $server['host'] . sprintf($this->flushEndpoint, $this->port, $server['bucket']), 126 | ], $configureOption, $this->setCredential()); 127 | curl_setopt_array($initialize, $options); 128 | curl_multi_add_handle($handler, $initialize); 129 | } 130 | $this->callMulti($handler); 131 | } 132 | 133 | /** 134 | * @return array 135 | */ 136 | protected function setCredential(): array 137 | { 138 | if (count($this->sasl) === 2) { 139 | list($username, $password) = $this->sasl; 140 | 141 | return [CURLOPT_USERPWD => "{$username}:{$password}"]; 142 | } 143 | 144 | return []; 145 | } 146 | 147 | /** 148 | * @param $handler 149 | * 150 | * @throws \RuntimeException 151 | */ 152 | protected function callMulti($handler) 153 | { 154 | $running = null; 155 | 156 | do { 157 | $stat = curl_multi_exec($handler, $running); 158 | } while ($stat === CURLM_CALL_MULTI_PERFORM); 159 | if (!$running || $stat !== CURLM_OK) { 160 | throw new \RuntimeException('failed to initialized cURL'); 161 | } 162 | 163 | do { 164 | curl_multi_select($handler, $this->timeout); 165 | do { 166 | $stat = curl_multi_exec($handler, $running); 167 | } while ($stat === CURLM_CALL_MULTI_PERFORM); 168 | do { 169 | if ($read = curl_multi_info_read($handler, $remains)) { 170 | $response = curl_multi_getcontent($read['handle']); 171 | 172 | if ($response === false) { 173 | $info = curl_getinfo($read['handle']); 174 | throw new \RuntimeException("error: {$info['url']}: {$info['http_code']}"); 175 | } 176 | curl_multi_remove_handle($handler, $read['handle']); 177 | curl_close($read['handle']); 178 | } 179 | } while ($remains); 180 | } while ($running); 181 | curl_multi_close($handler); 182 | } 183 | 184 | /** 185 | * @param int $second 186 | */ 187 | public function timeout(int $second) 188 | { 189 | $this->timeout = $second; 190 | } 191 | 192 | /** 193 | * @param int $port 194 | */ 195 | public function port(int $port) 196 | { 197 | $this->port = $port; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/Console/DesignCreatorCommand.php: -------------------------------------------------------------------------------- 1 | */ 40 | private $config = []; 41 | 42 | /** 43 | * DesignCreatorCommand constructor. 44 | * 45 | * @param DatabaseManager $databaseManager 46 | * @param array $config 47 | */ 48 | public function __construct(DatabaseManager $databaseManager, array $config = []) 49 | { 50 | $this->databaseManager = $databaseManager; 51 | $this->config = $config; 52 | parent::__construct(); 53 | } 54 | 55 | /** 56 | * @return string[] 57 | */ 58 | protected function getArguments() 59 | { 60 | return [ 61 | ['bucket', InputArgument::REQUIRED, 'Represents a bucket connection.'], 62 | ]; 63 | } 64 | 65 | /** 66 | * Get the console command options. 67 | * 68 | * @return array 69 | */ 70 | protected function getOptions() 71 | { 72 | return [ 73 | ['database', 'db', InputOption::VALUE_REQUIRED, 'The database connection to use.', $this->defaultDatabase], 74 | ]; 75 | } 76 | 77 | /** 78 | * Execute the console command 79 | */ 80 | public function handle() 81 | { 82 | /** @var \Illuminate\Database\Connection|CouchbaseConnection $connection */ 83 | $connection = $this->databaseManager->connection($this->option('database')); 84 | if ($connection instanceof CouchbaseConnection) { 85 | /** @var \Couchbase\Bucket $bucket */ 86 | $bucket = $connection->openBucket($this->argument('bucket')); 87 | foreach ($this->config as $name => $document) { 88 | $bucket->manager()->insertDesignDocument($name, $document); 89 | $this->comment("created view name [{$name}]"); 90 | } 91 | } 92 | 93 | return; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Console/IndexCreatorCommand.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class IndexCreatorCommand extends Command 28 | { 29 | /** @var string */ 30 | protected $name = 'couchbase:create-index'; 31 | 32 | /** @var string */ 33 | protected $description = 'Create a secondary index for the current bucket.'; 34 | 35 | /** @var DatabaseManager */ 36 | protected $databaseManager; 37 | 38 | /** @var string */ 39 | protected $defaultDatabase = 'couchbase'; 40 | 41 | /** 42 | * IndexFinderCommand constructor. 43 | * 44 | * @param DatabaseManager $databaseManager 45 | */ 46 | public function __construct(DatabaseManager $databaseManager) 47 | { 48 | $this->databaseManager = $databaseManager; 49 | parent::__construct(); 50 | } 51 | 52 | /** 53 | * @return string[] 54 | */ 55 | protected function getArguments() 56 | { 57 | return [ 58 | ['bucket', InputArgument::REQUIRED, 'Represents a bucket connection.'], 59 | ['name', InputArgument::REQUIRED, 'the name of the index.'], 60 | ['fields', InputArgument::IS_ARRAY | InputArgument::REQUIRED, 'the JSON fields to index.'], 61 | ]; 62 | } 63 | 64 | /** 65 | * Get the console command options. 66 | * 67 | * @return array 68 | */ 69 | protected function getOptions() 70 | { 71 | return [ 72 | ['database', 'db', InputOption::VALUE_REQUIRED, 'The database connection to use.', $this->defaultDatabase], 73 | [ 74 | 'where', 75 | null, 76 | InputOption::VALUE_REQUIRED, 77 | 'the WHERE clause of the index.', 78 | '', 79 | ], 80 | [ 81 | 'ignore', 82 | 'ig', 83 | InputOption::VALUE_NONE, 84 | 'if a primary index already exists, an exception will be thrown unless this is set to true.', 85 | ], 86 | [ 87 | 'defer', 88 | null, 89 | InputOption::VALUE_NONE, 90 | 'true to defer building of the index until buildN1qlDeferredIndexes()}is called (or a direct call to the corresponding query service API)', 91 | ], 92 | ]; 93 | } 94 | 95 | /** 96 | * Execute the console command 97 | */ 98 | public function handle() 99 | { 100 | /** @var \Illuminate\Database\Connection|CouchbaseConnection $connection */ 101 | $connection = $this->databaseManager->connection($this->option('database')); 102 | if ($connection instanceof CouchbaseConnection) { 103 | $bucket = $connection->openBucket($this->argument('bucket')); 104 | $fields = $this->argument('fields'); 105 | $whereClause = $this->option('where'); 106 | $name = $this->argument('name'); 107 | $bucket->manager()->createN1qlIndex( 108 | $name, 109 | $fields, 110 | $whereClause, 111 | $this->option('ignore'), 112 | $this->option('defer') 113 | ); 114 | $field = implode(",", $fields); 115 | $this->info("created SECONDARY INDEX [{$name}] fields [{$field}], for [{$this->argument('bucket')}] bucket."); 116 | if ($whereClause !== '') { 117 | $this->comment("WHERE clause [{$whereClause}]"); 118 | } 119 | } 120 | 121 | return; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Console/IndexFinderCommand.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class IndexFinderCommand extends Command 28 | { 29 | /** @var string */ 30 | protected $name = 'couchbase:indexes'; 31 | 32 | /** @var string */ 33 | protected $description = 'List all N1QL indexes that are registered for the current bucket.'; 34 | 35 | /** @var DatabaseManager */ 36 | protected $databaseManager; 37 | 38 | /** @var string */ 39 | protected $defaultDatabase = 'couchbase'; 40 | 41 | /** @var string[] */ 42 | private $headers = [ 43 | "name", 44 | "isPrimary", 45 | "type", 46 | "state", 47 | "keyspace", 48 | "namespace", 49 | "fields", 50 | "condition", 51 | ]; 52 | 53 | /** 54 | * IndexFinderCommand constructor. 55 | * 56 | * @param DatabaseManager $databaseManager 57 | */ 58 | public function __construct(DatabaseManager $databaseManager) 59 | { 60 | $this->databaseManager = $databaseManager; 61 | parent::__construct(); 62 | } 63 | 64 | /** 65 | * @return string[] 66 | */ 67 | protected function getArguments() 68 | { 69 | return [ 70 | ['bucket', InputArgument::REQUIRED, 'Represents a bucket connection.'], 71 | ]; 72 | } 73 | 74 | /** 75 | * Get the console command options. 76 | * 77 | * @return array 78 | */ 79 | protected function getOptions() 80 | { 81 | return [ 82 | ['database', 'db', InputOption::VALUE_REQUIRED, 'The database connection to use.', $this->defaultDatabase], 83 | ]; 84 | } 85 | 86 | /** 87 | * Execute the console command 88 | */ 89 | public function handle() 90 | { 91 | $row = []; 92 | $tableRows = []; 93 | /** @var \Illuminate\Database\Connection|CouchbaseConnection $connection */ 94 | $connection = $this->databaseManager->connection($this->option('database')); 95 | if ($connection instanceof CouchbaseConnection) { 96 | $bucket = $connection->getCouchbase()->openBucket($this->argument('bucket')); 97 | $indexes = $bucket->manager()->listN1qlIndexes(); 98 | foreach ($indexes as $index) { 99 | foreach ($index as $key => $value) { 100 | if (array_search($key, $this->headers) !== false) { 101 | $row[] = (!is_array($value)) ? $value : implode(",", $value); 102 | } 103 | } 104 | $tableRows[] = $row; 105 | $row = []; 106 | } 107 | $this->table($this->headers, $tableRows); 108 | } 109 | 110 | return; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Console/IndexRemoverCommand.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class IndexRemoverCommand extends Command 28 | { 29 | /** @var string */ 30 | protected $name = 'couchbase:drop-index'; 31 | 32 | /** @var string */ 33 | protected $description = 'Drop the given secondary index associated with the current bucket.'; 34 | 35 | /** @var DatabaseManager */ 36 | protected $databaseManager; 37 | 38 | /** @var string */ 39 | protected $defaultDatabase = 'couchbase'; 40 | 41 | /** 42 | * IndexFinderCommand constructor. 43 | * 44 | * @param DatabaseManager $databaseManager 45 | */ 46 | public function __construct(DatabaseManager $databaseManager) 47 | { 48 | $this->databaseManager = $databaseManager; 49 | parent::__construct(); 50 | } 51 | 52 | /** 53 | * @return string[] 54 | */ 55 | protected function getArguments() 56 | { 57 | return [ 58 | ['bucket', InputArgument::REQUIRED, 'Represents a bucket connection.'], 59 | ['name', InputArgument::REQUIRED, 'the name of the index.'], 60 | ]; 61 | } 62 | 63 | /** 64 | * Get the console command options. 65 | * 66 | * @return array 67 | */ 68 | protected function getOptions() 69 | { 70 | return [ 71 | ['database', 'db', InputOption::VALUE_REQUIRED, 'The database connection to use.', $this->defaultDatabase], 72 | [ 73 | 'ignore', 74 | 'ig', 75 | InputOption::VALUE_NONE, 76 | 'if a primary index already exists, an exception will be thrown unless this is set to true.', 77 | ], 78 | ]; 79 | } 80 | 81 | /** 82 | * Execute the console command 83 | */ 84 | public function handle() 85 | { 86 | /** @var \Illuminate\Database\Connection|CouchbaseConnection $connection */ 87 | $connection = $this->databaseManager->connection($this->option('database')); 88 | if ($connection instanceof CouchbaseConnection) { 89 | $bucket = $connection->openBucket($this->argument('bucket')); 90 | $name = $this->argument('name'); 91 | $bucket->manager()->dropN1qlIndex( 92 | $name, 93 | $this->option('ignore') 94 | ); 95 | $this->info("dropped SECONDARY INDEX [{$name}] for [{$this->argument('bucket')}] bucket."); 96 | } 97 | 98 | return; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Console/PrimaryIndexCreatorCommand.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class PrimaryIndexCreatorCommand extends Command 28 | { 29 | /** @var string */ 30 | protected $name = 'couchbase:create-primary-index'; 31 | 32 | /** @var string */ 33 | protected $description = 'Create a primary N1QL index for the current bucket.'; 34 | 35 | /** @var DatabaseManager */ 36 | protected $databaseManager; 37 | 38 | /** @var string */ 39 | protected $defaultDatabase = 'couchbase'; 40 | 41 | /** 42 | * IndexFinderCommand constructor. 43 | * 44 | * @param DatabaseManager $databaseManager 45 | */ 46 | public function __construct(DatabaseManager $databaseManager) 47 | { 48 | $this->databaseManager = $databaseManager; 49 | parent::__construct(); 50 | } 51 | 52 | /** 53 | * @return string[] 54 | */ 55 | protected function getArguments() 56 | { 57 | return [ 58 | ['bucket', InputArgument::REQUIRED, 'Represents a bucket connection.'], 59 | ]; 60 | } 61 | 62 | /** 63 | * Get the console command options. 64 | * 65 | * @return array 66 | */ 67 | protected function getOptions() 68 | { 69 | return [ 70 | ['database', 'db', InputOption::VALUE_REQUIRED, 'The database connection to use.', $this->defaultDatabase], 71 | ['name', null, InputOption::VALUE_REQUIRED, 'the custom name for the primary index.', '#primary'], 72 | [ 73 | 'ignore', 74 | 'ig', 75 | InputOption::VALUE_NONE, 76 | 'if a primary index already exists, an exception will be thrown unless this is set to true.', 77 | ], 78 | [ 79 | 'defer', 80 | null, 81 | InputOption::VALUE_NONE, 82 | 'true to defer building of the index until buildN1qlDeferredIndexes()}is called (or a direct call to the corresponding query service API)', 83 | ], 84 | ]; 85 | } 86 | 87 | /** 88 | * Execute the console command 89 | */ 90 | public function handle() 91 | { 92 | /** @var \Illuminate\Database\Connection|CouchbaseConnection $connection */ 93 | $connection = $this->databaseManager->connection($this->option('database')); 94 | if ($connection instanceof CouchbaseConnection) { 95 | $bucket = $connection->openBucket($this->argument('bucket')); 96 | $bucket->manager()->createN1qlPrimaryIndex( 97 | $this->option('name'), 98 | $this->option('ignore'), 99 | $this->option('defer') 100 | ); 101 | $this->info("created PRIMARY INDEX [{$this->option('name')}] for [{$this->argument('bucket')}] bucket."); 102 | } 103 | 104 | return; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Console/PrimaryIndexRemoverCommand.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class PrimaryIndexRemoverCommand extends Command 28 | { 29 | /** @var string */ 30 | protected $name = 'couchbase:drop-primary-index'; 31 | 32 | /** @var string */ 33 | protected $description = 'Drop the given primary index associated with the current bucket.'; 34 | 35 | /** @var DatabaseManager */ 36 | protected $databaseManager; 37 | 38 | /** @var string */ 39 | protected $defaultDatabase = 'couchbase'; 40 | 41 | /** 42 | * IndexFinderCommand constructor. 43 | * 44 | * @param DatabaseManager $databaseManager 45 | */ 46 | public function __construct(DatabaseManager $databaseManager) 47 | { 48 | $this->databaseManager = $databaseManager; 49 | parent::__construct(); 50 | } 51 | 52 | /** 53 | * @return string[] 54 | */ 55 | protected function getArguments() 56 | { 57 | return [ 58 | ['bucket', InputArgument::REQUIRED, 'Represents a bucket connection.'], 59 | ]; 60 | } 61 | 62 | /** 63 | * Get the console command options. 64 | * 65 | * @return array 66 | */ 67 | protected function getOptions() 68 | { 69 | return [ 70 | ['database', 'db', InputOption::VALUE_REQUIRED, 'The database connection to use.', $this->defaultDatabase], 71 | ['name', null, InputOption::VALUE_REQUIRED, 'the custom name for the primary index.', '#primary'], 72 | [ 73 | 'ignore', 74 | 'ig', 75 | InputOption::VALUE_NONE, 76 | 'if a primary index already exists, an exception will be thrown unless this is set to true.', 77 | ], 78 | ]; 79 | } 80 | 81 | /** 82 | * Execute the console command 83 | */ 84 | public function handle() 85 | { 86 | /** @var \Illuminate\Database\Connection|CouchbaseConnection $connection */ 87 | $connection = $this->databaseManager->connection($this->option('database')); 88 | if ($connection instanceof CouchbaseConnection) { 89 | $bucket = $connection->openBucket($this->argument('bucket')); 90 | $bucket->manager()->dropN1qlPrimaryIndex( 91 | $this->option('name'), 92 | $this->option('ignore') 93 | ); 94 | $this->info("dropped PRIMARY INDEX [{$this->option('name')}] for [{$this->argument('bucket')}] bucket."); 95 | } 96 | 97 | return; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Console/QueueCreatorCommand.php: -------------------------------------------------------------------------------- 1 | 28 | */ 29 | class QueueCreatorCommand extends Command 30 | { 31 | /** @var string */ 32 | protected $name = 'couchbase:create-queue-index'; 33 | 34 | /** @var string */ 35 | protected $description = 'Create primary index, secondary indexes for the queue jobs couchbase bucket.'; 36 | 37 | /** @var DatabaseManager */ 38 | protected $databaseManager; 39 | 40 | /** @var string */ 41 | protected $defaultDatabase = 'couchbase'; 42 | 43 | const PRIMARY_KEY = '#job_queue_primary'; 44 | 45 | /** @var array */ 46 | protected $secondaryIndexes = [ 47 | 'idx_job_queue' => [ // index name 48 | 'queue', // fields 49 | ], 50 | 'idx_job_identifier' => [ 51 | 'id', 52 | ], 53 | 'idx_job_queue_cover' => [ 54 | 'queue', 55 | 'reserved_at', 56 | 'available_at', 57 | 'id', 58 | ], 59 | ]; 60 | 61 | /** 62 | * IndexFinderCommand constructor. 63 | * 64 | * @param DatabaseManager $databaseManager 65 | */ 66 | public function __construct(DatabaseManager $databaseManager) 67 | { 68 | $this->databaseManager = $databaseManager; 69 | parent::__construct(); 70 | } 71 | 72 | /** 73 | * @return string[] 74 | */ 75 | protected function getArguments() 76 | { 77 | return [ 78 | ['bucket', InputArgument::OPTIONAL, 'Represents a bucket connection.', 'jobs'], 79 | ]; 80 | } 81 | 82 | /** 83 | * Get the console command options. 84 | * 85 | * @return array 86 | */ 87 | protected function getOptions() 88 | { 89 | return [ 90 | ['database', 'db', InputOption::VALUE_REQUIRED, 'The database connection to use.', $this->defaultDatabase], 91 | [ 92 | 'ignore', 93 | 'ig', 94 | InputOption::VALUE_NONE, 95 | 'if a primary index already exists, an exception will be thrown unless this is set to true.', 96 | ], 97 | [ 98 | 'defer', 99 | null, 100 | InputOption::VALUE_NONE, 101 | 'true to defer building of the index until buildN1qlDeferredIndexes()}is called (or a direct call to the corresponding query service API)', 102 | ], 103 | ]; 104 | } 105 | 106 | /** 107 | * Execute the console command 108 | */ 109 | public function handle() 110 | { 111 | /** @var \Illuminate\Database\Connection|CouchbaseConnection $connection */ 112 | $connection = $this->databaseManager->connection($this->option('database')); 113 | if ($connection instanceof CouchbaseConnection) { 114 | $bucket = $connection->openBucket($this->argument('bucket')); 115 | $primary = self::PRIMARY_KEY; 116 | try { 117 | $bucket->manager()->createN1qlPrimaryIndex( 118 | $primary, 119 | $this->option('ignore'), 120 | $this->option('defer') 121 | ); 122 | $this->info("created PRIMARY INDEX [{$primary}] for [{$this->argument('bucket')}] bucket."); 123 | } catch (\Exception $e) { 124 | $this->error($e->getMessage()); 125 | } 126 | foreach ($this->secondaryIndexes as $name => $fields) { 127 | try { 128 | $bucket->manager()->createN1qlIndex( 129 | $name, 130 | $fields, 131 | '', 132 | $this->option('ignore'), 133 | $this->option('defer') 134 | ); 135 | $field = implode(",", $fields); 136 | $this->info("created SECONDARY INDEX [{$name}] fields [{$field}], for [{$this->argument('bucket')}] bucket."); 137 | } catch (\Exception $e) { 138 | $this->error($e->getMessage()); 139 | } 140 | } 141 | } 142 | 143 | return; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/ConsoleServiceProvider.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | class ConsoleServiceProvider extends ServiceProvider 34 | { 35 | /** @var bool */ 36 | protected $defer = true; 37 | 38 | public function boot() 39 | { 40 | $this->registerCommands(); 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function register() 47 | { 48 | $this->app->singleton('migration.repository', function ($app) { 49 | $table = $app['config']['database.migrations']; 50 | 51 | return new CouchbaseMigrationRepository($app['db'], $table); 52 | }); 53 | } 54 | 55 | /** 56 | * register laravel-couchbase commands 57 | */ 58 | protected function registerCommands(): void 59 | { 60 | $this->app->singleton('command.couchbase.indexes', function ($app) { 61 | return new IndexFinderCommand($app['Illuminate\Database\DatabaseManager']); 62 | }); 63 | $this->app->singleton('command.couchbase.primary.index.create', function ($app) { 64 | return new PrimaryIndexCreatorCommand($app['Illuminate\Database\DatabaseManager']); 65 | }); 66 | $this->app->singleton('command.couchbase.primary.index.drop', function ($app) { 67 | return new PrimaryIndexRemoverCommand($app['Illuminate\Database\DatabaseManager']); 68 | }); 69 | $this->app->singleton('command.couchbase.index.create', function ($app) { 70 | return new IndexCreatorCommand($app['Illuminate\Database\DatabaseManager']); 71 | }); 72 | $this->app->singleton('command.couchbase.index.drop', function ($app) { 73 | return new IndexRemoverCommand($app['Illuminate\Database\DatabaseManager']); 74 | }); 75 | $this->app->singleton('command.couchbase.queue.index.create', function ($app) { 76 | return new QueueCreatorCommand($app['Illuminate\Database\DatabaseManager']); 77 | }); 78 | $this->app->singleton('command.couchbase.design.document.create', function ($app) { 79 | return new DesignCreatorCommand( 80 | $app['Illuminate\Database\DatabaseManager'], 81 | $app['config']->get('couchbase.design') 82 | ); 83 | }); 84 | $this->commands([ 85 | 'command.couchbase.indexes', 86 | 'command.couchbase.primary.index.create', 87 | 'command.couchbase.primary.index.drop', 88 | 'command.couchbase.index.create', 89 | 'command.couchbase.index.drop', 90 | 'command.couchbase.queue.index.create', 91 | 'command.couchbase.design.document.create', 92 | ]); 93 | } 94 | 95 | /** 96 | * {@inheritdoc} 97 | */ 98 | public function provides() 99 | { 100 | return [ 101 | 'command.couchbase.indexes', 102 | 'command.couchbase.primary.index.create', 103 | 'command.couchbase.primary.index.drop', 104 | 'command.couchbase.index.create', 105 | 'command.couchbase.index.drop', 106 | 'command.couchbase.queue.index.create', 107 | 'command.couchbase.design.document.create', 108 | 'migration.repository', 109 | ]; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/CouchbaseServiceProvider.php: -------------------------------------------------------------------------------- 1 | 34 | */ 35 | class CouchbaseServiceProvider extends ServiceProvider 36 | { 37 | /** 38 | * Bootstrap application services. 39 | */ 40 | public function boot() 41 | { 42 | $this->registerCouchbaseBucketCacheDriver(); 43 | $this->registerMemcachedBucketCacheDriver(); 44 | $this->registerCouchbaseQueueDriver(); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function register() 51 | { 52 | $configPath = __DIR__ . '/config/couchbase.php'; 53 | $this->mergeConfigFrom($configPath, 'couchbase'); 54 | $this->publishes([$configPath => config_path('couchbase.php')], 'couchbase'); 55 | $this->registerCouchbaseComponent(); 56 | } 57 | 58 | protected function registerCouchbaseComponent() 59 | { 60 | $this->app->singleton(Connectable::class, function () { 61 | return new CouchbaseConnector(); 62 | }); 63 | 64 | $this->app->singleton('couchbase.memcached.connector', function () { 65 | return new MemcachedConnector(); 66 | }); 67 | 68 | // add couchbase session driver 69 | $this->app['session']->extend('couchbase', function ($app) { 70 | $minutes = $app['config']['session.lifetime']; 71 | 72 | return new CacheBasedSessionHandler(clone $this->app['cache']->driver('couchbase'), $minutes); 73 | }); 74 | 75 | // add couchbase session driver 76 | $this->app['session']->extend('couchbase-memcached', function ($app) { 77 | $minutes = $app['config']['session.lifetime']; 78 | 79 | return new CacheBasedSessionHandler(clone $this->app['cache']->driver('couchbase-memcached'), $minutes); 80 | }); 81 | 82 | // add couchbase extension 83 | $this->app['db']->extend('couchbase', function (array $config, $name) { 84 | /* @var \Couchbase\Cluster $cluster */ 85 | return new CouchbaseConnection($config, $name); 86 | }); 87 | } 88 | 89 | /** 90 | * register 'couchbase' cache driver. 91 | * for bucket type couchbase. 92 | */ 93 | protected function registerCouchbaseBucketCacheDriver(): void 94 | { 95 | $this->app['cache']->extend('couchbase', function ($app, $config) { 96 | /** @var \Couchbase\Cluster $cluster */ 97 | $cluster = $app['db']->connection($config['driver'])->getCouchbase(); 98 | $password = (isset($config['bucket_password'])) ? $config['bucket_password'] : ''; 99 | 100 | return new Repository( 101 | new CouchbaseStore( 102 | $cluster, 103 | $config['bucket'], 104 | $password, 105 | $app['config']->get('cache.prefix') 106 | ) 107 | ); 108 | }); 109 | } 110 | 111 | /** 112 | * register 'couchbase' cache driver. 113 | * for bucket type memcached. 114 | */ 115 | protected function registerMemcachedBucketCacheDriver(): void 116 | { 117 | $this->app['cache']->extend('couchbase-memcached', function ($app, $config) { 118 | $prefix = $app['config']['cache.prefix']; 119 | $credential = $config['sasl'] ?? []; 120 | $memcachedBucket = $this->app['couchbase.memcached.connector'] 121 | ->connect($config['servers']); 122 | 123 | return new Repository( 124 | new MemcachedBucketStore( 125 | $memcachedBucket, 126 | strval($prefix), 127 | $config['servers'], 128 | $credential 129 | ) 130 | ); 131 | }); 132 | } 133 | 134 | /** 135 | * register custom queue 'couchbase' driver 136 | */ 137 | protected function registerCouchbaseQueueDriver(): void 138 | { 139 | /** @var QueueManager $queueManager */ 140 | $queueManager = $this->app['queue']; 141 | $queueManager->addConnector('couchbase', function () { 142 | /** @var DatabaseManager $databaseManager */ 143 | $databaseManager = $this->app['db']; 144 | 145 | return new QueueConnector($databaseManager); 146 | }); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/Database/Connectable.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | interface Connectable 24 | { 25 | /** 26 | * @param array $servers 27 | * 28 | * @return Cluster 29 | */ 30 | public function connect(array $servers): Cluster; 31 | } 32 | -------------------------------------------------------------------------------- /src/Database/CouchbaseConnection.php: -------------------------------------------------------------------------------- 1 | 35 | */ 36 | class CouchbaseConnection extends Connection 37 | { 38 | /** @var string */ 39 | protected $bucket; 40 | 41 | /** @var Cluster */ 42 | protected $connection; 43 | 44 | /** @var */ 45 | protected $managerUser; 46 | 47 | /** @var */ 48 | protected $managerPassword; 49 | 50 | /** @var array */ 51 | protected $options = []; 52 | 53 | /** @var int */ 54 | protected $fetchMode = 0; 55 | 56 | /** @var array */ 57 | protected $enableN1qlServers = []; 58 | 59 | /** @var string */ 60 | protected $bucketPassword = ''; 61 | 62 | /** @var string[] */ 63 | protected $metrics; 64 | 65 | /** @var int default consistency */ 66 | protected $consistency = N1qlQuery::NOT_BOUNDED; 67 | 68 | /** @var string[] function to handle the retrieval of various properties. */ 69 | private $properties = [ 70 | 'operationTimeout', 71 | 'viewTimeout', 72 | 'durabilityInterval', 73 | 'durabilityTimeout', 74 | 'httpTimeout', 75 | 'configTimeout', 76 | 'configDelay', 77 | 'configNodeTimeout', 78 | 'htconfigIdleTimeout', 79 | ]; 80 | 81 | /** @var array */ 82 | protected $config = []; 83 | 84 | /** @var string */ 85 | private $name; 86 | 87 | /** @var bool */ 88 | private $crossBucket = true; 89 | 90 | /** 91 | * @param array $config 92 | * @param string $name 93 | */ 94 | public function __construct(array $config, $name) 95 | { 96 | $this->config = $config; 97 | $this->name = $name; 98 | $this->getManagedConfigure($config); 99 | 100 | $this->useDefaultQueryGrammar(); 101 | 102 | $this->useDefaultPostProcessor(); 103 | } 104 | 105 | /** 106 | * @param string $password 107 | * 108 | * @return CouchbaseConnection 109 | */ 110 | public function setBucketPassword(string $password): CouchbaseConnection 111 | { 112 | $this->bucketPassword = $password; 113 | 114 | return $this; 115 | } 116 | 117 | /** 118 | * @param string $name 119 | * 120 | * @return Bucket 121 | */ 122 | public function openBucket(string $name): Bucket 123 | { 124 | $couchbase = $this->getCouchbase(); 125 | if ($this->bucketPassword === '') { 126 | return $couchbase->openBucket($name); 127 | } 128 | 129 | return $couchbase->openBucket($name, $this->bucketPassword); 130 | } 131 | 132 | /** 133 | * @return ClusterManager 134 | */ 135 | public function manager(): ClusterManager 136 | { 137 | return $this->getCouchbase()->manager($this->managerUser, $this->managerPassword); 138 | } 139 | 140 | /** 141 | * @param Bucket $bucket 142 | * 143 | * @return string[] 144 | */ 145 | public function getOptions(Bucket $bucket): array 146 | { 147 | $options = []; 148 | foreach ($this->properties as $property) { 149 | $options[$property] = $bucket->$property; 150 | } 151 | 152 | return $options; 153 | } 154 | 155 | /** 156 | * @param Bucket $bucket 157 | */ 158 | protected function registerOption(Bucket $bucket) 159 | { 160 | if (count($this->options)) { 161 | foreach ($this->options as $option => $value) { 162 | $bucket->$option = $value; 163 | } 164 | } 165 | } 166 | 167 | /** 168 | * @return Processor 169 | */ 170 | protected function getDefaultPostProcessor() 171 | { 172 | return new Processor(); 173 | } 174 | 175 | /** 176 | * @return Grammar 177 | */ 178 | protected function getDefaultQueryGrammar() 179 | { 180 | return new Grammar(); 181 | } 182 | 183 | /** 184 | * @return Builder|\Illuminate\Database\Schema\Builder 185 | */ 186 | public function getSchemaBuilder() 187 | { 188 | return new Builder($this); 189 | } 190 | 191 | /** 192 | * @param array $config enable(array), options(array), administrator(array), bucket_password(string) 193 | */ 194 | protected function getManagedConfigure(array $config) 195 | { 196 | $this->enableN1qlServers = (isset($config['enables'])) ? $config['enables'] : []; 197 | $this->options = (isset($config['options'])) ? $config['options'] : []; 198 | $manager = (isset($config['administrator'])) ? $config['administrator'] : null; 199 | $this->managerUser = ''; 200 | $this->managerPassword = ''; 201 | if (!is_null($manager)) { 202 | $this->managerUser = $config['administrator']['user']; 203 | $this->managerPassword = $config['administrator']['password']; 204 | } 205 | $this->bucketPassword = (isset($config['bucket_password'])) ? $config['bucket_password'] : ''; 206 | } 207 | 208 | /** 209 | * {@inheritdoc} 210 | */ 211 | public function getName() 212 | { 213 | return $this->name; 214 | } 215 | 216 | /** 217 | * @return \Couchbase\Cluster 218 | */ 219 | protected function createConnection(): Cluster 220 | { 221 | $this->setReconnector(function () { 222 | $this->connection = (new CouchbaseConnector)->connect($this->config); 223 | 224 | return $this; 225 | }); 226 | 227 | return (new CouchbaseConnector)->connect($this->config); 228 | } 229 | 230 | /** 231 | * {@inheritdoc} 232 | */ 233 | public function getDriverName() 234 | { 235 | return 'couchbase'; 236 | } 237 | 238 | /** 239 | * @return Cluster 240 | */ 241 | public function getCouchbase(): Cluster 242 | { 243 | if (is_null($this->connection)) { 244 | $this->connection = $this->createConnection(); 245 | } 246 | 247 | return $this->connection; 248 | } 249 | 250 | /** 251 | * @param string $table 252 | * 253 | * @return QueryBuilder 254 | */ 255 | public function table($table) 256 | { 257 | return $this->bucket($table)->query()->from($table); 258 | } 259 | 260 | /** 261 | * @param int $consistency 262 | * @param callable $callback 263 | * 264 | * @return mixed 265 | */ 266 | public function callableConsistency(int $consistency, callable $callback) 267 | { 268 | $clone = clone $this; 269 | $clone->consistency = $consistency; 270 | 271 | return call_user_func_array($callback, [$clone]); 272 | } 273 | 274 | /** 275 | * @param int $consistency 276 | * 277 | * @return CouchbaseConnection 278 | */ 279 | public function consistency(int $consistency): CouchbaseConnection 280 | { 281 | $this->consistency = $consistency; 282 | 283 | return $this; 284 | } 285 | 286 | /** 287 | * @param bool $cross 288 | */ 289 | public function crossBucket(bool $cross): void 290 | { 291 | $this->crossBucket = $cross; 292 | } 293 | 294 | /** 295 | * @param string $bucket 296 | * 297 | * @return $this 298 | */ 299 | public function bucket(string $bucket): CouchbaseConnection 300 | { 301 | $this->bucket = $bucket; 302 | 303 | return $this; 304 | } 305 | 306 | /** 307 | * @param N1qlQuery $query 308 | * 309 | * @return mixed 310 | */ 311 | protected function executeQuery(N1qlQuery $query) 312 | { 313 | $bucket = $this->openBucket($this->bucket); 314 | $this->registerOption($bucket); 315 | $this->firePreparedQuery($query); 316 | $result = $bucket->query($query); 317 | $this->fireReturning($result); 318 | 319 | return $result; 320 | } 321 | 322 | /** 323 | * @param string $query 324 | * @param array $bindings 325 | * 326 | * @return \stdClass 327 | */ 328 | protected function execute(string $query, array $bindings = []) 329 | { 330 | $query = N1qlQuery::fromString($query); 331 | $query->consistency($this->consistency); 332 | $query->crossBucket($this->crossBucket); 333 | $query->positionalParams($bindings); 334 | $result = $this->executeQuery($query); 335 | $this->metrics = $result->metrics ?? []; 336 | 337 | return $result; 338 | } 339 | 340 | /** 341 | * {@inheritdoc} 342 | */ 343 | public function select($query, $bindings = [], $useReadPdo = true) 344 | { 345 | return $this->run($query, $bindings, function ($query, $bindings) { 346 | if ($this->pretending()) { 347 | return []; 348 | } 349 | 350 | $result = $this->execute($query, $bindings); 351 | $returning = []; 352 | if (isset($result->rows)) { 353 | foreach ($result->rows as $row) { 354 | if (!isset($row->{$this->bucket})) { 355 | return [$row]; 356 | } 357 | $returning[] = $row; 358 | } 359 | } 360 | 361 | return $returning; 362 | }); 363 | } 364 | 365 | /** 366 | * {@inheritdoc} 367 | */ 368 | public function cursor($query, $bindings = [], $useReadPdo = true) 369 | { 370 | return $this->run($query, $bindings, function ($query, $bindings) { 371 | if ($this->pretending()) { 372 | return []; 373 | } 374 | 375 | $result = $this->execute($query, $bindings); 376 | if (isset($result->rows)) { 377 | foreach ($result->rows as $row) { 378 | yield $row->{$this->bucket}; 379 | } 380 | } 381 | }); 382 | } 383 | 384 | /** 385 | * @param string $query 386 | * @param array $bindings 387 | * 388 | * @return int|mixed 389 | */ 390 | public function insert($query, $bindings = []) 391 | { 392 | return $this->affectingStatement($query, $bindings); 393 | } 394 | 395 | /** 396 | * {@inheritdoc} 397 | */ 398 | public function affectingStatement($query, $bindings = []) 399 | { 400 | return $this->run($query, $bindings, function ($query, $bindings) { 401 | if ($this->pretending()) { 402 | return 0; 403 | } 404 | $query = N1qlQuery::fromString($query); 405 | $query->consistency($this->consistency); 406 | $query->crossBucket($this->crossBucket); 407 | $query->namedParams(['parameters' => $bindings]); 408 | $result = $this->executeQuery($query); 409 | $this->metrics = $result->metrics ?? []; 410 | if (!count($result->rows)) { 411 | return false; 412 | } 413 | 414 | return $result->rows[0]->{$this->bucket} ?? $result->rows[0]; 415 | }); 416 | } 417 | 418 | /** 419 | * @param string $query 420 | * @param array $bindings 421 | * 422 | * @return mixed 423 | */ 424 | public function positionalStatement(string $query, array $bindings = []) 425 | { 426 | return $this->run($query, $bindings, function ($query, $bindings) { 427 | if ($this->pretending()) { 428 | return 0; 429 | } 430 | $query = N1qlQuery::fromString($query); 431 | $query->consistency($this->consistency); 432 | $query->crossBucket($this->crossBucket); 433 | $query->positionalParams($bindings); 434 | $result = $this->executeQuery($query); 435 | $this->metrics = $result->metrics ?? []; 436 | if (!count($result->rows)) { 437 | return false; 438 | } 439 | 440 | return $result->rows[0]->{$this->bucket} ?? $result->rows[0]; 441 | }); 442 | } 443 | 444 | /** 445 | * {@inheritdoc} 446 | */ 447 | public function transaction(Closure $callback, $attempts = 1) 448 | { 449 | throw new NotSupportedException(__METHOD__); 450 | } 451 | 452 | /** 453 | * {@inheritdoc} 454 | */ 455 | public function beginTransaction() 456 | { 457 | throw new NotSupportedException(__METHOD__); 458 | } 459 | 460 | /** 461 | * {@inheritdoc} 462 | */ 463 | public function commit() 464 | { 465 | throw new NotSupportedException(__METHOD__); 466 | } 467 | 468 | /** 469 | * {@inheritdoc} 470 | */ 471 | public function rollBack($toLevel = null) 472 | { 473 | throw new NotSupportedException(__METHOD__); 474 | } 475 | 476 | /** 477 | * {@inheritdoc} 478 | */ 479 | protected function reconnectIfMissingConnection() 480 | { 481 | if (is_null($this->connection)) { 482 | $this->reconnect(); 483 | } 484 | } 485 | 486 | /** 487 | * {@inheritdoc} 488 | */ 489 | public function disconnect() 490 | { 491 | $this->connection = null; 492 | } 493 | 494 | /** 495 | * N1QL upsert query. 496 | * 497 | * @param string $query 498 | * @param array $bindings 499 | * 500 | * @return int 501 | */ 502 | public function upsert(string $query, array $bindings = []) 503 | { 504 | return $this->affectingStatement($query, $bindings); 505 | } 506 | 507 | /** 508 | * Get a new query builder instance. 509 | * 510 | * @return QueryBuilder 511 | */ 512 | public function query() 513 | { 514 | return new QueryBuilder( 515 | $this, $this->getQueryGrammar(), $this->getPostProcessor() 516 | ); 517 | } 518 | 519 | /** 520 | * @param string|null $bucket 521 | * 522 | * @return View 523 | */ 524 | public function view(string $bucket = null): View 525 | { 526 | $bucket = is_null($bucket) ? $this->bucket : $bucket; 527 | 528 | return new View($this->openBucket($bucket), $this->events); 529 | } 530 | 531 | /** 532 | * Run an update statement against the database. 533 | * 534 | * @param string $query 535 | * @param array $bindings 536 | * 537 | * @return int|\stdClass 538 | */ 539 | public function update($query, $bindings = []) 540 | { 541 | return $this->positionalStatement($query, $bindings); 542 | } 543 | 544 | /** 545 | * Run a delete statement against the database. 546 | * 547 | * @param string $query 548 | * @param array $bindings 549 | * 550 | * @return int|\stdClass 551 | */ 552 | public function delete($query, $bindings = []) 553 | { 554 | return $this->positionalStatement($query, $bindings); 555 | } 556 | 557 | /** 558 | * @return \string[] 559 | */ 560 | public function metrics(): array 561 | { 562 | return $this->metrics; 563 | } 564 | 565 | /** 566 | * @param N1qlQuery $queryObject 567 | */ 568 | protected function firePreparedQuery(N1qlQuery $queryObject) 569 | { 570 | if (isset($this->events)) { 571 | $this->events->dispatch(new QueryPrepared($queryObject)); 572 | } 573 | } 574 | 575 | /** 576 | * @param mixed $returning 577 | */ 578 | protected function fireReturning($returning) 579 | { 580 | if (isset($this->events)) { 581 | $this->events->dispatch(new ResultReturning($returning)); 582 | } 583 | } 584 | 585 | /** 586 | * @param null|\PDO $pdo 587 | * 588 | * @return $this 589 | */ 590 | public function setPdo($pdo) 591 | { 592 | $this->connection = $this->createConnection(); 593 | $this->getManagedConfigure($this->config); 594 | $this->useDefaultQueryGrammar(); 595 | $this->useDefaultPostProcessor(); 596 | 597 | return $this; 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /src/Database/CouchbaseConnector.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class CouchbaseConnector implements Connectable 23 | { 24 | /** @var string[] */ 25 | protected $configure = [ 26 | 'host' => 'couchbase://127.0.0.1', 27 | 'user' => '', 28 | 'password' => '', 29 | ]; 30 | 31 | /** 32 | * @param array $servers 33 | * 34 | * @return Cluster 35 | */ 36 | public function connect(array $servers): Cluster 37 | { 38 | $configure = array_merge($this->configure, $servers); 39 | $cluster = new Cluster($configure['host']); 40 | if (!empty($configure['user']) && !empty($configure['password'])) { 41 | $cluster->authenticateAs(strval($configure['user']), strval($configure['password'])); 42 | } 43 | 44 | return $cluster; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Design/AbstractDocument.php: -------------------------------------------------------------------------------- 1 | name = $name; 22 | } 23 | 24 | /** 25 | * @return string 26 | */ 27 | abstract protected function document(): string; 28 | 29 | /** 30 | * @return string 31 | */ 32 | public function __toString(): string 33 | { 34 | return json_encode([ 35 | $this->name => [ 36 | 'map' => $this->document(), 37 | ], 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Events/QueryPrepared.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | final class QueryPrepared 24 | { 25 | /** @var N1qlQuery */ 26 | private $object; 27 | 28 | /** 29 | * QueryPrepared constructor. 30 | * 31 | * @param mixed $queryObject 32 | */ 33 | public function __construct($queryObject) 34 | { 35 | if ($this->isN1ql($queryObject)) { 36 | $this->object = $queryObject; 37 | } 38 | } 39 | 40 | /** 41 | * @param mixed $queryObject 42 | * 43 | * @return bool 44 | */ 45 | private function isN1ql($queryObject): bool 46 | { 47 | if ($queryObject instanceof N1qlQuery) { 48 | return true; 49 | } 50 | 51 | return false; 52 | } 53 | 54 | /** 55 | * @return N1qlQuery|null 56 | */ 57 | public function getQuery(): ?N1qlQuery 58 | { 59 | return $this->object; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Events/ResultReturning.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | final class ResultReturning 22 | { 23 | /** @var mixed */ 24 | private $returning; 25 | 26 | /** 27 | * ResultReturning constructor. 28 | * 29 | * @param mixed $returning 30 | */ 31 | public function __construct($returning) 32 | { 33 | $this->returning = $returning; 34 | } 35 | 36 | /** 37 | * @return mixed 38 | */ 39 | public function returning() 40 | { 41 | return $this->returning; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Events/ViewQuerying.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | final class ViewQuerying 24 | { 25 | /** @var ViewQuery */ 26 | private $viewQuery; 27 | 28 | /** 29 | * ViewQuerying constructor. 30 | * 31 | * @param ViewQuery $viewQuery 32 | */ 33 | public function __construct(ViewQuery $viewQuery) 34 | { 35 | $this->viewQuery = $viewQuery; 36 | } 37 | 38 | /** 39 | * @return ViewQuery 40 | */ 41 | public function viewQuery(): ViewQuery 42 | { 43 | return $this->viewQuery; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Exceptions/FlushException.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | final class FlushException extends \Exception 22 | { 23 | /** 24 | * FlushException constructor. 25 | * 26 | * @param array $message 27 | * @param int $code 28 | * @param \Throwable|null $previous 29 | */ 30 | public function __construct(array $message, $code = 0, \Throwable $previous = null) 31 | { 32 | parent::__construct($message['_'], $code, $previous); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Exceptions/NotSupportedException.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | final class NotSupportedException extends \Exception 22 | { 23 | /** 24 | * @param string $message 25 | * @param int $code 26 | * @param \Throwable|null $previous 27 | */ 28 | public function __construct($message, $code = 0, \Throwable $previous = null) 29 | { 30 | parent::__construct("$message method is not supported", $code, $previous); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/MemcachedConnector.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class MemcachedConnector extends \Illuminate\Cache\MemcachedConnector 25 | { 26 | /** 27 | * Create a new Memcached connection. 28 | * 29 | * @param array $servers 30 | * @param string|null $connectionId 31 | * @param array $options 32 | * @param array $credentials 33 | * 34 | * @return \Memcached 35 | * 36 | * @throws \RuntimeException 37 | */ 38 | public function connect( 39 | array $servers, 40 | $connectionId = null, 41 | array $options = [], 42 | array $credentials = [] 43 | ) { 44 | $memcached = $this->getMemcached($connectionId, $credentials, []); 45 | 46 | foreach ($servers as $server) { 47 | $memcached->addServer( 48 | $server['host'], intval($server['port']), $server['weight'] 49 | ); 50 | } 51 | 52 | return $memcached; 53 | } 54 | 55 | /** 56 | * {@inheritdoc} 57 | */ 58 | protected function getMemcached($connectionId, array $credentials, array $options) 59 | { 60 | return $this->createMemcachedInstance($connectionId); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Migrations/CouchbaseMigrationRepository.php: -------------------------------------------------------------------------------- 1 | 27 | */ 28 | class CouchbaseMigrationRepository extends DatabaseMigrationRepository 29 | { 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function log($file, $batch) 34 | { 35 | $record = ['migration' => $file, 'batch' => $batch]; 36 | 37 | $builder = $this->table(); 38 | if ($builder instanceof QueryBuilder) { 39 | /** @var Builder */ 40 | $builder->key("{$file}:{$batch}")->insert($record); 41 | 42 | return; 43 | } 44 | $builder->insert($record); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function createRepository() 51 | { 52 | $schema = $this->getConnection()->getSchemaBuilder(); 53 | 54 | if ($schema instanceof Builder) { 55 | $schema->create($this->table, function (CouchbaseBlueprint $table) { 56 | $table->primaryIndex(); 57 | $table->index(['migration', 'batch'], 'migration_secondary_index'); 58 | }); 59 | 60 | return; 61 | } 62 | $schema->create($this->table, function (Blueprint $table) { 63 | $table->string('migration'); 64 | $table->integer('batch'); 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Query/Builder.php: -------------------------------------------------------------------------------- 1 | key = $key; 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * @param array $column 55 | * 56 | * @return Builder 57 | */ 58 | public function returning(array $column = ['*']): Builder 59 | { 60 | $this->returning = $column; 61 | 62 | return $this; 63 | } 64 | 65 | /** 66 | * Insert a new record into the database. 67 | * 68 | * @param array $values 69 | * 70 | * @return bool 71 | */ 72 | public function insert(array $values) 73 | { 74 | if (empty($values)) { 75 | return true; 76 | } 77 | $values = $this->detectValues($values); 78 | $bindings = []; 79 | foreach ($values as $record) { 80 | foreach ($record as $key => $value) { 81 | $bindings[$key] = $value; 82 | } 83 | } 84 | 85 | $sql = $this->grammar->compileInsert($this, $values); 86 | 87 | return $this->connection->insert($sql, $bindings); 88 | } 89 | 90 | /** 91 | * supported N1QL upsert query. 92 | * 93 | * @param array $values 94 | * 95 | * @return bool|mixed 96 | */ 97 | public function upsert(array $values) 98 | { 99 | if (empty($values)) { 100 | return true; 101 | } 102 | $values = $this->detectValues($values); 103 | $bindings = []; 104 | foreach ($values as $record) { 105 | foreach ($record as $key => $value) { 106 | $bindings[$key] = $value; 107 | } 108 | } 109 | 110 | $sql = $this->grammar->compileUpsert($this, $values); 111 | 112 | return $this->connection->upsert($sql, $bindings); 113 | } 114 | 115 | /** 116 | * @param string|int|array $values 117 | * 118 | * @return array 119 | */ 120 | protected function detectValues($values): array 121 | { 122 | if (!is_array(reset($values))) { 123 | $values = [$values]; 124 | } else { 125 | foreach ($values as $key => $value) { 126 | ksort($value); 127 | $values[$key] = $value; 128 | } 129 | } 130 | 131 | return $values; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Query/Grammar.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | class Grammar extends IlluminateGrammar 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function wrapValue($value) 31 | { 32 | if ($value === '*') { 33 | return $value; 34 | } 35 | 36 | return $value; 37 | } 38 | 39 | /** 40 | * @param mixed $value 41 | * 42 | * @return string 43 | */ 44 | protected function wrapKey($value) 45 | { 46 | if (is_null($value)) { 47 | return; 48 | } 49 | 50 | return '"' . str_replace('"', '""', $value) . '"'; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | * 56 | * notice: supported set query only 57 | */ 58 | public function compileUpdate(Builder $query, $values) 59 | { 60 | // keyspace-ref: 61 | $table = $this->wrapTable($query->from); 62 | // use-keys-clause: 63 | $keyClause = $this->wrapKey($query->key); 64 | // returning-clause 65 | $returning = implode(', ', $query->returning); 66 | 67 | $columns = []; 68 | 69 | foreach ($values as $key => $value) { 70 | $columns[] = $this->wrap($key) . ' = ' . $this->parameter($value); 71 | } 72 | 73 | $columns = implode(', ', $columns); 74 | 75 | $joins = ''; 76 | if (isset($query->joins)) { 77 | $joins = ' ' . $this->compileJoins($query, $query->joins); 78 | } 79 | $where = $this->compileWheres($query); 80 | 81 | return trim("update {$table} USE KEYS {$keyClause} {$joins} set $columns $where RETURNING {$returning}"); 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function compileInsert(Builder $query, array $values) 88 | { 89 | // keyspace-ref: 90 | $table = $this->wrapTable($query->from); 91 | // use-keys-clause: 92 | $keyClause = $this->wrapKey($query->key); 93 | // returning-clause 94 | $returning = implode(', ', $query->returning); 95 | 96 | if (!is_array(reset($values))) { 97 | $values = [$values]; 98 | } 99 | $parameters = []; 100 | 101 | foreach ($values as $record) { 102 | $parameters[] = '(' . $this->parameterize($record) . ')'; 103 | } 104 | $parameters = (!$keyClause) ? implode(', ', $parameters) : "({$keyClause}, \$parameters)"; 105 | $keyValue = (!$keyClause) ? null : '(KEY, VALUE)'; 106 | 107 | return "insert into {$table} {$keyValue} values $parameters RETURNING {$returning}"; 108 | } 109 | 110 | /** 111 | * {@inheritdoc} 112 | * 113 | * @see http://developer.couchbase.com/documentation/server/4.1/n1ql/n1ql-language-reference/delete.html 114 | */ 115 | public function compileDelete(Builder $query) 116 | { 117 | // keyspace-ref: 118 | $table = $this->wrapTable($query->from); 119 | // use-keys-clause: 120 | $keyClause = null; 121 | if ($query->key) { 122 | $key = $this->wrapKey($query->key); 123 | $keyClause = "USE KEYS {$key}"; 124 | } 125 | // returning-clause 126 | $returning = implode(', ', $query->returning); 127 | $where = is_array($query->wheres) ? $this->compileWheres($query) : ''; 128 | 129 | return trim("delete from {$table} {$keyClause} {$where} RETURNING {$returning}"); 130 | } 131 | 132 | /** 133 | * @param QueryBuilder $query 134 | * @param array $values 135 | * 136 | * @return string 137 | */ 138 | public function compileUpsert(QueryBuilder $query, array $values): string 139 | { 140 | // keyspace-ref: 141 | $table = $this->wrapTable($query->from); 142 | // use-keys-clause: 143 | $keyClause = $this->wrapKey($query->key); 144 | // returning-clause 145 | $returning = implode(', ', $query->returning); 146 | 147 | if (!is_array(reset($values))) { 148 | $values = [$values]; 149 | } 150 | $parameters = []; 151 | 152 | foreach ($values as $record) { 153 | $parameters[] = '(' . $this->parameterize($record) . ')'; 154 | } 155 | $parameters = (!$keyClause) ? implode(', ', $parameters) : "({$keyClause}, \$parameters)"; 156 | $keyValue = (!$keyClause) ? null : '(KEY, VALUE)'; 157 | 158 | return "UPSERT INTO {$table} {$keyValue} VALUES $parameters RETURNING {$returning}"; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Query/Processor.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | class Processor extends IlluminateProcessor 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /src/Query/View.php: -------------------------------------------------------------------------------- 1 | 28 | */ 29 | class View 30 | { 31 | /** @var Bucket */ 32 | protected $bucket; 33 | 34 | /** @var Dispatcher */ 35 | protected $dispatcher; 36 | 37 | /** 38 | * Specifies the mode of updating to perorm before and after executing the query 39 | * 40 | * @see \Couchbase\ViewQuery::UPDATE_BEFORE 41 | * @see \Couchbase\ViewQuery::UPDATE_NONE 42 | * @see \Couchbase\ViewQuery::UPDATE_AFTER 43 | */ 44 | private $consistency = null; 45 | 46 | /** 47 | * View constructor. 48 | * 49 | * @param Bucket $bucket 50 | * @param Dispatcher|null $dispatcher 51 | */ 52 | public function __construct(Bucket $bucket, Dispatcher $dispatcher = null) 53 | { 54 | $this->bucket = $bucket; 55 | $this->dispatcher = $dispatcher; 56 | } 57 | 58 | /** 59 | * @param string $designDoc 60 | * @param string $name 61 | * 62 | * @return ViewQuery 63 | */ 64 | public function from(string $designDoc, string $name): ViewQuery 65 | { 66 | return ViewQuery::from($designDoc, $name); 67 | } 68 | 69 | /** 70 | * @param string $designDoc 71 | * @param string $name 72 | * 73 | * @return SpatialViewQuery 74 | */ 75 | public function fromSpatial(string $designDoc, string $name): SpatialViewQuery 76 | { 77 | return ViewQuery::fromSpatial($designDoc, $name); 78 | } 79 | 80 | /** 81 | * @param ViewQuery $viewQuery 82 | * @param bool $jsonAsArray 83 | * 84 | * @return mixed 85 | */ 86 | public function execute(ViewQuery $viewQuery, bool $jsonAsArray = false) 87 | { 88 | if (isset($this->dispatcher)) { 89 | $this->dispatcher->dispatch(new ViewQuerying($viewQuery)); 90 | } 91 | if (!is_null($this->consistency)) { 92 | $viewQuery = $viewQuery->consistency($this->consistency); 93 | } 94 | 95 | return $this->bucket->query($viewQuery, $jsonAsArray); 96 | } 97 | 98 | /** 99 | * @param int $consistency 100 | */ 101 | public function consistency(int $consistency): void 102 | { 103 | $this->consistency = $consistency; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Queue/CouchbaseConnector.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | class CouchbaseConnector implements ConnectorInterface 27 | { 28 | /** @var ConnectionResolverInterface */ 29 | protected $connectionResolver; 30 | 31 | /** 32 | * CouchbaseConnector constructor. 33 | * 34 | * @param ConnectionResolverInterface $connectionResolver 35 | */ 36 | public function __construct(ConnectionResolverInterface $connectionResolver) 37 | { 38 | $this->connectionResolver = $connectionResolver; 39 | } 40 | 41 | /** 42 | * @param array $config 43 | * 44 | * @return CouchbaseQueue|\Illuminate\Contracts\Queue\Queue 45 | */ 46 | public function connect(array $config) 47 | { 48 | /** @var CouchbaseConnection $connection */ 49 | $connection = $this->connectionResolver->connection($config['driver']); 50 | 51 | return new CouchbaseQueue( 52 | $connection, 53 | $config['bucket'], 54 | $config['queue'], 55 | Arr::get($config, 'retry_after', 60) 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Queue/CouchbaseQueue.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | class CouchbaseQueue extends DatabaseQueue 28 | { 29 | /** 30 | * The couchbase bucket that holds the jobs. 31 | * 32 | * @var string 33 | */ 34 | protected $table; 35 | 36 | /** @var CouchbaseConnection */ 37 | protected $database; 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function pop($queue = null) 43 | { 44 | $queue = $this->getQueue($queue); 45 | if ($job = $this->getNextAvailableJob($queue)) { 46 | return $this->marshalJob($queue, $job); 47 | } 48 | 49 | return null; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | protected function marshalJob($queue, $job) 56 | { 57 | $job = $this->markJobAsReserved($job); 58 | 59 | return new DatabaseJob( 60 | $this->container, $this, $job, $this->connectionName, $queue 61 | ); 62 | } 63 | 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | protected function getNextAvailableJob($queue) 68 | { 69 | $job = $this->database->table($this->table) 70 | ->where('queue', $this->getQueue($queue)) 71 | ->where(function (Builder $query) { 72 | $this->isAvailable($query); 73 | $this->isReservedButExpired($query); 74 | }) 75 | ->orderBy('id', 'asc') 76 | ->first(['*', 'meta().id']); 77 | 78 | return $job ? new DatabaseJobRecord((object)$job) : null; 79 | } 80 | 81 | /** 82 | * {@inheritdoc} 83 | */ 84 | protected function markJobAsReserved($job) 85 | { 86 | $bucket = $this->table; 87 | /** @var \Couchbase\Bucket $openBucket */ 88 | $openBucket = $this->database->openBucket($bucket); 89 | // lock bucket 90 | $meta = $openBucket->getAndLock($job->id, 10); 91 | $meta->value->attempts = $job->$bucket->attempts + 1; 92 | $meta->value->reserved_at = $job->touch(); 93 | $openBucket->replace($job->id, $meta->value, ['cas' => $meta->cas]); 94 | 95 | return $meta->value; 96 | } 97 | 98 | /** 99 | * {@inheritdoc} 100 | */ 101 | public function bulk($jobs, $data = '', $queue = null) 102 | { 103 | foreach ((array)$jobs as $job) { 104 | $this->push($job, $data, $queue); 105 | } 106 | } 107 | 108 | /** 109 | * {@inheritdoc} 110 | */ 111 | public function deleteReserved($queue, $id) 112 | { 113 | $this->database->table($this->table)->where('id', $id)->delete(); 114 | } 115 | 116 | /** 117 | * {@inheritdoc} 118 | */ 119 | protected function pushToDatabase($queue, $payload, $delay = 0, $attempts = 0) 120 | { 121 | $attributes = $this->buildDatabaseRecord( 122 | $this->getQueue($queue), $payload, $this->availableAt($delay), $attempts 123 | ); 124 | $increment = $this->incrementKey(); 125 | $attributes['id'] = $increment; 126 | $result = $this->database->table($this->table) 127 | ->key($this->uniqueKey($attributes))->insert($attributes); 128 | if ($result) { 129 | return $increment; 130 | } 131 | 132 | return false; 133 | } 134 | 135 | /** 136 | * generate increment key 137 | * 138 | * @param int $initial 139 | * 140 | * @return int 141 | */ 142 | protected function incrementKey($initial = 1) 143 | { 144 | $result = $this->database->openBucket($this->table) 145 | ->counter($this->identifier(), $initial, ['initial' => abs($initial)]); 146 | 147 | return $result->value; 148 | } 149 | 150 | /** 151 | * @param array $attributes 152 | * 153 | * @return string 154 | */ 155 | protected function uniqueKey(array $attributes): string 156 | { 157 | $array = array_only($attributes, ['queue', 'attempts', 'id']); 158 | 159 | return implode(':', $array); 160 | } 161 | 162 | /** 163 | * @return string 164 | */ 165 | protected function identifier(): string 166 | { 167 | return __CLASS__ . ':sequence'; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/Schema/Blueprint.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | class Blueprint extends \Illuminate\Database\Schema\Blueprint 23 | { 24 | use NotSupportedTrait; 25 | 26 | /** @var CouchbaseConnection */ 27 | protected $connection; 28 | 29 | /** @var string[] */ 30 | protected $options = [ 31 | 'bucketType' => 'couchbase', 32 | 'saslPassword' => '', 33 | 'flushEnabled' => true, 34 | ]; 35 | 36 | /** 37 | * @param CouchbaseConnection $connection 38 | */ 39 | public function connector(CouchbaseConnection $connection) 40 | { 41 | $this->connection = $connection; 42 | } 43 | 44 | /** 45 | * @param array $options 46 | */ 47 | public function setOptions(array $options) 48 | { 49 | $this->options = array_merge($this->options, $options); 50 | } 51 | 52 | /** 53 | * @return bool 54 | */ 55 | public function create() 56 | { 57 | $this->connection->manager()->createBucket($this->table, $this->options); 58 | } 59 | 60 | /** 61 | * {@inheritdoc} 62 | */ 63 | public function drop() 64 | { 65 | $this->connection->manager()->removeBucket($this->table); 66 | } 67 | 68 | /** 69 | * drop for N1QL primary index 70 | * 71 | * @param string $index 72 | * @param bool $ignoreIfNotExist 73 | * 74 | * @return mixed 75 | */ 76 | public function dropPrimary($index = null, $ignoreIfNotExist = false) 77 | { 78 | $this->connection->openBucket($this->getTable()) 79 | ->manager()->dropN1qlPrimaryIndex($this->detectIndexName($index), $ignoreIfNotExist); 80 | } 81 | 82 | /** 83 | * drop for N1QL secondary index 84 | * 85 | * @param string $index 86 | * @param bool $ignoreIfNotExist 87 | * 88 | * @return mixed 89 | */ 90 | public function dropIndex($index, $ignoreIfNotExist = false) 91 | { 92 | $this->connection->openBucket($this->getTable()) 93 | ->manager()->dropN1qlIndex($index, $ignoreIfNotExist); 94 | } 95 | 96 | /** 97 | * Specify the primary index for the current bucket. 98 | * 99 | * @param string|null $name 100 | * @param boolean $ignoreIfExist if a primary index already exists, an exception will be thrown unless this is 101 | * set to true. 102 | * @param boolean $defer true to defer building of the index until buildN1qlDeferredIndexes()}is 103 | * called (or a direct call to the corresponding query service API). 104 | */ 105 | public function primaryIndex($name = null, $ignoreIfExist = false, $defer = false) 106 | { 107 | $this->connection->openBucket($this->getTable()) 108 | ->manager()->createN1qlPrimaryIndex( 109 | $this->detectIndexName($name), 110 | $ignoreIfExist, 111 | $defer 112 | ); 113 | } 114 | 115 | /** 116 | * Specify a secondary index for the current bucket. 117 | * 118 | * @param array $columns the JSON fields to index. 119 | * @param string $name the name of the index. 120 | * @param string $whereClause the WHERE clause of the index. 121 | * @param boolean $ignoreIfExist if a secondary index already exists with that name, an exception will be 122 | * thrown unless this is set to true. 123 | * @param boolean $defer true to defer building of the index until buildN1qlDeferredIndexes() is 124 | * called (or a direct call to the corresponding query service API). 125 | * 126 | * @return mixed 127 | */ 128 | public function index($columns, $name = null, $whereClause = '', $ignoreIfExist = false, $defer = false) 129 | { 130 | $name = (is_null($name)) ? $this->getTable() . "_secondary_index" : $name; 131 | 132 | return $this->connection->openBucket($this->getTable()) 133 | ->manager()->createN1qlIndex( 134 | $name, 135 | $columns, 136 | $whereClause, 137 | $ignoreIfExist, 138 | $defer 139 | ); 140 | } 141 | 142 | /** 143 | * Get the table the blueprint describes. 144 | * 145 | * @return string 146 | */ 147 | public function getTable() 148 | { 149 | return $this->table; 150 | } 151 | 152 | /** 153 | * @param $index 154 | * 155 | * @return string 156 | */ 157 | protected function detectIndexName($index) 158 | { 159 | $index = (is_null($index)) ? "" : $index; 160 | 161 | return $index; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/Schema/Builder.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | class Builder extends \Illuminate\Database\Schema\Builder 25 | { 26 | /** 27 | * The database connection instance. 28 | * 29 | * @var \Illuminate\Database\Connection|CouchbaseConnection 30 | */ 31 | protected $connection; 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function hasTable($table) 37 | { 38 | try { 39 | $bucketInfo = $this->connection->openBucket($table)->manager()->info(); 40 | if (!is_null($bucketInfo['name'])) { 41 | return true; 42 | } 43 | 44 | return true; 45 | } catch (Exception $e) { 46 | return false; 47 | } 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function hasColumn($table, $column) 54 | { 55 | return true; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function hasColumns($table, array $columns) 62 | { 63 | return true; 64 | } 65 | 66 | /** 67 | * needs administrator password, user 68 | * 69 | * @param string $collection 70 | * @param Closure|null $callback 71 | * 72 | * @return void 73 | */ 74 | public function create($collection, Closure $callback = null) 75 | { 76 | $blueprint = $this->createBlueprint($collection); 77 | $blueprint->create(); 78 | sleep(10); 79 | if ($callback) { 80 | $callback($blueprint); 81 | } 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | public function drop($collection) 88 | { 89 | $blueprint = $this->createBlueprint($collection); 90 | $blueprint->drop(); 91 | sleep(10); 92 | 93 | return true; 94 | } 95 | 96 | /** 97 | * {@inheritdoc} 98 | */ 99 | protected function createBlueprint($table, Closure $callback = null): Blueprint 100 | { 101 | $blueprint = new Blueprint($table, $callback); 102 | $blueprint->connector($this->connection); 103 | 104 | return $blueprint; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Schema/NotSupportedTrait.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | trait NotSupportedTrait 28 | { 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function toSql(Connection $connection, Grammar $grammar) 33 | { 34 | throw new NotSupportedException(__METHOD__); 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | protected function createIndexName($type, array $columns) 41 | { 42 | throw new NotSupportedException(__METHOD__); 43 | } 44 | 45 | /** 46 | * {@inheritdoc} 47 | */ 48 | public function addColumn($type, $name, array $parameters = []) 49 | { 50 | throw new NotSupportedException(__METHOD__); 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | public function removeColumn($name) 57 | { 58 | throw new NotSupportedException(__METHOD__); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | protected function addCommand($name, array $parameters = []) 65 | { 66 | throw new NotSupportedException(__METHOD__); 67 | } 68 | 69 | /** 70 | * {@inheritdoc} 71 | */ 72 | protected function createCommand($name, array $parameters = []) 73 | { 74 | throw new NotSupportedException(__METHOD__); 75 | } 76 | 77 | /** 78 | * {@inheritdoc} 79 | */ 80 | public function getChangedColumns() 81 | { 82 | throw new NotSupportedException(__METHOD__); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/config/couchbase.php: -------------------------------------------------------------------------------- 1 | [ 20 | /* 21 | 'Your Design Document Name' => [ 22 | 'views' => [ 23 | 'Your View Name' => [ 24 | 'map' => file_get_contents(__DIR__ . '/../resources/sample.ddoc'), 25 | ], 26 | ], 27 | ], 28 | */ 29 | ] 30 | ]; 31 | -------------------------------------------------------------------------------- /src/resources/sample.ddoc: -------------------------------------------------------------------------------- 1 | function (doc, meta) { 2 | emit(meta.id, null); 3 | } 4 | -------------------------------------------------------------------------------- /src/transfer.php: -------------------------------------------------------------------------------- 1 | COUCHBASE_SERTYPE_PHP, 23 | 'cmprtype' => COUCHBASE_CMPRTYPE_NONE, 24 | 'cmprthresh' => 0, 25 | 'cmprfactor' => 0, 26 | ]); 27 | } 28 | -------------------------------------------------------------------------------- /tests/Console/DesignCreatorCommandTest.php: -------------------------------------------------------------------------------- 1 | databaseManager = $this->app['db']; 29 | $this->config = $this->app['config']; 30 | $this->command = new DesignCreatorCommand( 31 | $this->databaseManager, 32 | $this->config->get('couchbase.design') 33 | ); 34 | $this->command->setLaravel(new MockApplication); 35 | } 36 | 37 | public function testCreateSecondaryIndex() 38 | { 39 | /** @var \Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 40 | $connection = $this->databaseManager->connection('couchbase'); 41 | $bucket = $connection->openBucket($this->bucket); 42 | $bucket->manager()->removeDesignDocument('dev_testing'); 43 | $bucket->manager()->removeDesignDocument('dev_testing_name'); 44 | sleep(4); 45 | $output = new BufferedOutput(); 46 | $this->command->run( 47 | new ArrayInput([ 48 | 'bucket' => $this->bucket, 49 | ]), 50 | $output 51 | ); 52 | $output->fetch(); 53 | $lists = $bucket->manager()->listDesignDocuments(); 54 | $documents = []; 55 | foreach ($lists['rows'] as $row) { 56 | $documents[] = $row['doc']['meta']['id']; 57 | } 58 | $this->assertContains('_design/dev_testing_name', $documents); 59 | $this->assertContains('_design/dev_testing', $documents); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Console/IndexCreatorCommandTest.php: -------------------------------------------------------------------------------- 1 | databaseManager = $this->app['db']; 19 | $this->command = new \Ytake\LaravelCouchbase\Console\IndexCreatorCommand($this->databaseManager); 20 | $this->command->setLaravel(new MockApplication); 21 | } 22 | 23 | public function testCreateSecondaryIndex() 24 | { 25 | /** @var \Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 26 | $connection = $this->databaseManager->connection('couchbase'); 27 | $connection->manager(); 28 | $bucket = $connection->openBucket($this->bucket); 29 | try { 30 | $bucket->manager()->dropN1qlPrimaryIndex('#primary'); 31 | } catch (\Couchbase\Exception $e) { 32 | // none 33 | } 34 | $bucket->manager()->createN1qlPrimaryIndex(); 35 | sleep(4); 36 | $output = new \Symfony\Component\Console\Output\BufferedOutput(); 37 | $this->command->run( 38 | new \Symfony\Component\Console\Input\ArrayInput([ 39 | 'bucket' => $this->bucket, 40 | 'name' => 'testing_gsi', 41 | 'fields' => ['params1', 'params2'], 42 | ]), 43 | $output 44 | ); 45 | $fetch = $output->fetch(); 46 | $this->assertSame("created SECONDARY INDEX [testing_gsi] fields [params1,params2], for [index_testing] bucket.", trim($fetch)); 47 | /** @var \Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 48 | $connection = $this->databaseManager->connection('couchbase'); 49 | $bucket = $connection->openBucket($this->bucket); 50 | $indexes = $bucket->manager()->listN1qlIndexes(); 51 | foreach ($indexes as $index) { 52 | if (!$index->isPrimary && $index->keyspace === 'keyspace') { 53 | $this->assertSame("testing_gsi", $index->name); 54 | $this->assertInstanceOf('CouchbaseN1qlIndex', $index); 55 | } 56 | } 57 | $bucket->manager()->dropN1qlPrimaryIndex(); 58 | $bucket->manager()->dropN1qlIndex('testing_gsi'); 59 | sleep(5); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Console/IndexFinderCommandTest.php: -------------------------------------------------------------------------------- 1 | app['db']; 16 | $this->command = new \Ytake\LaravelCouchbase\Console\IndexFinderCommand($cluster); 17 | $this->command->setLaravel(new MockApplication()); 18 | } 19 | 20 | /** 21 | * 22 | */ 23 | public function testShouldReturnDatabaseInformation() 24 | { 25 | $output = new \Symfony\Component\Console\Output\BufferedOutput(); 26 | $this->command->run( 27 | new \Symfony\Component\Console\Input\ArrayInput([ 28 | 'bucket' => 'testing', 29 | ]), 30 | $output 31 | ); 32 | $fetch = $output->fetch(); 33 | $this->assertNotNull($fetch); 34 | $this->assertContains('primary', $fetch); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Console/IndexRemoverCommandTest.php: -------------------------------------------------------------------------------- 1 | databaseManager = $this->app['db']; 19 | $this->command = new \Ytake\LaravelCouchbase\Console\IndexRemoverCommand($this->databaseManager); 20 | $this->command->setLaravel(new MockApplication); 21 | } 22 | 23 | public function testDropSecondaryIndex() 24 | { 25 | /** @var \Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 26 | $connection = $this->databaseManager->connection('couchbase'); 27 | $bucket = $connection->openBucket($this->bucket); 28 | $bucket->manager()->createN1qlPrimaryIndex(); 29 | $bucket->manager()->createN1qlIndex('testing_gsi', ['params1', 'params2']); 30 | $output = new \Symfony\Component\Console\Output\BufferedOutput(); 31 | $this->command->run( 32 | new \Symfony\Component\Console\Input\ArrayInput([ 33 | 'bucket' => $this->bucket, 34 | 'name' => 'testing_gsi', 35 | ]), 36 | $output 37 | ); 38 | $fetch = $output->fetch(); 39 | $this->assertSame("dropped SECONDARY INDEX [testing_gsi] for [index_testing] bucket.", trim($fetch)); 40 | sleep(5); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Console/PrimaryIndexCreatorCommandTest.php: -------------------------------------------------------------------------------- 1 | databaseManager = $this->app['db']; 19 | $this->command = new \Ytake\LaravelCouchbase\Console\PrimaryIndexCreatorCommand($this->databaseManager); 20 | $this->command->setLaravel(new MockApplication); 21 | } 22 | 23 | public function testCreatePrimaryIndex() 24 | { 25 | /** @var \Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 26 | $connection = $this->databaseManager->connection('couchbase'); 27 | $bucket = $connection->openBucket($this->bucket); 28 | $output = new \Symfony\Component\Console\Output\BufferedOutput(); 29 | $this->command->run( 30 | new \Symfony\Component\Console\Input\ArrayInput([ 31 | 'bucket' => $this->bucket, 32 | '--ignore' => true, 33 | ]), 34 | $output 35 | ); 36 | $fetch = $output->fetch(); 37 | $this->assertNotNull($fetch); 38 | $this->assertSame("created PRIMARY INDEX [#primary] for [index_testing] bucket.", trim($fetch)); 39 | $bucket->manager()->dropN1qlPrimaryIndex(); 40 | sleep(5); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Console/PrimaryIndexRemoverCommandTest.php: -------------------------------------------------------------------------------- 1 | databaseManager = $this->app['db']; 19 | $this->command = new \Ytake\LaravelCouchbase\Console\PrimaryIndexRemoverCommand($this->databaseManager); 20 | $this->command->setLaravel(new MockApplication); 21 | } 22 | 23 | public function testDropPrimaryIndex() 24 | { 25 | /** @var \Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 26 | $connection = $this->databaseManager->connection('couchbase'); 27 | $connection->manager(); 28 | $bucket = $connection->openBucket($this->bucket); 29 | $bucket->manager()->createN1qlPrimaryIndex(); 30 | $output = new \Symfony\Component\Console\Output\BufferedOutput(); 31 | $this->command->run( 32 | new \Symfony\Component\Console\Input\ArrayInput([ 33 | 'bucket' => $this->bucket, 34 | ]), 35 | $output 36 | ); 37 | $fetch = $output->fetch(); 38 | $this->assertNotNull($fetch); 39 | $this->assertSame("dropped PRIMARY INDEX [#primary] for [index_testing] bucket.", trim($fetch)); 40 | sleep(5); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/CouchbaseConnectorTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf(Cluster::class, $connector->connect([])); 15 | } 16 | 17 | public function testShouldBeClusterWithAuthenticator() 18 | { 19 | $connector = new CouchbaseConnector; 20 | $this->assertInstanceOf(Cluster::class, $connector->connect([ 21 | 'host' => 'couchbase://127.0.0.1', 22 | 'user' => 'testing', 23 | 'password' => 'testing', 24 | ])); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/CouchbaseGrammerTest.php: -------------------------------------------------------------------------------- 1 | app['db']; 17 | $this->grammer = new \Ytake\LaravelCouchbase\Query\Grammar; 18 | $processor = new \Ytake\LaravelCouchbase\Query\Processor(); 19 | $this->builder = new \Ytake\LaravelCouchbase\Query\Builder( 20 | $databaseManager->connection(), 21 | $this->grammer, 22 | $processor 23 | ); 24 | } 25 | 26 | public function testShouldReturnDeleteQueryNotUseKey() 27 | { 28 | $this->builder->from('testing')->where('arg', 1); 29 | $this->assertSame( 30 | 'delete from testing where arg = ? RETURNING *', 31 | $this->grammer->compileDelete($this->builder) 32 | ); 33 | } 34 | 35 | public function testShouldReturnDeleteQueryUseKey() 36 | { 37 | $this->builder->from('testing')->where('arg', 1)->key('testing'); 38 | $this->assertSame( 39 | 'delete from testing USE KEYS "testing" where arg = ? RETURNING *', 40 | $this->grammer->compileDelete($this->builder) 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/CouchbaseReconnectionTest.php: -------------------------------------------------------------------------------- 1 | app['db']; 15 | /** @var \Ytake\LaravelCouchbase\Database\CouchbaseConnection $couchbase */ 16 | $couchbase = $database->connection('couchbase'); 17 | $result = $couchbase->table('testing') 18 | ->where('reconnector_testing', 'should return null')->returning(['click'])->get(); 19 | $this->assertCount(0, $result); 20 | $this->assertInstanceOf( 21 | \CouchbaseCluster::class, 22 | $couchbase->getCouchbase() 23 | ); 24 | $database->disconnect('couchbase'); 25 | $property = $this->getProtectProperty($couchbase, 'connection'); 26 | $this->assertNull($property->getValue($couchbase)); 27 | $couchbase = $database->reconnect('couchbase'); 28 | $property = $this->getProtectProperty($couchbase, 'connection'); 29 | $this->assertInstanceOf(\CouchbaseCluster::class, $property->getValue($couchbase)); 30 | } 31 | 32 | public function testShouldReturnedReconnectableConnectionInstance() 33 | { 34 | /** @var \Illuminate\Database\DatabaseManager $database */ 35 | $database = $this->app['db']; 36 | $reConnection = $database->reconnect('couchbase'); 37 | $this->assertInstanceOf(\Ytake\LaravelCouchbase\Database\CouchbaseConnection::class, $reConnection); 38 | $result = $reConnection->table('testing') 39 | ->where('reconnector_testing', 'should return null')->returning(['click'])->get(); 40 | $this->assertCount(0, $result); 41 | } 42 | 43 | /** 44 | * @param $class 45 | * @param $name 46 | * 47 | * @return ReflectionProperty 48 | * @throws ReflectionException 49 | */ 50 | protected function getProtectProperty($class, $name): \ReflectionProperty 51 | { 52 | $class = new \ReflectionClass($class); 53 | $property = $class->getProperty($name); 54 | $property->setAccessible(true); 55 | return $property; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tests/CouchbaseStoreSerializeTest.php: -------------------------------------------------------------------------------- 1 | app['db']->connection('couchbase')->getCouchbase(); 12 | $this->store = new \Ytake\LaravelCouchbase\Cache\CouchbaseStore( 13 | $cluster, 'testing', '', 'testing', 'php' 14 | ); 15 | } 16 | 17 | public function testShouldBeArrayWithChangedDecoder() 18 | { 19 | $this->store->add('serialize', ['sample' => 'testing', 'need' => 'array']); 20 | $this->assertInternalType('array', $this->store->get('serialize')); 21 | $this->store->forget('serialize'); 22 | } 23 | 24 | public function testShouldBeObjectWithChangedDecoder() 25 | { 26 | $object = new stdClass; 27 | $this->store->add('serialize', $object); 28 | $this->assertInstanceOf('stdClass', $this->store->get('serialize')); 29 | $this->store->forget('serialize'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/CouchbaseStoreTest.php: -------------------------------------------------------------------------------- 1 | app['db']->connection('couchbase')->getCouchbase(); 12 | $this->store = new \Ytake\LaravelCouchbase\Cache\CouchbaseStore( 13 | $cluster, 'testing', '', 'testing' 14 | ); 15 | } 16 | 17 | public function testAddAlreadyKey() 18 | { 19 | $this->assertTrue($this->store->add('test', 'test', 120)); 20 | $this->assertFalse($this->store->add('test', 'test', 120)); 21 | $this->store->forget('test'); 22 | } 23 | 24 | public function testAddArrayableKey() 25 | { 26 | $this->store->add(['test', 'test2'], 'test', 120); 27 | $result = $this->store->get(['test', 'test2']); 28 | foreach ($result as $row) { 29 | $this->assertSame('test', $row); 30 | } 31 | 32 | $this->store->forget(['test', 'test2']); 33 | } 34 | 35 | public function testPrefix() 36 | { 37 | $this->assertSame('testing:', $this->store->getPrefix()); 38 | } 39 | 40 | public function testNotFoundKey() 41 | { 42 | $this->assertNull($this->store->get('notFoundTest')); 43 | } 44 | 45 | public function testFlushException() 46 | { 47 | $this->store->add('test1234', 'test', 120); 48 | $this->store->flush(); 49 | $this->assertNull($this->store->get('test1234')); 50 | } 51 | 52 | public function testIncrement() 53 | { 54 | $this->assertSame(1, $this->store->increment('test', 1)); 55 | $this->assertSame(11, $this->store->increment('test', 10)); 56 | $this->store->forget('test'); 57 | } 58 | 59 | public function testDecrement() 60 | { 61 | $this->assertSame(-1, $this->store->decrement('test', 1)); 62 | $this->assertSame(-11, $this->store->decrement('test', 10)); 63 | $this->assertSame(-21, $this->store->decrement('test', -10)); 64 | $this->store->forget('test'); 65 | } 66 | 67 | public function testUpsert() 68 | { 69 | $value = ['message' => 'testing']; 70 | $this->store->put('test', json_encode($value), 400); 71 | $this->assertSame(json_encode($value), $this->store->get('test')); 72 | $value = ['message' => 'testing2']; 73 | $this->store->put('test', json_encode($value), 400); 74 | $this->assertSame(json_encode($value), $this->store->get('test')); 75 | $this->store->forget('test'); 76 | } 77 | 78 | public function testCacheableComponentInstance() 79 | { 80 | /** @var Illuminate\Cache\Repository $cache */ 81 | $cache = $this->app['cache']->driver('couchbase'); 82 | $this->assertInstanceOf(get_class($this->store), $cache->getStore()); 83 | $cache->add('test', 'testing', 400); 84 | $this->assertSame('testing', $this->store->get('test')); 85 | $this->store->forget('test'); 86 | } 87 | 88 | public function testShouldBeLockedTheCache() 89 | { 90 | $this->store->forget('cache:lock'); 91 | $result = $this->store->lock('cache:lock', 600)->get(); 92 | $this->assertTrue($result); 93 | $resultSecond = $this->store->lock('cache:lock', 600)->get(); 94 | $this->assertFalse($resultSecond); 95 | 96 | $this->store->lock('cache:lock:two', 600)->get(function () {}); 97 | $this->assertNull($this->store->get('cache:lock:two')); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/CouchbaseTestCase.php: -------------------------------------------------------------------------------- 1 | createApplicationContainer(); 18 | } 19 | 20 | /** 21 | * @return \Illuminate\Config\Repository 22 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 23 | */ 24 | protected function registerConfigure() 25 | { 26 | $filesystem = new \Illuminate\Filesystem\Filesystem; 27 | $this->app['config']->set( 28 | "database", 29 | $filesystem->getRequire(__DIR__ . '/config/database.php') 30 | ); 31 | $this->app['config']->set( 32 | "cache", 33 | $filesystem->getRequire(__DIR__ . '/config/cache.php') 34 | ); 35 | $this->app['config']->set( 36 | "session", 37 | $filesystem->getRequire(__DIR__ . '/config/session.php') 38 | ); 39 | $this->app['config']->set( 40 | "queue", 41 | $filesystem->getRequire(__DIR__ . '/config/queue.php') 42 | ); 43 | $this->app['config']->set( 44 | 'couchbase', 45 | $filesystem->getRequire(__DIR__ . '/config/couchbase.php') 46 | ); 47 | $this->app['files'] = $filesystem; 48 | } 49 | 50 | protected function registerDatabase() 51 | { 52 | Model::clearBootedModels(); 53 | $this->app->singleton('db.factory', function ($app) { 54 | return new ConnectionFactory($app); 55 | }); 56 | $this->app->singleton('db', function ($app) { 57 | return new DatabaseManager($app, $app['db.factory']); 58 | }); 59 | $this->app->bind('db.connection', function ($app) { 60 | return $app['db']->connection(); 61 | }); 62 | } 63 | 64 | protected function registerCache() 65 | { 66 | $this->app->singleton('cache', function ($app) { 67 | return new \Illuminate\Cache\CacheManager($app); 68 | }); 69 | $this->app->singleton('cache.store', function ($app) { 70 | return $app['cache']->driver(); 71 | }); 72 | 73 | $this->app->singleton('memcached.connector', function () { 74 | return new \Illuminate\Cache\MemcachedConnector(); 75 | }); 76 | } 77 | 78 | protected function createApplicationContainer() 79 | { 80 | $this->app = new \TestContainer(); 81 | 82 | $this->app->singleton('config', function () { 83 | return new \Illuminate\Config\Repository; 84 | }); 85 | $this->registerConfigure(); 86 | $queueProvider = new \Illuminate\Queue\QueueServiceProvider($this->app); 87 | $queueProvider->register(); 88 | $sessionProvider = new \Illuminate\Session\SessionServiceProvider($this->app); 89 | $sessionProvider->register(); 90 | $this->registerDatabase(); 91 | $this->registerCache(); 92 | $couchbaseProvider = new ServiceProvider($this->app); 93 | $couchbaseProvider->register(); 94 | $couchbaseProvider->boot(); 95 | $this->app->bind( 96 | \Illuminate\Container\Container::class, 97 | function () { 98 | return $this->app; 99 | } 100 | ); 101 | (new \Illuminate\Events\EventServiceProvider($this->app))->register(); 102 | \Illuminate\Container\Container::setInstance($this->app); 103 | } 104 | 105 | protected function tearDown() 106 | { 107 | $this->app = null; 108 | } 109 | 110 | /** 111 | * @param string $bucket 112 | * 113 | * @return Couchbase\ClusterManager 114 | */ 115 | protected function createBucket($bucket) 116 | { 117 | $cluster = new \Couchbase\Cluster('127.0.0.1'); 118 | $clusterManager = $cluster->manager('Administrator', 'Administrator'); 119 | $clusterManager->createBucket($bucket, 120 | ['bucketType' => 'couchbase', 'saslPassword' => '', 'flushEnabled' => true]); 121 | sleep(5); 122 | return $clusterManager; 123 | } 124 | 125 | /** 126 | * @param CouchbaseClusterManager $clusterManager 127 | * @param string $bucket 128 | */ 129 | protected function removeBucket(\CouchbaseClusterManager $clusterManager, $bucket) 130 | { 131 | $clusterManager->removeBucket($bucket); 132 | } 133 | } 134 | 135 | class TestContainer extends \Illuminate\Container\Container 136 | { 137 | public function version() 138 | { 139 | return '5.5.1'; 140 | } 141 | } 142 | 143 | class ServiceProvider extends \Ytake\LaravelCouchbase\CouchbaseServiceProvider 144 | { 145 | public function register() 146 | { 147 | $this->registerCouchbaseComponent(); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /tests/DatabaseTest.php: -------------------------------------------------------------------------------- 1 | connection = new \Ytake\LaravelCouchbase\Database\CouchbaseConnection( 12 | $this->app['config']->get('database.connections.couchbase'), 13 | 'couchbase' 14 | ); 15 | } 16 | 17 | /** 18 | * @expectedException \Ytake\LaravelCouchbase\Exceptions\NotSupportedException 19 | */ 20 | public function testNotSupportedRollback() 21 | { 22 | $this->connection->rollBack(1); 23 | } 24 | 25 | /** 26 | * @expectedException \Ytake\LaravelCouchbase\Exceptions\NotSupportedException 27 | */ 28 | public function testNotSupportedTransaction() 29 | { 30 | $this->connection->transaction(function () { 31 | 32 | }); 33 | } 34 | 35 | /** 36 | * @expectedException \Ytake\LaravelCouchbase\Exceptions\NotSupportedException 37 | */ 38 | public function testNotSupportedBeginTransaction() 39 | { 40 | $this->connection->beginTransaction(); 41 | } 42 | 43 | /** 44 | * @expectedException \Ytake\LaravelCouchbase\Exceptions\NotSupportedException 45 | */ 46 | public function testNotSupportedCommit() 47 | { 48 | $this->connection->commit(); 49 | } 50 | 51 | public function testCallableChangeToConsistency() 52 | { 53 | $this->connection->callableConsistency(\Couchbase\N1qlQuery::REQUEST_PLUS, 54 | function (\Ytake\LaravelCouchbase\Database\CouchbaseConnection $con) { 55 | \Closure::bind(function () { 56 | \PHPUnit\Framework\Assert::assertSame(\Couchbase\N1qlQuery::REQUEST_PLUS, $this->consistency); 57 | }, $con, get_class($con))->__invoke(); 58 | } 59 | ); 60 | \Closure::bind(function () { 61 | \PHPUnit\Framework\Assert::assertSame(\Couchbase\N1qlQuery::NOT_BOUNDED, $this->consistency); 62 | }, $this->connection, get_class($this->connection))->__invoke(); 63 | } 64 | 65 | public function testShouldReturnConfigurationArray() 66 | { 67 | $bucket = $this->connection->openBucket('testing'); 68 | static::assertInternalType('array', $this->connection->getOptions($bucket)); 69 | } 70 | 71 | public function testShouldBeCouchbaseInstanceForReconnection() 72 | { 73 | /** @var Ytake\LaravelCouchbase\Database\CouchbaseConnection $db */ 74 | $db = $this->app['db']->connection(); 75 | $this->assertSame('couchbase', $db->getName()); 76 | $bucket = $db->bucket('testing'); 77 | $db->disconnect(); 78 | $this->app['db']->reconnect(); 79 | $this->assertInstanceOf(get_class($bucket), $this->app['db']->connection()->bucket('testing')); 80 | /** @var \Illuminate\Database\DatabaseManager $manager */ 81 | $manager = $this->app['db']; 82 | $this->assertInstanceOf(Ytake\LaravelCouchbase\Database\CouchbaseConnection::class, $manager->reconnect()); 83 | $this->assertInstanceOf(Ytake\LaravelCouchbase\Database\CouchbaseConnection::class, $db->setPdo(null)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/DeleteQueryTest.php: -------------------------------------------------------------------------------- 1 | connection = new \Ytake\LaravelCouchbase\Database\CouchbaseConnection( 12 | $this->app['config']->get('database.connections.couchbase'), 13 | 'couchbase' 14 | ); 15 | } 16 | 17 | public function testInsertAndDeleteQueries() 18 | { 19 | $value = [ 20 | 'click' => 'to edit', 21 | 'content' => 'testing' 22 | ]; 23 | $key = 'insert:and:delete'; 24 | /** @var Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 25 | $connection = $this->app['db']->connection('couchbase'); 26 | $result = $connection->table('testing')->key($key)->insert($value); 27 | $this->assertInstanceOf('stdClass', $result); 28 | sleep(1); 29 | $deleteReturning = $connection->table('testing')->key($key) 30 | ->where('click', 'to edit')->returning(['click'])->delete(); 31 | $this->assertSame('to edit', $deleteReturning->click); 32 | } 33 | 34 | public function testInsertAndNotDeleteQueries() 35 | { 36 | $value = [ 37 | 'click' => 'to edit', 38 | 'content' => 'testing' 39 | ]; 40 | $key = 'insert:and:delete'; 41 | /** @var Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 42 | $connection = $this->app['db']->connection('couchbase'); 43 | $connection->table('testing')->key($key)->insert($value); 44 | $this->assertInternalType('array', $connection->metrics()); 45 | $connection->openBucket('testing')->manager()->flush(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/DesignDocumentTest.php: -------------------------------------------------------------------------------- 1 | assertArrayHasKey('testing', $decodeDocument); 25 | $this->assertArrayHasKey('map', $decodeDocument['testing']); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/MemcachedBucketTest.php: -------------------------------------------------------------------------------- 1 | repository = $this->app['cache']->driver('couchbase-memcached'); 13 | } 14 | 15 | public function testShouldReturnRepositoryInstance() 16 | { 17 | $this->assertInstanceOf('Illuminate\Cache\Repository', $this->repository); 18 | } 19 | 20 | public function testShouldReturnSameCacheAccess() 21 | { 22 | $this->repository->add('memcached-testing', 'couchbase', 60); 23 | $this->assertSame('couchbase', $this->repository->get('memcached-testing')); 24 | $this->repository->forget('memcached-testing'); 25 | $this->assertNull($this->repository->get('memcached-testing')); 26 | } 27 | 28 | public function testShouldReturnIncrementalValues() 29 | { 30 | $this->repository->increment('memcached-testing'); 31 | $this->assertSame(1, $this->repository->get('memcached-testing')); 32 | 33 | $this->repository->increment('memcached-testing', 100); 34 | $this->assertSame(101, $this->repository->get('memcached-testing')); 35 | $this->repository->decrement('memcached-testing', 100); 36 | $this->assertSame(1, $this->repository->get('memcached-testing')); 37 | $this->repository->decrement('memcached-testing', 100); 38 | $this->assertSame(0, $this->repository->get('memcached-testing')); 39 | $this->repository->forget('memcached-testing'); 40 | 41 | $this->repository->decrement('memcached-testing', 100); 42 | $this->assertSame(0, $this->repository->get('memcached-testing')); 43 | $this->repository->forget('memcached-testing'); 44 | } 45 | 46 | public function testShouldReturnNullFlushRecord() 47 | { 48 | $this->repository->add('memcached-testing', 'couchbase', 60); 49 | $this->repository->decrement('memcached-testing-decrement', 100); 50 | $this->repository->increment('memcached-testing-increment', 100); 51 | $this->repository->flush(); 52 | $this->assertNull($this->repository->get('memcached-testing')); 53 | $this->assertNull($this->repository->get('memcached-testing-decrement')); 54 | $this->assertNull($this->repository->get('memcached-testing-increment')); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/MemcachedConnectorTest.php: -------------------------------------------------------------------------------- 1 | connect( 9 | [ 10 | ['host' => '127.0.0.1', 'port' => '11255', 'weight' => 100], 11 | ['host' => '127.0.0.1', 'port' => '11255', 'weight' => 100], 12 | ] 13 | ); 14 | 15 | $this->assertInstanceOf(\Memcached::class, $memcached); 16 | } 17 | 18 | public function testShouldReturnMemcachedInstanceForBucket() 19 | { 20 | /** @var \Illuminate\Cache\Repository $cache */ 21 | $cache = $this->app['cache']->driver('couchbase-memcached'); 22 | $this->assertInstanceOf('Illuminate\Cache\Repository', $cache); 23 | $cache->add('testing', 'testing', 20); 24 | $this->assertEquals('testing', $cache->get('testing')); 25 | $cache->add('testing-array', ['testing'], 20); 26 | $this->assertInternalType('array', $cache->get('testing-array')); 27 | $cache = $this->app['cache']->driver('memcached'); 28 | $this->assertInstanceOf('Illuminate\Cache\Repository', $cache); 29 | $cache->flush(); 30 | $this->assertNull($cache->get('testing-array')); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/MockApplication.php: -------------------------------------------------------------------------------- 1 | app['session']; 9 | /** @var \Illuminate\Session\Store $driver */ 10 | $driver = $session->driver('couchbase'); 11 | $this->assertInstanceOf(\Illuminate\Session\Store::class, $driver); 12 | $driver->put('session:data', 'hello'); 13 | $this->assertSame('hello', $driver->get('session:data')); 14 | $driver->flush(); 15 | $this->assertNull($driver->get('session:data')); 16 | } 17 | 18 | public function testCacheDriver() 19 | { 20 | /** @var Illuminate\Cache\CacheManager $cache */ 21 | $cache = $this->app['cache']; 22 | /** @var Illuminate\Cache\Repository $repository */ 23 | $repository = $cache->driver('couchbase'); 24 | $this->assertInstanceOf(get_class($repository), $cache->driver('couchbase2')); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Query/ViewTest.php: -------------------------------------------------------------------------------- 1 | connection = $this->app['db']->connection(); 34 | 35 | $this->databaseManager = $this->app['db']; 36 | $this->config = $this->app['config']; 37 | $this->command = new DesignCreatorCommand( 38 | $this->databaseManager, 39 | $this->config->get('couchbase.design') 40 | ); 41 | $this->command->setLaravel(new MockApplication); 42 | } 43 | 44 | /** 45 | * @see \Ytake\LaravelCouchbase\Query\View::from() 46 | * @see \Ytake\LaravelCouchbase\Query\View::execute() 47 | */ 48 | public function testItShouldBeStdClass() 49 | { 50 | /** @var \Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 51 | $connection = $this->databaseManager->connection('couchbase'); 52 | $bucket = $connection->openBucket($this->bucket); 53 | $bucket->manager()->removeDesignDocument('dev_testing'); 54 | $bucket->manager()->removeDesignDocument('dev_testing_name'); 55 | sleep(6); 56 | $output = new BufferedOutput(); 57 | $this->command->run( 58 | new ArrayInput([ 59 | 'bucket' => $this->bucket, 60 | ]), 61 | $output 62 | ); 63 | sleep(4); 64 | /** @var Illuminate\Events\Dispatcher $dispatcher */ 65 | $dispatcher = $this->app['events']; 66 | $dispatcher->listen(\Ytake\LaravelCouchbase\Events\ViewQuerying::class, function ($instance) { 67 | $this->assertInstanceOf(\Ytake\LaravelCouchbase\Events\ViewQuerying::class, $instance); 68 | }); 69 | $view = $this->connection->view("testing"); 70 | $query = $view->from("dev_testing", "testing"); 71 | $result = $view->execute($query); 72 | $this->assertInstanceOf('stdClass', $result); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/Queue/QueueCouchbaseConnectorTest.php: -------------------------------------------------------------------------------- 1 | app['db']->connection('couchbase'); 19 | 20 | $schema = $connection->getSchemaBuilder(); 21 | $schema->create('jobs', function (\Ytake\LaravelCouchbase\Schema\Blueprint $blueprint) { 22 | $blueprint->primaryIndex(); 23 | }); 24 | $connection->openBucket(self::BUCKET)->manager()->flush(); 25 | } 26 | 27 | public function testQueueConnect() 28 | { 29 | /** @var \Illuminate\Queue\QueueManager $queue */ 30 | $queue = $this->app['queue']; 31 | $connect = $queue->connection('couchbase'); 32 | $this->assertInstanceOf(\Ytake\LaravelCouchbase\Queue\CouchbaseQueue::class, $connect); 33 | } 34 | 35 | public function testShouldAppendQueueWorkTasks() 36 | { 37 | /** @var \Illuminate\Queue\QueueManager $queue */ 38 | $queue = $this->app['queue']; 39 | /** @var \Ytake\LaravelCouchbase\Queue\CouchbaseQueue $connect */ 40 | $connect = $queue->connection('couchbase'); 41 | /** @var CouchbaseConnection $database */ 42 | $database = $connect->getDatabase(); 43 | $database->openBucket(self::BUCKET)->manager()->flush(); 44 | sleep(4); 45 | $database->openBucket(self::BUCKET); 46 | $this->assertNull($connect->pop()); 47 | $connect->bulk(['testing:queue1', 'testing:queue2']); 48 | sleep(5); 49 | /** @var Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 50 | $connection = $this->app['db']->connection('couchbase'); 51 | $this->assertSame(2, $connection->table(self::BUCKET)->where('queue', 'default')->count()); 52 | /** @var Illuminate\Queue\Jobs\DatabaseJob $databaseJob */ 53 | $databaseJob = $connect->pop(); 54 | $this->assertInstanceOf(Illuminate\Queue\Jobs\DatabaseJob::class, $databaseJob); 55 | $databaseJob->delete(); 56 | sleep(1); 57 | $this->assertSame(1, $connection->table(self::BUCKET)->where('queue', 'default')->count()); 58 | } 59 | 60 | public function tearDown() 61 | { 62 | /** @var Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 63 | $connection = $this->app['db']->connection('couchbase'); 64 | $this->removeBucket($connection->manager(), self::BUCKET); 65 | parent::tearDown(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Schema/BlueprintTest.php: -------------------------------------------------------------------------------- 1 | connection = $this->app['db']->connection(); 17 | } 18 | 19 | public function testBlueprintSchemaBuild() 20 | { 21 | $schema = $this->connection->getSchemaBuilder(); 22 | $schema->create('sample', function (\Ytake\LaravelCouchbase\Schema\Blueprint $blueprint) { 23 | try { 24 | $blueprint->dropPrimary(); 25 | }catch (\Exception $e) { 26 | 27 | } 28 | try { 29 | $blueprint->dropIndex("secondary"); 30 | }catch (\Exception $e) { 31 | 32 | } 33 | $blueprint->primaryIndex(); 34 | $blueprint->index(["message"], "secondary"); 35 | }); 36 | $indexes = $this->connection->openBucket('sample')->manager()->listN1qlIndexes(); 37 | $this->assertNotCount(0, $indexes); 38 | $this->removeBucket($this->connection->manager(), 'sample'); 39 | sleep(5); 40 | } 41 | 42 | public function testBlueprintSchemaBuildAndDropIndexes() 43 | { 44 | $schema = $this->connection->getSchemaBuilder(); 45 | $schema->create('sample', function (\Ytake\LaravelCouchbase\Schema\Blueprint $blueprint) { 46 | try { 47 | $blueprint->dropPrimary(); 48 | }catch (\Exception $e) { 49 | 50 | } 51 | try { 52 | $blueprint->dropIndex("secondary"); 53 | }catch (\Exception $e) { 54 | 55 | } 56 | $blueprint->primaryIndex(); 57 | $blueprint->index(["message"], "secondary"); 58 | $blueprint->dropPrimary(); 59 | $blueprint->dropIndex("secondary"); 60 | }); 61 | $indexes = $this->connection->openBucket('sample')->manager()->listN1qlIndexes(); 62 | $this->assertCount(0, $indexes); 63 | $this->removeBucket($this->connection->manager(), 'sample'); 64 | sleep(5); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Schema/BuilderTest.php: -------------------------------------------------------------------------------- 1 | connection = $this->app['db']->connection(); 17 | } 18 | 19 | public function testShouldReturnSchemaBuilderInstance() 20 | { 21 | $schemaBuilder = $this->connection->getSchemaBuilder(); 22 | $this->assertInstanceOf(\Ytake\LaravelCouchbase\Schema\Builder::class, $schemaBuilder); 23 | } 24 | 25 | public function testShouldReturnFalseSpecifiedNotExistsBucket() 26 | { 27 | $this->assertFalse($this->connection->getSchemaBuilder()->hasTable("sample1")); 28 | } 29 | 30 | public function testShouldReturnTrueSpecifiedExistsBucket() 31 | { 32 | $clusterManager = $this->createBucket("sample1"); 33 | $this->assertTrue($this->connection->getSchemaBuilder()->hasTable("sample1")); 34 | $this->removeBucket($clusterManager, "sample1"); 35 | } 36 | 37 | public function testShouldReturnTrue() 38 | { 39 | $clusterManager = $this->createBucket("sample1"); 40 | $schemaBuilder = $this->connection->getSchemaBuilder(); 41 | $this->assertTrue($schemaBuilder->hasColumn("sample1", "testing")); 42 | $this->assertTrue($schemaBuilder->hasColumns("sample1", ["testing"])); 43 | $this->removeBucket($clusterManager, "sample1"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/TaggingCacheStoreTest.php: -------------------------------------------------------------------------------- 1 | app['db']->connection('couchbase')->getCouchbase(); 15 | $this->store = new \Ytake\LaravelCouchbase\Cache\CouchbaseStore( 16 | $cluster, 'testing', '', 'testing' 17 | ); 18 | } 19 | 20 | public function testAddTaggableKeys() 21 | { 22 | $this->store->tags(['testing1', 'testing2'])->add('tagging', 'test', 20); 23 | $this->assertSame('test', $this->store->tags(['testing1', 'testing2'])->get('tagging')); 24 | $this->store->tags(['testing4'])->add('tagging2', 'test2', 20); 25 | $this->store->tags(['testing1', 'testing2'])->flush(); 26 | $this->assertNull($this->store->tags(['testing1', 'testing2'])->get('tagging')); 27 | $this->assertSame('test2', $this->store->tags(['testing4'])->get('tagging2')); 28 | $this->store->tags(['testing4'])->flush(); 29 | $this->assertNull($this->store->tags(['testing4'])->get('tagging')); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/UpdateQueryTest.php: -------------------------------------------------------------------------------- 1 | connection = new \Ytake\LaravelCouchbase\Database\CouchbaseConnection( 16 | $this->app['config']->get('database.connections.couchbase'), 17 | 'couchbase' 18 | ); 19 | } 20 | 21 | public function testInsertAndUpdateQueries() 22 | { 23 | $value = [ 24 | 'click' => 'to edit', 25 | 'content' => 'testing', 26 | ]; 27 | $key = 'insert'; 28 | /** @var Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 29 | $connection = $this->app['db']->connection('couchbase'); 30 | $cluster = $connection->getCouchbase(); 31 | $store = new \Ytake\LaravelCouchbase\Cache\CouchbaseStore( 32 | $cluster, 'testing', '', 'testing' 33 | ); 34 | $store->flush(); 35 | $result = $connection->table('testing')->key($key)->insert($value); 36 | $this->assertInstanceOf('stdClass', $result); 37 | 38 | $result = $connection->table('testing')->key($key) 39 | ->where('click', 'to edit')->update( 40 | ['click' => 'testing edit'] 41 | ); 42 | sleep(10); 43 | $this->assertInstanceOf(\Illuminate\Support\Collection::class, $connection->table('testing')->get()); 44 | $generator = $connection->table('testing')->cursor(); 45 | $this->assertInstanceOf(\Generator::class, $generator); 46 | $this->assertInstanceOf('stdClass', $result); 47 | $this->assertSame('testing edit', $result->click); 48 | $connection->openBucket('testing')->manager()->flush(); 49 | } 50 | 51 | public function testInsertAndNotUpdateQueries() 52 | { 53 | $value = [ 54 | 'click' => 'to edit', 55 | 'content' => 'testing', 56 | ]; 57 | $key = 'insert:no'; 58 | /** @var Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 59 | $connection = $this->app['db']->connection('couchbase'); 60 | $result = $connection->table('testing')->key($key)->insert($value); 61 | $this->assertInstanceOf('stdClass', $result); 62 | sleep(1); 63 | /** @var Illuminate\Events\Dispatcher $dispatcher */ 64 | $dispatcher = $this->app['events']; 65 | $dispatcher->listen(QueryPrepared::class, function ($instance) { 66 | /** @var QueryPrepared $instance */ 67 | static::assertInstanceOf(QueryPrepared::class, $instance); 68 | static::assertInstanceOf(\Couchbase\N1qlQuery::class, $instance->getQuery()); 69 | }); 70 | $dispatcher->listen(ResultReturning::class, function ($instance) { 71 | /** @var ResultReturning $instance */ 72 | static::assertInstanceOf(ResultReturning::class, $instance); 73 | static::assertInstanceOf(\stdClass::class, $instance->returning()); 74 | }); 75 | $this->assertSame(null, $connection->table('testing')->key($key)->where('clicking', 'to edit')->first()); 76 | $connection->openBucket('testing')->manager()->flush(); 77 | } 78 | 79 | public function testUpsertQuery() 80 | { 81 | $value = [ 82 | 'click' => 'to edit', 83 | 'content' => 'testing', 84 | ]; 85 | $key = 'upsert:click:content'; 86 | /** @var Ytake\LaravelCouchbase\Database\CouchbaseConnection $connection */ 87 | $connection = $this->app['db']->connection('couchbase'); 88 | $result = $connection->table('testing')->key($key)->upsert($value); 89 | $this->assertInstanceOf('stdClass', $result); 90 | $result = $connection->table('testing')->key($key)->upsert([ 91 | 'click' => 'to', 92 | 'content' => 'testing for upsert', 93 | ]); 94 | $this->assertSame('testing for upsert', $result->content); 95 | $connection->openBucket('testing')->manager()->flush(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/bin/memcached.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | if (php --version | grep -i HipHop > /dev/null); then 4 | echo "skipping memcache on HHVM" 5 | else 6 | mkdir -p ~/.phpenv/versions/$(phpenv version-name)/etc 7 | echo "extension=memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 8 | fi 9 | -------------------------------------------------------------------------------- /tests/config/app.php: -------------------------------------------------------------------------------- 1 | 'testing1testing1testing1testing1', 5 | 6 | 'cipher' => 'AES-256-CBC', 7 | ]; 8 | -------------------------------------------------------------------------------- /tests/config/cache.php: -------------------------------------------------------------------------------- 1 | 'couchbase', 5 | 'stores' => [ 6 | 'couchbase' => [ 7 | 'driver' => 'couchbase', 8 | 'bucket' => 'testing', 9 | 'bucket_password' => '', 10 | ], 11 | 'couchbase2' => [ 12 | 'driver' => 'couchbase', 13 | 'bucket' => 'testing', 14 | 'bucket_password' => '', 15 | ], 16 | 'couchbase-memcached' => [ 17 | 'driver' => 'couchbase-memcached', 18 | 'servers' => [ 19 | [ 20 | 'host' => '127.0.0.1', 21 | 'port' => 11255, 22 | 'weight' => 100, 23 | 'bucket' => 'memcache-couch', 24 | 'options' => [ 25 | 26 | ], 27 | ], 28 | ], 29 | 'sasl' => [ 30 | 'Administrator', 31 | 'Administrator', 32 | ], 33 | ], 34 | 'couchbase-memcached_second' => [ 35 | 'driver' => 'couchbase-memcached', 36 | 'servers' => [ 37 | [ 38 | 'host' => '127.0.0.1', 39 | 'port' => 11255, 40 | 'weight' => 100, 41 | 'bucket' => 'memcache-couch', 42 | 'options' => [ 43 | 44 | ], 45 | ], 46 | ], 47 | 'sasl' => [ 48 | 'Administrator', 49 | 'Administrator', 50 | ], 51 | ], 52 | 'memcached' => [ 53 | 'driver' => 'memcached', 54 | 'servers' => [ 55 | [ 56 | 'host' => '127.0.0.1', 57 | 'port' => 11211, 58 | 'weight' => 100, 59 | ], 60 | ], 61 | ], 62 | ], 63 | 'prefix' => 'testing', 64 | ]; 65 | -------------------------------------------------------------------------------- /tests/config/couchbase.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'dev_testing_name' => [ 21 | 'views' => [ 22 | 't' => [ 23 | 'map' => file_get_contents(__DIR__ . '/../resources/sample.ddoc'), 24 | ], 25 | ], 26 | ], 27 | 'dev_testing' => [ 28 | 'views' => [ 29 | 'testing' => [ 30 | 'map' => file_get_contents(__DIR__ . '/../resources/sample.ddoc'), 31 | ], 32 | ], 33 | ], 34 | ], 35 | ]; 36 | -------------------------------------------------------------------------------- /tests/config/database.php: -------------------------------------------------------------------------------- 1 | PDO::FETCH_CLASS, 4 | 'default' => 'couchbase', 5 | 'connections' => [ 6 | 'couchbase' => [ 7 | 'driver' => 'couchbase', 8 | 'host' => 'couchbase://127.0.0.1?dnssrv=false', 9 | // 'bucket_password' => null, 10 | // 'bucket' => '', 11 | 'administrator' => [ 12 | 'user' => 'Administrator', 13 | 'password' => 'Administrator', 14 | ], 15 | 'user' => 'Administrator', 16 | 'password' => 'Administrator', 17 | ], 18 | /* 19 | 'testing' => [ 20 | 'driver' => 'couchbase', 21 | 'host' => 'couchbase://127.0.0.1?detailed_errcodes=1&http_poolsize=0&operation_timeout=4', 22 | // 'bucket_password' => null, 23 | // 'bucket' => '', 24 | 'administrator' => [ 25 | 'user' => 'Administrator', 26 | 'password' => 'Administrator', 27 | ], 28 | 'user' => 'testing', 29 | 'password' => 'testing', 30 | ], 31 | */ 32 | ], 33 | 'migrations' => 'migrations', 34 | ]; 35 | -------------------------------------------------------------------------------- /tests/config/queue.php: -------------------------------------------------------------------------------- 1 | 'couchbase', 6 | 7 | 'connections' => [ 8 | 9 | 'couchbase' => [ 10 | 'driver' => 'couchbase', 11 | 'bucket' => 'jobs', 12 | 'queue' => 'default', 13 | 'retry_after' => 90, 14 | ], 15 | ], 16 | ]; 17 | -------------------------------------------------------------------------------- /tests/config/session.php: -------------------------------------------------------------------------------- 1 | 'couchbase', 6 | 7 | 'lifetime' => 120, 8 | 9 | 'expire_on_close' => false, 10 | 11 | 'encrypt' => false, 12 | 13 | 'connection' => null, 14 | 15 | 'table' => 'sessions', 16 | 17 | 'lottery' => [2, 100], 18 | 19 | 'cookie' => 'laravel_session', 20 | 21 | 'path' => '/', 22 | 23 | 'domain' => null, 24 | 25 | 'secure' => false, 26 | 27 | ]; 28 | -------------------------------------------------------------------------------- /tests/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/resources/sample.ddoc: -------------------------------------------------------------------------------- 1 | function (doc, meta) { 2 | emit(meta.id, null); 3 | } 4 | --------------------------------------------------------------------------------