├── .github └── workflows │ └── php.yml ├── .gitignore ├── .php_cs.dist ├── README.md ├── composer.json ├── psalm.xml └── src └── Tarantool ├── Connection.php ├── Eloquent └── Model.php ├── Query ├── Builder.php ├── Grammar.php └── Processor.php ├── Schema └── Grammar.php ├── ServiceProvider.php └── Traits ├── Dsn.php ├── Helper.php └── Query.php /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | 13 | - name: Validate composer.json and composer.lock 14 | run: composer validate 15 | 16 | - name: Install dependencies 17 | run: composer install --prefer-dist --no-progress --no-suggest 18 | 19 | - name: Run phpcs 20 | run: composer run-script test:style 21 | 22 | - name: Run psalm 23 | run: composer run-script test:psalm -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor/ 3 | composer.lock 4 | .php_cs.cache 5 | .php_cs -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | ['syntax' => 'short'], 8 | 'binary_operator_spaces' => [ 9 | 'default' => 'single_space', 10 | 'operators' => ['=>' => null] 11 | ], 12 | 'blank_line_after_namespace' => true, 13 | 'blank_line_after_opening_tag' => true, 14 | 'blank_line_before_statement' => [ 15 | 'statements' => ['return'] 16 | ], 17 | 'braces' => true, 18 | 'cast_spaces' => true, 19 | 'class_attributes_separation' => [ 20 | 'elements' => ['method'] 21 | ], 22 | 'class_definition' => true, 23 | 'concat_space' => [ 24 | 'spacing' => 'none' 25 | ], 26 | 'declare_equal_normalize' => true, 27 | 'elseif' => true, 28 | 'encoding' => true, 29 | 'full_opening_tag' => true, 30 | 'fully_qualified_strict_types' => true, // added by Shift 31 | 'function_declaration' => true, 32 | 'function_typehint_space' => true, 33 | 'heredoc_to_nowdoc' => true, 34 | 'include' => true, 35 | 'increment_style' => ['style' => 'post'], 36 | 'indentation_type' => true, 37 | 'linebreak_after_opening_tag' => true, 38 | 'line_ending' => true, 39 | 'lowercase_cast' => true, 40 | 'lowercase_constants' => true, 41 | 'lowercase_keywords' => true, 42 | 'lowercase_static_reference' => true, // added from Symfony 43 | 'magic_method_casing' => true, // added from Symfony 44 | 'magic_constant_casing' => true, 45 | 'method_argument_space' => true, 46 | 'native_function_casing' => true, 47 | 'no_alias_functions' => true, 48 | 'no_extra_blank_lines' => [ 49 | 'tokens' => [ 50 | 'extra', 51 | 'throw', 52 | 'use', 53 | 'use_trait', 54 | ] 55 | ], 56 | 'no_blank_lines_after_class_opening' => true, 57 | 'no_blank_lines_after_phpdoc' => true, 58 | 'no_closing_tag' => true, 59 | 'no_empty_phpdoc' => true, 60 | 'no_empty_statement' => true, 61 | 'no_leading_import_slash' => true, 62 | 'no_leading_namespace_whitespace' => true, 63 | 'no_mixed_echo_print' => [ 64 | 'use' => 'echo' 65 | ], 66 | 'no_multiline_whitespace_around_double_arrow' => true, 67 | 'multiline_whitespace_before_semicolons' => [ 68 | 'strategy' => 'no_multi_line' 69 | ], 70 | 'no_short_bool_cast' => true, 71 | 'no_singleline_whitespace_before_semicolons' => true, 72 | 'no_spaces_after_function_name' => true, 73 | 'no_spaces_around_offset' => true, 74 | 'no_spaces_inside_parenthesis' => true, 75 | 'no_trailing_comma_in_list_call' => true, 76 | 'no_trailing_comma_in_singleline_array' => true, 77 | 'no_trailing_whitespace' => true, 78 | 'no_trailing_whitespace_in_comment' => true, 79 | 'no_unneeded_control_parentheses' => true, 80 | 'no_unreachable_default_argument_value' => true, 81 | 'no_useless_return' => true, 82 | 'no_whitespace_before_comma_in_array' => true, 83 | 'no_whitespace_in_blank_line' => true, 84 | 'normalize_index_brace' => true, 85 | 'not_operator_with_successor_space' => true, 86 | 'object_operator_without_whitespace' => true, 87 | 'ordered_imports' => ['sortAlgorithm' => 'alpha'], 88 | 'phpdoc_indent' => true, 89 | 'phpdoc_inline_tag' => true, 90 | 'phpdoc_no_access' => true, 91 | 'phpdoc_no_package' => true, 92 | 'phpdoc_no_useless_inheritdoc' => true, 93 | 'phpdoc_scalar' => true, 94 | 'phpdoc_single_line_var_spacing' => true, 95 | 'phpdoc_summary' => true, 96 | 'phpdoc_to_comment' => false, 97 | 'phpdoc_trim' => true, 98 | 'phpdoc_types' => true, 99 | 'phpdoc_var_without_name' => true, 100 | 'psr4' => true, 101 | 'self_accessor' => true, 102 | 'short_scalar_cast' => true, 103 | 'simplified_null_return' => false, // disabled by Shift 104 | 'single_blank_line_at_eof' => true, 105 | 'single_blank_line_before_namespace' => true, 106 | 'single_class_element_per_statement' => true, 107 | 'single_import_per_statement' => true, 108 | 'single_line_after_imports' => true, 109 | 'single_line_comment_style' => [ 110 | 'comment_types' => ['hash'] 111 | ], 112 | 'single_quote' => true, 113 | 'space_after_semicolon' => true, 114 | 'standardize_not_equals' => true, 115 | 'switch_case_semicolon_to_colon' => true, 116 | 'switch_case_space' => true, 117 | 'ternary_operator_spaces' => true, 118 | 'trailing_comma_in_multiline_array' => true, 119 | 'trim_array_spaces' => true, 120 | 'unary_operator_spaces' => true, 121 | 'visibility_required' => [ 122 | 'elements' => ['method', 'property'] 123 | ], 124 | 'whitespace_after_comma_in_array' => true, 125 | 'no_unused_imports' => true, 126 | ]; 127 | 128 | $project_path = getcwd(); 129 | $finder = Finder::create() 130 | ->in([ 131 | $project_path . '/src', 132 | ]) 133 | ->name('*.php') 134 | ->ignoreDotFiles(true) 135 | ->ignoreVCS(true); 136 | 137 | return Config::create() 138 | ->setFinder($finder) 139 | ->setRules($rules) 140 | ->setRiskyAllowed(true) 141 | ->setUsingCache(true); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Tarantool 2 | 3 | This package adds functionalities to the Eloquent model and Query builder for Tarantool, using the original Laravel API. *This library extends the original Laravel classes, so it uses exactly the same methods.* 4 | 5 | Installation 6 | ------------ 7 | #### Laravel Version Compatibility 8 | 9 | Laravel | Package 10 | :---------|:---------- 11 | 5.8.x | 0.1.9 12 | 6.x | 0.1.9 13 | 7.x | 0.1.9 14 | 8.x | 1.x 15 | 16 | 17 | #### Via Composer 18 | 19 | ``` 20 | composer require chocofamilyme/laravel-tarantool 21 | ``` 22 | 23 | Configuration 24 | ------------- 25 | 26 | You can use Tarantool either as the main database, either as a side database. To do so, add a new `tarantool` connection to `config/database.php`: 27 | 28 | ```php 29 | 'tarantool' => [ 30 | 'driver' => 'tarantool', 31 | 'host' => env('DB_HOST', '127.0.0.1'), 32 | 'port' => env('DB_PORT', 3301), 33 | 'database' => env('DB_DATABASE'), 34 | 'username' => env('DB_USERNAME'), 35 | 'password' => env('DB_PASSWORD'), 36 | 'driver_oprions' => [ 37 | 'connection_type' => env('DB_CONNECTION_TYPE', 'tcp') 38 | ], 39 | 'options' => [ 40 | 'connect_timeout' => 5, 41 | 'max_retries' => 3 42 | ] 43 | ], 44 | ``` 45 | 46 | Set tarantool as main database 47 | 48 | ```php 49 | 'default' => env('DB_CONNECTION', 'tarantool'), 50 | ``` 51 | 52 | You can also configure connection with dsn string: 53 | 54 | ```php 55 | 'tarantool' => [ 56 | 'driver' => 'tarantool', 57 | 'dsn' => env('DB_DSN'), 58 | 'database' => env('DB_DATABASE'), 59 | ], 60 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chocofamilyme/laravel-tarantool", 3 | "description": "A Tarantool based Eloquent ORM and Query builder for Laravel", 4 | "type": "library", 5 | "keywords": [ 6 | "db", 7 | "tarantool", 8 | "laravel", 9 | "eloquent-tarantool", 10 | "eloquent", 11 | "orm" 12 | ], 13 | "require": { 14 | "php": "^7.3|^8.0", 15 | "illuminate/support": "^8.0", 16 | "illuminate/container": "^8.0", 17 | "illuminate/database": "^8.0", 18 | "illuminate/events": "^8.0", 19 | "rybakit/msgpack": "^0.7", 20 | "tarantool/client": "^0.9.0" 21 | }, 22 | "require-dev": { 23 | "friendsofphp/php-cs-fixer": "^2.16", 24 | "vimeo/psalm": "^4.1.0" 25 | }, 26 | "license": [ 27 | "BSD-3-Clause" 28 | ], 29 | "homepage": "https://github.com/chocofamilyme/laravel-tarantool", 30 | "authors": [ 31 | { 32 | "name": "IT Rahmet App", 33 | "homepage": "https://rahmetapp.kz/" 34 | } 35 | ], 36 | "autoload": { 37 | "psr-4": { 38 | "Chocofamily\\Tarantool\\": "src/Tarantool/" 39 | } 40 | }, 41 | "extra": { 42 | "laravel": { 43 | "providers": [ 44 | "Chocofamily\\Tarantool\\ServiceProvider" 45 | ] 46 | } 47 | }, 48 | "scripts": { 49 | "test": [ 50 | "@test:style", 51 | "@test:psalm" 52 | ], 53 | "test:style": "@php vendor/bin/php-cs-fixer", 54 | "test:psalm": "@php vendor/bin/psalm" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Tarantool/Connection.php: -------------------------------------------------------------------------------- 1 | config = $config; 34 | $dsn = $this->getDsn($config); 35 | 36 | $connection = $this->createConnection($dsn); 37 | 38 | $this->setClient($connection); 39 | 40 | $this->useDefaultPostProcessor(); 41 | $this->useDefaultSchemaGrammar(); 42 | $this->useDefaultQueryGrammar(); 43 | } 44 | 45 | /** 46 | * Create a new Tarantool connection. 47 | * 48 | * @param string $dsn 49 | * @return TarantoolClient 50 | */ 51 | protected function createConnection(string $dsn) 52 | { 53 | return TarantoolClient::fromDsn($dsn); 54 | } 55 | 56 | /** 57 | * Begin a fluent query against a database table. 58 | * 59 | * @param \Closure|\Illuminate\Database\Query\Builder|string $table 60 | * @param string|null $as 61 | * @return \Illuminate\Database\Query\Builder 62 | */ 63 | public function table($table, $as = null) 64 | { 65 | return $this->query()->from($table); 66 | } 67 | 68 | /** 69 | * @param string $query 70 | * @param array $bindings 71 | * @param bool $useReadPdo 72 | * @return \Generator 73 | * @throws \Exception 74 | */ 75 | public function cursor($query, $bindings = [], $useReadPdo = true) 76 | { 77 | /** @var SqlQueryResult $queryResult */ 78 | $queryResult = $this->run($query, $bindings, function () { 79 | }); 80 | 81 | $metaData = $queryResult->getMetadata(); 82 | 83 | array_walk_recursive($metaData, function (&$value) { 84 | $value = strtolower($value); 85 | }); 86 | 87 | $result = new SqlQueryResult($queryResult->getData(), $metaData); 88 | 89 | return $result->getIterator(); 90 | } 91 | 92 | /** 93 | * Get a new query builder instance. 94 | * 95 | * @return \Illuminate\Database\Query\Builder 96 | */ 97 | public function query() 98 | { 99 | return new Builder($this, $this->getDefaultQueryGrammar(), $this->getDefaultPostProcessor()); 100 | } 101 | 102 | /** 103 | * {@inheritdoc} 104 | */ 105 | public function getSchemaBuilder() 106 | { 107 | return new SchemaBuilder($this); 108 | } 109 | 110 | /** 111 | * @param $connection 112 | * 113 | * @return self 114 | */ 115 | public function setClient(TarantoolClient $connection): self 116 | { 117 | $this->connection = $connection; 118 | 119 | return $this; 120 | } 121 | 122 | /** 123 | * return Tarantool object. 124 | * 125 | * @return TarantoolClient 126 | */ 127 | public function getClient() 128 | { 129 | return $this->connection; 130 | } 131 | 132 | /** 133 | * {@inheritdoc} 134 | */ 135 | public function getDatabaseName() 136 | { 137 | return $this->config['database']; 138 | } 139 | 140 | /** 141 | * {@inheritdoc} 142 | */ 143 | public function getDriverName() 144 | { 145 | return 'tarantool'; 146 | } 147 | 148 | /** 149 | * {@inheritdoc} 150 | */ 151 | protected function getDefaultPostProcessor() 152 | { 153 | return new QProcessor(); 154 | } 155 | 156 | /** 157 | * {@inheritdoc} 158 | */ 159 | protected function getDefaultQueryGrammar() 160 | { 161 | return new QGrammar(); 162 | } 163 | 164 | /** 165 | * {@inheritdoc} 166 | */ 167 | protected function getDefaultSchemaGrammar() 168 | { 169 | return new SGrammar(); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/Tarantool/Eloquent/Model.php: -------------------------------------------------------------------------------- 1 | aggregate = compact('function', 'columns'); 21 | if (empty($this->groups)) { 22 | $this->orders = []; 23 | $this->bindings['order'] = []; 24 | } 25 | 26 | return $this; 27 | } 28 | 29 | /** 30 | * Execute an aggregate function on the database. 31 | * 32 | * @param string $function 33 | * @param array $columns 34 | * @return mixed 35 | */ 36 | public function aggregate($function, $columns = ['*']) 37 | { 38 | $results = $this->cloneWithout($this->unions ? [] : ['columns']) 39 | ->cloneWithoutBindings($this->unions ? [] : ['select']) 40 | ->setAggregate($function, $columns) 41 | ->get($columns); 42 | 43 | if (! $results->isEmpty() && ! empty($results[0])) { 44 | return array_change_key_case((array) $results[0])['aggregate']; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Tarantool/Query/Grammar.php: -------------------------------------------------------------------------------- 1 | map(function ($segment, $key) use ($segments) { 42 | if (count($segments) > 1) { 43 | if ($key == 0) { 44 | return $this->wrapTable($segment); 45 | } else { 46 | return strtoupper($this->addQuotes($segment)); 47 | } 48 | } else { 49 | return $this->wrapValue($segment); 50 | } 51 | })->implode('.'); 52 | } 53 | 54 | /** 55 | * Wrap a single string in keyword identifiers. 56 | * 57 | * @param string $value 58 | * @return string 59 | */ 60 | protected function wrapValue($value) 61 | { 62 | if ($value === '*') { 63 | return $value; 64 | } 65 | 66 | if (in_array($value, $this->reservedWords)) { 67 | return $this->addQuotes($value); 68 | } 69 | 70 | return str_replace('"', '""', $value); 71 | } 72 | 73 | protected function addQuotes(string $string): string 74 | { 75 | if ($string === '*') { 76 | return $string; 77 | } 78 | 79 | return '"'.str_replace('"', '""', $string).'"'; 80 | } 81 | 82 | /** 83 | * Wrap a single string in keyword identifiers. 84 | * 85 | * @param string $value 86 | * @return string 87 | */ 88 | public function wrapTable($value) 89 | { 90 | if ($this->isExpression($value)) { 91 | return parent::wrapTable($value); 92 | } 93 | return '"'.str_replace('"', '""', strtoupper($value)).'"'; 94 | } 95 | 96 | /** 97 | * Convert an array of column names into a delimited string. 98 | * 99 | * @param array $columns 100 | * @return string 101 | */ 102 | public function columnizeCustom(array $columns): string 103 | { 104 | $wrappedColumns = array_map([$this, 'wrap'], $columns); 105 | array_walk($wrappedColumns, function (&$x) { 106 | $x = Str::contains($x, '"') ? $x : '"'.$x.'"'; 107 | }); 108 | 109 | return implode(', ', $wrappedColumns); 110 | } 111 | 112 | /** 113 | * Compile an insert statement into SQL. 114 | * 115 | * @param \Illuminate\Database\Query\Builder $query 116 | * @param array $values 117 | * @return string 118 | */ 119 | public function compileInsert(Builder $query, array $values) 120 | { 121 | // Essentially we will force every insert to be treated as a batch insert which 122 | // simply makes creating the SQL easier for us since we can utilize the same 123 | // basic routine regardless of an amount of records given to us to insert. 124 | $table = $this->wrapTable($query->from); 125 | 126 | if (! is_array(reset($values))) { 127 | $values = [$values]; 128 | } 129 | 130 | $columns = $this->columnizeCustom(array_keys(reset($values))); 131 | 132 | // We need to build a list of parameter place-holders of values that are bound 133 | // to the query. Each insert should have the exact same amount of parameter 134 | // bindings so we will loop through the record and parameterize them all. 135 | $parameters = collect($values)->map(function ($record) { 136 | return '('.$this->parameterize($record).')'; 137 | })->implode(', '); 138 | 139 | return "insert into $table ($columns) values $parameters"; 140 | } 141 | 142 | /** 143 | * overrides default wrapUnion function with removing parentheses on union subquery 144 | * that is how tarantool union works 145 | * 146 | * @param string $sql 147 | * @return string 148 | */ 149 | protected function wrapUnion($sql) 150 | { 151 | return $sql; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/Tarantool/Query/Processor.php: -------------------------------------------------------------------------------- 1 | getConnection()->insert($sql, $values); 26 | $id = $result->getAutoincrementIds()[0]; 27 | 28 | return is_numeric($id) ? (int) $id : $id; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Tarantool/Schema/Grammar.php: -------------------------------------------------------------------------------- 1 | $column) { 64 | if (! $primaryKeyExist) { 65 | $searchPM = strripos($column, 'PRIMARY KEY'); 66 | if ($searchPM != false) { 67 | $primaryKeyExist = true; 68 | } 69 | } 70 | 71 | if (is_bool($idColumnIndex) && $idColumnIndex === false) { 72 | $searchID = strripos($column, '"id"'); 73 | if ($searchID !== false) { 74 | $idColumnIndex = $index; 75 | } 76 | } 77 | 78 | if (! $autoIncrementExist) { 79 | $searchAutoIncrement = strripos($column, 'autoincrement'); 80 | if ($searchAutoIncrement !== false) { 81 | $autoIncrementExist = true; 82 | } 83 | } 84 | } 85 | 86 | if (is_int($idColumnIndex) and $idColumnIndex !== false) { 87 | $textToAdd = ''; 88 | 89 | if ($primaryKeyExist === false) { 90 | $textToAdd = $textToAdd.' PRIMARY KEY'; 91 | } 92 | 93 | if ($autoIncrementExist === false) { 94 | $textToAdd = $textToAdd.' AUTOINCREMENT'; 95 | } 96 | 97 | $columns[$idColumnIndex] = $columns[$idColumnIndex].$textToAdd; 98 | } 99 | } 100 | 101 | return implode(', ', $columns); 102 | } 103 | 104 | /** 105 | * Compile a create table command. 106 | * 107 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 108 | * @param \Illuminate\Support\Fluent $command 109 | * @param Connection $connection 110 | * @return string 111 | */ 112 | public function compileCreate(Blueprint $blueprint, Fluent $command, Connection $connection) 113 | { 114 | $columns = $this->autoAddPrimaryKey($this->getColumns($blueprint)); 115 | $sql = 'CREATE TABLE IF NOT EXISTS '.$this->wrapTable($blueprint)." ($columns)"; 116 | 117 | return $sql; 118 | } 119 | 120 | /** 121 | * Compile a drop table command. 122 | * 123 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 124 | * @param \Illuminate\Support\Fluent $command 125 | * @return string 126 | */ 127 | public function compileDrop(Blueprint $blueprint, Fluent $command) 128 | { 129 | return 'drop table '.$this->wrapTable($blueprint); 130 | } 131 | 132 | /** 133 | * Compile a primary key command. 134 | * 135 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 136 | * @param \Illuminate\Support\Fluent $command 137 | * @return string 138 | */ 139 | public function compilePrimary(Blueprint $blueprint, Fluent $command) 140 | { 141 | $command->name(null); 142 | 143 | return $this->compileKey($blueprint, $command, 'PRIMARY KEY'); 144 | } 145 | 146 | /** 147 | * Compile a unique key command. 148 | * 149 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 150 | * @param \Illuminate\Support\Fluent $command 151 | * @return string 152 | */ 153 | public function compileUnique(Blueprint $blueprint, Fluent $command) 154 | { 155 | $columns = $this->columnize($command->columns); 156 | $table = $this->wrapTable($blueprint); 157 | 158 | return 'CREATE UNIQUE INDEX '.strtoupper(substr($command->index, 0, 31))." ON {$table} ($columns)"; 159 | } 160 | 161 | /** 162 | * Compile a plain index key command. 163 | * 164 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 165 | * @param \Illuminate\Support\Fluent $command 166 | * @return string 167 | */ 168 | public function compileIndex(Blueprint $blueprint, Fluent $command) 169 | { 170 | $columns = $this->columnize($command->columns); 171 | $table = $this->wrapTable($blueprint); 172 | 173 | return 'CREATE INDEX '.strtoupper(substr($command->index, 0, 31))." ON {$table} ($columns)"; 174 | } 175 | 176 | /** 177 | * Compile an index creation command. 178 | * 179 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 180 | * @param \Illuminate\Support\Fluent $command 181 | * @param string $type 182 | * @return string 183 | */ 184 | protected function compileKey(Blueprint $blueprint, Fluent $command, $type) 185 | { 186 | $columns = $this->columnize($command->columns); 187 | $table = $this->wrapTable($blueprint); 188 | 189 | return "alter table {$table} add {$type} ($columns)"; 190 | } 191 | 192 | /** 193 | * Compile a foreign key command. 194 | * 195 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 196 | * @param \Illuminate\Support\Fluent $command 197 | * @return string 198 | */ 199 | /* 200 | public function compileForeign(Blueprint $blueprint, Fluent $command) 201 | { 202 | $table = $this->wrapTable($blueprint); 203 | $on = $this->wrapTable($command->on); 204 | // We need to prepare several of the elements of the foreign key definition 205 | // before we can create the SQL, such as wrapping the tables and convert 206 | // an array of columns to comma-delimited strings for the SQL queries. 207 | $columns = $this->columnize($command->columns); 208 | $onColumns = $this->columnize((array) $command->references); 209 | $sql = "alter table {$table} add constraint ".strtoupper(substr($command->index, 0, 31))." "; 210 | $sql .= "foreign key ({$columns}) references {$on} ({$onColumns})"; 211 | // Once we have the basic foreign key creation statement constructed we can 212 | // build out the syntax for what should happen on an update or delete of 213 | // the affected columns, which will get something like "cascade", etc. 214 | if ( ! is_null($command->onDelete)) 215 | { 216 | $sql .= " on delete {$command->onDelete}"; 217 | } 218 | if ( ! is_null($command->onUpdate)) 219 | { 220 | $sql .= " on update {$command->onUpdate}"; 221 | } 222 | return $sql; 223 | } 224 | */ 225 | 226 | /** 227 | * Compile a drop foreign key command. 228 | * 229 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 230 | * @param \Illuminate\Support\Fluent $command 231 | * @return string 232 | */ 233 | public function compileDropForeign(Blueprint $blueprint, Fluent $command) 234 | { 235 | $table = $this->wrapTable($blueprint); 236 | 237 | return "alter table {$table} drop constraint {$command->index}"; 238 | } 239 | 240 | /** 241 | * Get the SQL for a nullable column modifier. 242 | * 243 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 244 | * @param \Illuminate\Support\Fluent $column 245 | * @return string|null 246 | */ 247 | protected function modifyNullable(Blueprint $blueprint, Fluent $column) 248 | { 249 | return $column->nullable ? '' : ' not null'; 250 | } 251 | 252 | /** 253 | * Get the SQL for a default column modifier. 254 | * 255 | * @param \Illuminate\Database\Schema\Blueprint $blueprint 256 | * @param \Illuminate\Support\Fluent $column 257 | * @return string|null 258 | */ 259 | protected function modifyDefault(Blueprint $blueprint, Fluent $column) 260 | { 261 | if (! is_null($column->default)) { 262 | return ' default '.$this->getDefaultValue($column->default); 263 | } 264 | 265 | return null; 266 | } 267 | 268 | /** 269 | * Create the column definition for a char type. 270 | * 271 | * @param \Illuminate\Support\Fluent $column 272 | * @return string 273 | */ 274 | protected function typeChar(Fluent $column) 275 | { 276 | return 'TEXT'; 277 | } 278 | 279 | /** 280 | * Create the column definition for a string type. 281 | * 282 | * @param \Illuminate\Support\Fluent $column 283 | * @return string 284 | */ 285 | protected function typeString(Fluent $column) 286 | { 287 | return 'VARCHAR ('.$column->length.')'; 288 | } 289 | 290 | /** 291 | * Create the column definition for a text type. 292 | * 293 | * @param \Illuminate\Support\Fluent $column 294 | * @return string 295 | */ 296 | protected function typeText(Fluent $column) 297 | { 298 | return 'TEXT'; 299 | } 300 | 301 | /** 302 | * Create the column definition for a medium text type. 303 | * 304 | * @param \Illuminate\Support\Fluent $column 305 | * @return string 306 | */ 307 | protected function typeMediumText(Fluent $column) 308 | { 309 | return 'TEXT'; 310 | } 311 | 312 | /** 313 | * Create the column definition for a long text type. 314 | * 315 | * @param \Illuminate\Support\Fluent $column 316 | * @return string 317 | */ 318 | protected function typeLongText(Fluent $column) 319 | { 320 | return 'TEXT'; 321 | } 322 | 323 | /** 324 | * Create the column definition for a integer type. 325 | * 326 | * @param \Illuminate\Support\Fluent $column 327 | * @return string 328 | */ 329 | protected function typeInteger(Fluent $column) 330 | { 331 | return 'INTEGER'; 332 | } 333 | 334 | /** 335 | * Create the column definition for a big integer type. 336 | * 337 | * @param \Illuminate\Support\Fluent $column 338 | * @return string 339 | */ 340 | protected function typeBigInteger(Fluent $column) 341 | { 342 | return 'INTEGER'; 343 | } 344 | 345 | /** 346 | * Create the column definition for a medium integer type. 347 | * 348 | * @param \Illuminate\Support\Fluent $column 349 | * @return string 350 | */ 351 | protected function typeMediumInteger(Fluent $column) 352 | { 353 | return 'INTEGER'; 354 | } 355 | 356 | /** 357 | * Create the column definition for a tiny integer type. 358 | * 359 | * @param \Illuminate\Support\Fluent $column 360 | * @return string 361 | */ 362 | protected function typeTinyInteger(Fluent $column) 363 | { 364 | return 'INTEGER'; 365 | } 366 | 367 | /** 368 | * Create the column definition for a small integer type. 369 | * 370 | * @param \Illuminate\Support\Fluent $column 371 | * @return string 372 | */ 373 | protected function typeSmallInteger(Fluent $column) 374 | { 375 | return 'INTEGER'; 376 | } 377 | 378 | /** 379 | * Create the column definition for a float type. 380 | * 381 | * @param \Illuminate\Support\Fluent $column 382 | * @return string 383 | */ 384 | protected function typeFloat(Fluent $column) 385 | { 386 | return 'NUMBER'; 387 | } 388 | 389 | /** 390 | * Create the column definition for a double type. 391 | * 392 | * @param \Illuminate\Support\Fluent $column 393 | * @return string 394 | */ 395 | protected function typeDouble(Fluent $column) 396 | { 397 | return 'NUMBER'; 398 | } 399 | 400 | /** 401 | * Create the column definition for a decimal type. 402 | * 403 | * @param \Illuminate\Support\Fluent $column 404 | * @return string 405 | */ 406 | protected function typeDecimal(Fluent $column) 407 | { 408 | return 'NUMBER'; 409 | } 410 | 411 | /** 412 | * Create the column definition for a boolean type. 413 | * 414 | * @param \Illuminate\Support\Fluent $column 415 | * @return string 416 | */ 417 | protected function typeBoolean(Fluent $column) 418 | { 419 | return 'SCALAR'; 420 | } 421 | 422 | /** 423 | * Create the column definition for an enum type. 424 | * 425 | * @param \Illuminate\Support\Fluent $column 426 | * @return string 427 | */ 428 | protected function typeEnum(Fluent $column) 429 | { 430 | return 'TEXT'; 431 | } 432 | 433 | /** 434 | * Create the column definition for a json type. 435 | * 436 | * @param \Illuminate\Support\Fluent $column 437 | * @return string 438 | */ 439 | protected function typeJson(Fluent $column) 440 | { 441 | return 'TEXT'; 442 | } 443 | 444 | /** 445 | * Create the column definition for a date type. 446 | * 447 | * @param \Illuminate\Support\Fluent $column 448 | * @return string 449 | */ 450 | protected function typeDate(Fluent $column) 451 | { 452 | return 'VARCHAR (10)'; 453 | } 454 | 455 | /** 456 | * Create the column definition for a date-time type. 457 | * 458 | * @param \Illuminate\Support\Fluent $column 459 | * @return string 460 | */ 461 | protected function typeDateTime(Fluent $column) 462 | { 463 | return 'VARCHAR (30)'; 464 | } 465 | 466 | /** 467 | * Create the column definition for a time type. 468 | * 469 | * @param \Illuminate\Support\Fluent $column 470 | * @return string 471 | */ 472 | protected function typeTime(Fluent $column) 473 | { 474 | return 'VARCHAR (10)'; 475 | } 476 | 477 | /** 478 | * Create the column definition for a timestamp type. 479 | * 480 | * @param \Illuminate\Support\Fluent $column 481 | * @return string 482 | */ 483 | protected function typeTimestamp(Fluent $column) 484 | { 485 | return 'VARCHAR (200)'; 486 | } 487 | 488 | /** 489 | * Create the column definition for a binary type. 490 | * 491 | * @param \Illuminate\Support\Fluent $column 492 | * @return string 493 | */ 494 | protected function typeBinary(Fluent $column) 495 | { 496 | return 'SCALAR'; 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /src/Tarantool/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->get('db')); 18 | Model::setEventDispatcher($this->app->get('events')); 19 | } 20 | 21 | /** 22 | * Register the service provider. 23 | */ 24 | public function register() 25 | { 26 | // Add database driver. 27 | $this->app->resolving('db', function ($db) { 28 | $db->extend('tarantool', function ($config, $name) { 29 | $config['name'] = $name; 30 | 31 | return new Connection($config); 32 | }); 33 | }); 34 | /* 35 | // Add connector for queue support. 36 | $this->app->resolving('queue', function ($queue) { 37 | $queue->addConnector('tarantool', function () { 38 | return new TarantoolQueue($this->app['db']); 39 | }); 40 | }); 41 | */ 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Tarantool/Traits/Dsn.php: -------------------------------------------------------------------------------- 1 | hasDsnString($config) 16 | ? $this->getDsnString($config) 17 | : $this->getHostDsn($config); 18 | } 19 | 20 | /** 21 | * Determine if the given configuration array has a dsn string. 22 | * 23 | * @param array $config 24 | * @return bool 25 | */ 26 | protected function hasDsnString(array $config) 27 | { 28 | return isset($config['dsn']) && ! empty($config['dsn']); 29 | } 30 | 31 | /** 32 | * Get the DSN string form configuration. 33 | * 34 | * @param array $config 35 | * @return string 36 | */ 37 | protected function getDsnString(array $config) 38 | { 39 | return $config['dsn']; 40 | } 41 | 42 | /** 43 | * Get the DSN string for a host / port configuration. 44 | * 45 | * @param array $config 46 | * @return string 47 | */ 48 | protected function getHostDsn(array $config) 49 | { 50 | $host = $config['host']; 51 | 52 | if (! empty($config['port']) && strpos($host, ':') === false) { 53 | $host = $host.':'.$config['port']; 54 | } 55 | 56 | $auth = $config['username'].':'.$config['password']; 57 | 58 | $options = isset($config['options']) && ! empty($config['options']) ? http_build_query($config['options'], '', '&') : null; 59 | 60 | $connType = isset($config['type']) && ! empty($config['type']) ? $config['type'] : 'tcp'; 61 | 62 | return $connType.'://'.$auth.'@'.$host.($options ? '/?'.$options : ''); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Tarantool/Traits/Helper.php: -------------------------------------------------------------------------------- 1 | executeQuery($query, $bindings, $useReadPdo); 28 | 29 | return $this->getDataWithKeys($result); 30 | } 31 | 32 | /** 33 | * Run an insert statement against the database. 34 | * 35 | * @param string $query 36 | * @param array $bindings 37 | * @return array 38 | * @psalm-suppress ImplementedReturnTypeMismatch 39 | */ 40 | public function insert($query, $bindings = []) 41 | { 42 | return $this->executeQuery($query, $bindings); 43 | } 44 | 45 | /** 46 | * Run an update statement against the database. 47 | * 48 | * @param string $query 49 | * @param array $bindings 50 | * @return array 51 | * @psalm-suppress ImplementedReturnTypeMismatch 52 | */ 53 | public function update($query, $bindings = []) 54 | { 55 | return $this->executeQuery($query, $bindings); 56 | } 57 | 58 | /** 59 | * Run a delete statement against the database. 60 | * 61 | * @param string $query 62 | * @param array $bindings 63 | * @return int 64 | */ 65 | public function delete($query, $bindings = []) 66 | { 67 | /** @var SqlQueryResult $result */ 68 | $result = $this->executeQuery($query, $bindings); 69 | 70 | return (int) ($result->count() !== 0); 71 | } 72 | 73 | /** 74 | * Run a raw, unprepared query against the PDO connection. 75 | * 76 | * @param string $query 77 | * @return bool 78 | */ 79 | public function unprepared($query) 80 | { 81 | $class = $this; 82 | $client = $this->getClient(); 83 | 84 | return $this->run($query, [], function ($query) use ($class, $client) { 85 | if ($this->pretending()) { 86 | return true; 87 | } 88 | $this->recordsHaveBeenModified( 89 | $change = $class->runQuery($client, $query, []) !== false 90 | ); 91 | 92 | return $change; 93 | }); 94 | } 95 | 96 | /** 97 | * Run query. 98 | * 99 | * @param string $query 100 | * @param array $bindings 101 | * @param bool $useReadPdo 102 | * @return array 103 | */ 104 | public function executeQuery(string $query, array $bindings, bool $useReadPdo = false) 105 | { 106 | $class = $this; 107 | $client = $this->getClient(); 108 | 109 | return $this->run($query, $bindings, function ($query, $bindings) use ($class, $client) { 110 | if ($this->pretending()) { 111 | return []; 112 | } 113 | 114 | return $class->runQuery($client, $query, $bindings); 115 | }); 116 | } 117 | 118 | /** 119 | * Run a SQL statement. 120 | * 121 | * @param string $query 122 | * @param array $bindings 123 | * @param \Closure $callback 124 | * @return mixed 125 | * 126 | * @throws QueryException 127 | */ 128 | protected function runQueryCallback($query, $bindings, Closure $callback) 129 | { 130 | // To execute the statement, we'll simply call the callback, which will actually 131 | // run the SQL against the PDO connection. Then we can calculate the time it 132 | // took to execute and log the query SQL, bindings and time in our memory. 133 | try { 134 | $result = $this->runQuery($this->getClient(), $query, $bindings); 135 | } 136 | // If an exception occurs when attempting to run a query, we'll format the error 137 | // message to include the bindings with SQL, which will make this exception a 138 | // lot more helpful to the developer instead of just the database's errors. 139 | catch (Exception $e) { 140 | throw new QueryException( 141 | $query, $this->prepareBindings($bindings), $e 142 | ); 143 | } 144 | 145 | return $result; 146 | } 147 | 148 | /** 149 | * Runs a SQL query. 150 | * 151 | * @param Client $client 152 | * @param string $sql 153 | * @param array $params 154 | * @param string $operationType 155 | * @return SqlQueryResult|SqlUpdateResult 156 | */ 157 | private function runQuery(Client $client, string $sql, array $params, $operationType = '') 158 | { 159 | if (! $operationType) { 160 | $operationType = $this->getSqlType($sql); 161 | } 162 | 163 | if ($operationType === 'SELECT') { 164 | $result = $client->executeQuery($sql, ...$params); 165 | } else { 166 | $result = $client->executeUpdate($sql, ...$params); 167 | } 168 | 169 | return $result; 170 | } 171 | 172 | /** 173 | * @param SqlQueryResult $result 174 | * @return array 175 | */ 176 | private function getDataWithKeys(SqlQueryResult $result) : array 177 | { 178 | $data = iterator_to_array($result); 179 | 180 | return array_map(static function ($item) { 181 | return array_change_key_case($item, CASE_LOWER); 182 | }, $data); 183 | } 184 | } 185 | --------------------------------------------------------------------------------