Grammar.php
1 month ago
MySqlGrammar.php
1 month ago
PostgresGrammar.php
1 month ago
SQLiteGrammar.php
1 month ago
SqlServerGrammar.php
1 month ago
MySqlGrammar.php
324 lines
| 1 | <?php |
| 2 | |
| 3 | namespace IAWPSCOPED\Illuminate\Database\Query\Grammars; |
| 4 | |
| 5 | use IAWPSCOPED\Illuminate\Database\Query\Builder; |
| 6 | use IAWPSCOPED\Illuminate\Support\Str; |
| 7 | /** @internal */ |
| 8 | class MySqlGrammar extends Grammar |
| 9 | { |
| 10 | /** |
| 11 | * The grammar specific operators. |
| 12 | * |
| 13 | * @var string[] |
| 14 | */ |
| 15 | protected $operators = ['sounds like']; |
| 16 | /** |
| 17 | * Add a "where null" clause to the query. |
| 18 | * |
| 19 | * @param \Illuminate\Database\Query\Builder $query |
| 20 | * @param array $where |
| 21 | * @return string |
| 22 | */ |
| 23 | protected function whereNull(Builder $query, $where) |
| 24 | { |
| 25 | if ($this->isJsonSelector($where['column'])) { |
| 26 | [$field, $path] = $this->wrapJsonFieldAndPath($where['column']); |
| 27 | return '(json_extract(' . $field . $path . ') is null OR json_type(json_extract(' . $field . $path . ')) = \'NULL\')'; |
| 28 | } |
| 29 | return parent::whereNull($query, $where); |
| 30 | } |
| 31 | /** |
| 32 | * Add a "where not null" clause to the query. |
| 33 | * |
| 34 | * @param \Illuminate\Database\Query\Builder $query |
| 35 | * @param array $where |
| 36 | * @return string |
| 37 | */ |
| 38 | protected function whereNotNull(Builder $query, $where) |
| 39 | { |
| 40 | if ($this->isJsonSelector($where['column'])) { |
| 41 | [$field, $path] = $this->wrapJsonFieldAndPath($where['column']); |
| 42 | return '(json_extract(' . $field . $path . ') is not null AND json_type(json_extract(' . $field . $path . ')) != \'NULL\')'; |
| 43 | } |
| 44 | return parent::whereNotNull($query, $where); |
| 45 | } |
| 46 | /** |
| 47 | * Compile a "where fulltext" clause. |
| 48 | * |
| 49 | * @param \Illuminate\Database\Query\Builder $query |
| 50 | * @param array $where |
| 51 | * @return string |
| 52 | */ |
| 53 | public function whereFullText(Builder $query, $where) |
| 54 | { |
| 55 | $columns = $this->columnize($where['columns']); |
| 56 | $value = $this->parameter($where['value']); |
| 57 | $mode = ($where['options']['mode'] ?? []) === 'boolean' ? ' in boolean mode' : ' in natural language mode'; |
| 58 | $expanded = ($where['options']['expanded'] ?? []) && ($where['options']['mode'] ?? []) !== 'boolean' ? ' with query expansion' : ''; |
| 59 | return "match ({$columns}) against (" . $value . "{$mode}{$expanded})"; |
| 60 | } |
| 61 | /** |
| 62 | * Compile the index hints for the query. |
| 63 | * |
| 64 | * @param \Illuminate\Database\Query\Builder $query |
| 65 | * @param \Illuminate\Database\Query\IndexHint $indexHint |
| 66 | * @return string |
| 67 | */ |
| 68 | protected function compileIndexHint(Builder $query, $indexHint) |
| 69 | { |
| 70 | return match ($indexHint->type) { |
| 71 | 'hint' => "use index ({$indexHint->index})", |
| 72 | 'force' => "force index ({$indexHint->index})", |
| 73 | default => "ignore index ({$indexHint->index})", |
| 74 | }; |
| 75 | } |
| 76 | /** |
| 77 | * Compile an insert ignore statement into SQL. |
| 78 | * |
| 79 | * @param \Illuminate\Database\Query\Builder $query |
| 80 | * @param array $values |
| 81 | * @return string |
| 82 | */ |
| 83 | public function compileInsertOrIgnore(Builder $query, array $values) |
| 84 | { |
| 85 | return Str::replaceFirst('insert', 'insert ignore', $this->compileInsert($query, $values)); |
| 86 | } |
| 87 | /** |
| 88 | * Compile a "JSON contains" statement into SQL. |
| 89 | * |
| 90 | * @param string $column |
| 91 | * @param string $value |
| 92 | * @return string |
| 93 | */ |
| 94 | protected function compileJsonContains($column, $value) |
| 95 | { |
| 96 | [$field, $path] = $this->wrapJsonFieldAndPath($column); |
| 97 | return 'json_contains(' . $field . ', ' . $value . $path . ')'; |
| 98 | } |
| 99 | /** |
| 100 | * Compile a "JSON contains key" statement into SQL. |
| 101 | * |
| 102 | * @param string $column |
| 103 | * @return string |
| 104 | */ |
| 105 | protected function compileJsonContainsKey($column) |
| 106 | { |
| 107 | [$field, $path] = $this->wrapJsonFieldAndPath($column); |
| 108 | return 'ifnull(json_contains_path(' . $field . ', \'one\'' . $path . '), 0)'; |
| 109 | } |
| 110 | /** |
| 111 | * Compile a "JSON length" statement into SQL. |
| 112 | * |
| 113 | * @param string $column |
| 114 | * @param string $operator |
| 115 | * @param string $value |
| 116 | * @return string |
| 117 | */ |
| 118 | protected function compileJsonLength($column, $operator, $value) |
| 119 | { |
| 120 | [$field, $path] = $this->wrapJsonFieldAndPath($column); |
| 121 | return 'json_length(' . $field . $path . ') ' . $operator . ' ' . $value; |
| 122 | } |
| 123 | /** |
| 124 | * Compile a "JSON value cast" statement into SQL. |
| 125 | * |
| 126 | * @param string $value |
| 127 | * @return string |
| 128 | */ |
| 129 | public function compileJsonValueCast($value) |
| 130 | { |
| 131 | return 'cast(' . $value . ' as json)'; |
| 132 | } |
| 133 | /** |
| 134 | * Compile the random statement into SQL. |
| 135 | * |
| 136 | * @param string|int $seed |
| 137 | * @return string |
| 138 | */ |
| 139 | public function compileRandom($seed) |
| 140 | { |
| 141 | return 'RAND(' . $seed . ')'; |
| 142 | } |
| 143 | /** |
| 144 | * Compile the lock into SQL. |
| 145 | * |
| 146 | * @param \Illuminate\Database\Query\Builder $query |
| 147 | * @param bool|string $value |
| 148 | * @return string |
| 149 | */ |
| 150 | protected function compileLock(Builder $query, $value) |
| 151 | { |
| 152 | if (!\is_string($value)) { |
| 153 | return $value ? 'for update' : 'lock in share mode'; |
| 154 | } |
| 155 | return $value; |
| 156 | } |
| 157 | /** |
| 158 | * Compile an insert statement into SQL. |
| 159 | * |
| 160 | * @param \Illuminate\Database\Query\Builder $query |
| 161 | * @param array $values |
| 162 | * @return string |
| 163 | */ |
| 164 | public function compileInsert(Builder $query, array $values) |
| 165 | { |
| 166 | if (empty($values)) { |
| 167 | $values = [[]]; |
| 168 | } |
| 169 | return parent::compileInsert($query, $values); |
| 170 | } |
| 171 | /** |
| 172 | * Compile the columns for an update statement. |
| 173 | * |
| 174 | * @param \Illuminate\Database\Query\Builder $query |
| 175 | * @param array $values |
| 176 | * @return string |
| 177 | */ |
| 178 | protected function compileUpdateColumns(Builder $query, array $values) |
| 179 | { |
| 180 | return \IAWPSCOPED\collect($values)->map(function ($value, $key) { |
| 181 | if ($this->isJsonSelector($key)) { |
| 182 | return $this->compileJsonUpdateColumn($key, $value); |
| 183 | } |
| 184 | return $this->wrap($key) . ' = ' . $this->parameter($value); |
| 185 | })->implode(', '); |
| 186 | } |
| 187 | /** |
| 188 | * Compile an "upsert" statement into SQL. |
| 189 | * |
| 190 | * @param \Illuminate\Database\Query\Builder $query |
| 191 | * @param array $values |
| 192 | * @param array $uniqueBy |
| 193 | * @param array $update |
| 194 | * @return string |
| 195 | */ |
| 196 | public function compileUpsert(Builder $query, array $values, array $uniqueBy, array $update) |
| 197 | { |
| 198 | $useUpsertAlias = $query->connection->getConfig('use_upsert_alias'); |
| 199 | $sql = $this->compileInsert($query, $values); |
| 200 | if ($useUpsertAlias) { |
| 201 | $sql .= ' as laravel_upsert_alias'; |
| 202 | } |
| 203 | $sql .= ' on duplicate key update '; |
| 204 | $columns = \IAWPSCOPED\collect($update)->map(function ($value, $key) use($useUpsertAlias) { |
| 205 | if (!\is_numeric($key)) { |
| 206 | return $this->wrap($key) . ' = ' . $this->parameter($value); |
| 207 | } |
| 208 | return $useUpsertAlias ? $this->wrap($value) . ' = ' . $this->wrap('laravel_upsert_alias') . '.' . $this->wrap($value) : $this->wrap($value) . ' = values(' . $this->wrap($value) . ')'; |
| 209 | })->implode(', '); |
| 210 | return $sql . $columns; |
| 211 | } |
| 212 | /** |
| 213 | * Prepare a JSON column being updated using the JSON_SET function. |
| 214 | * |
| 215 | * @param string $key |
| 216 | * @param mixed $value |
| 217 | * @return string |
| 218 | */ |
| 219 | protected function compileJsonUpdateColumn($key, $value) |
| 220 | { |
| 221 | if (\is_bool($value)) { |
| 222 | $value = $value ? 'true' : 'false'; |
| 223 | } elseif (\is_array($value)) { |
| 224 | $value = 'cast(? as json)'; |
| 225 | } else { |
| 226 | $value = $this->parameter($value); |
| 227 | } |
| 228 | [$field, $path] = $this->wrapJsonFieldAndPath($key); |
| 229 | return "{$field} = json_set({$field}{$path}, {$value})"; |
| 230 | } |
| 231 | /** |
| 232 | * Compile an update statement without joins into SQL. |
| 233 | * |
| 234 | * @param \Illuminate\Database\Query\Builder $query |
| 235 | * @param string $table |
| 236 | * @param string $columns |
| 237 | * @param string $where |
| 238 | * @return string |
| 239 | */ |
| 240 | protected function compileUpdateWithoutJoins(Builder $query, $table, $columns, $where) |
| 241 | { |
| 242 | $sql = parent::compileUpdateWithoutJoins($query, $table, $columns, $where); |
| 243 | if (!empty($query->orders)) { |
| 244 | $sql .= ' ' . $this->compileOrders($query, $query->orders); |
| 245 | } |
| 246 | if (isset($query->limit)) { |
| 247 | $sql .= ' ' . $this->compileLimit($query, $query->limit); |
| 248 | } |
| 249 | return $sql; |
| 250 | } |
| 251 | /** |
| 252 | * Prepare the bindings for an update statement. |
| 253 | * |
| 254 | * Booleans, integers, and doubles are inserted into JSON updates as raw values. |
| 255 | * |
| 256 | * @param array $bindings |
| 257 | * @param array $values |
| 258 | * @return array |
| 259 | */ |
| 260 | public function prepareBindingsForUpdate(array $bindings, array $values) |
| 261 | { |
| 262 | $values = \IAWPSCOPED\collect($values)->reject(function ($value, $column) { |
| 263 | return $this->isJsonSelector($column) && \is_bool($value); |
| 264 | })->map(function ($value) { |
| 265 | return \is_array($value) ? \json_encode($value) : $value; |
| 266 | })->all(); |
| 267 | return parent::prepareBindingsForUpdate($bindings, $values); |
| 268 | } |
| 269 | /** |
| 270 | * Compile a delete query that does not use joins. |
| 271 | * |
| 272 | * @param \Illuminate\Database\Query\Builder $query |
| 273 | * @param string $table |
| 274 | * @param string $where |
| 275 | * @return string |
| 276 | */ |
| 277 | protected function compileDeleteWithoutJoins(Builder $query, $table, $where) |
| 278 | { |
| 279 | $sql = parent::compileDeleteWithoutJoins($query, $table, $where); |
| 280 | // When using MySQL, delete statements may contain order by statements and limits |
| 281 | // so we will compile both of those here. Once we have finished compiling this |
| 282 | // we will return the completed SQL statement so it will be executed for us. |
| 283 | if (!empty($query->orders)) { |
| 284 | $sql .= ' ' . $this->compileOrders($query, $query->orders); |
| 285 | } |
| 286 | if (isset($query->limit)) { |
| 287 | $sql .= ' ' . $this->compileLimit($query, $query->limit); |
| 288 | } |
| 289 | return $sql; |
| 290 | } |
| 291 | /** |
| 292 | * Wrap a single string in keyword identifiers. |
| 293 | * |
| 294 | * @param string $value |
| 295 | * @return string |
| 296 | */ |
| 297 | protected function wrapValue($value) |
| 298 | { |
| 299 | return $value === '*' ? $value : '`' . \str_replace('`', '``', $value) . '`'; |
| 300 | } |
| 301 | /** |
| 302 | * Wrap the given JSON selector. |
| 303 | * |
| 304 | * @param string $value |
| 305 | * @return string |
| 306 | */ |
| 307 | protected function wrapJsonSelector($value) |
| 308 | { |
| 309 | [$field, $path] = $this->wrapJsonFieldAndPath($value); |
| 310 | return 'json_unquote(json_extract(' . $field . $path . '))'; |
| 311 | } |
| 312 | /** |
| 313 | * Wrap the given JSON selector for boolean values. |
| 314 | * |
| 315 | * @param string $value |
| 316 | * @return string |
| 317 | */ |
| 318 | protected function wrapJsonBooleanSelector($value) |
| 319 | { |
| 320 | [$field, $path] = $this->wrapJsonFieldAndPath($value); |
| 321 | return 'json_extract(' . $field . $path . ')'; |
| 322 | } |
| 323 | } |
| 324 |