diff --git a/src/Connection.php b/src/Connection.php index 917ea8c..3865927 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -3,6 +3,8 @@ namespace DesignMyNight\Elasticsearch; use Closure; +use DesignMyNight\Elasticsearch\Exceptions\BulkInsertQueryException; +use DesignMyNight\Elasticsearch\Exceptions\QueryException; use Elasticsearch\ClientBuilder; use Illuminate\Database\Connection as BaseConnection; use Illuminate\Database\Events\QueryExecuted; @@ -282,17 +284,24 @@ public function scroll(string $scrollId, string $scrollTimeout = '30s', int $lim /** * Run an insert statement against the database. * - * @param array $params - * @param array $bindings + * @param array $params + * @param array $bindings * @return bool + * @throws BulkInsertQueryException */ public function insert($params, $bindings = []) { - return $this->run( + $result = $this->run( $this->addClientParams($params), $bindings, Closure::fromCallable([$this->connection, 'bulk']) ); + + if (!empty($result['errors'])) { + throw new BulkInsertQueryException($result); + } + + return true; } /** @@ -378,7 +387,7 @@ public function prepareBindings(array $bindings) * @param \Closure $callback * @return mixed * - * @throws \DesignMyNight\Elasticsearch\QueryException + * @throws \DesignMyNight\Elasticsearch\Exceptions\QueryException */ protected function runQueryCallback($query, $bindings, Closure $callback) { diff --git a/src/Exceptions/BulkInsertQueryException.php b/src/Exceptions/BulkInsertQueryException.php new file mode 100644 index 0000000..9e6ab64 --- /dev/null +++ b/src/Exceptions/BulkInsertQueryException.php @@ -0,0 +1,61 @@ +formatMessage($queryResult), 400); + } + + /** + * Format the error message. + * + * Takes the first {$this->errorLimit} bulk issues and concatenates them to a single string message + * + * @param array $result + * @return string + */ + private function formatMessage(array $result): string + { + $message = []; + + $items = array_filter($result['items'] ?? [], function(array $item): bool { + $itemAction = reset($item); + + return $itemAction && !empty($itemAction['error']); + }); + + $items = array_values($items); + + $totalErrors = count($items); + + // reduce to max limit + $items = array_splice($items, 0, $this->errorLimit); + + $message[] = 'Bulk Insert Errors (' . 'Showing ' . count($items) . ' of ' . $totalErrors . '):'; + + foreach ($items as $item) { + $itemAction = reset($item); + + $itemError = array_merge([ + '_id' => $itemAction['_id'], + 'reason' => $itemAction['error']['reason'], + ], $itemAction['error']['caused_by'] ?? []); + + $message[] = implode(': ', $itemError); + } + + return implode(PHP_EOL, $message); + } +} diff --git a/src/QueryException.php b/src/Exceptions/QueryException.php similarity index 89% rename from src/QueryException.php rename to src/Exceptions/QueryException.php index 0811f32..6ca4193 100644 --- a/src/QueryException.php +++ b/src/Exceptions/QueryException.php @@ -1,6 +1,6 @@ wheres[] = [ - 'column' => $column, - 'bounds' => $bounds, - 'type' => 'GeoBoundsIn', + 'column' => $column, + 'bounds' => $bounds, + 'type' => 'GeoBoundsIn', 'boolean' => 'and', - 'not' => false + 'not' => false, ]; return $this; @@ -159,15 +160,15 @@ public function whereGeoBoundsIn($column, array $bounds): self /** * Add a "where date" statement to the query. * - * @param string $column - * @param string $operator - * @param mixed $value - * @param string $boolean + * @param string $column + * @param string $operator + * @param mixed $value + * @param string $boolean * @return \Illuminate\Database\Query\Builder|static */ public function whereDate($column, $operator, $value = null, $boolean = 'and', $not = false): self { - list($value, $operator) = $this->prepareValueAndOperator( + [$value, $operator] = $this->prepareValueAndOperator( $value, $operator, func_num_args() == 2 @@ -183,9 +184,9 @@ public function whereDate($column, $operator, $value = null, $boolean = 'and', $ /** * Add a 'nested document' statement to the query. * - * @param string $column - * @param \Illuminate\Database\Query\Builder|static $query - * @param string $boolean + * @param string $column + * @param \Illuminate\Database\Query\Builder|static $query + * @param string $boolean * @return self */ public function whereNestedDoc($column, $query, $boolean = 'and'): self @@ -204,8 +205,8 @@ public function whereNestedDoc($column, $query, $boolean = 'and'): self /** * Add a 'must not' statement to the query. * - * @param \Illuminate\Database\Query\Builder|static $query - * @param string $boolean + * @param \Illuminate\Database\Query\Builder|static $query + * @param string $boolean * @return self */ public function whereNot($query, $boolean = 'and'): self @@ -222,10 +223,10 @@ public function whereNot($query, $boolean = 'and'): self /** * Add a prefix query * - * @param string $column - * @param string $value - * @param string $boolean - * @param boolean $not + * @param string $column + * @param string $value + * @param string $boolean + * @param boolean $not * @return self */ public function whereStartsWith($column, string $value, $boolean = 'and', $not = false): self @@ -240,9 +241,9 @@ public function whereStartsWith($column, string $value, $boolean = 'and', $not = /** * Add a script query * - * @param string $script - * @param array $options - * @param string $boolean + * @param string $script + * @param array $options + * @param string $boolean * @return self */ public function whereScript(string $script, array $options = [], $boolean = 'and'): self @@ -257,10 +258,10 @@ public function whereScript(string $script, array $options = [], $boolean = 'and /** * Add a "where weekday" statement to the query. * - * @param string $column - * @param string $operator - * @param \DateTimeInterface|string $value - * @param string $boolean + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string $value + * @param string $boolean * @return \Illuminate\Database\Query\Builder|static */ public function whereWeekday($column, $operator, $value = null, $boolean = 'and') @@ -281,9 +282,9 @@ public function whereWeekday($column, $operator, $value = null, $boolean = 'and' /** * Add an "or where weekday" statement to the query. * - * @param string $column - * @param string $operator - * @param \DateTimeInterface|string $value + * @param string $column + * @param string $operator + * @param \DateTimeInterface|string $value * @return \Illuminate\Database\Query\Builder|static */ public function orWhereWeekday($column, $operator, $value = null) @@ -300,11 +301,11 @@ public function orWhereWeekday($column, $operator, $value = null) /** * Add a date based (year, month, day, time) statement to the query. * - * @param string $type - * @param string $column - * @param string $operator - * @param mixed $value - * @param string $boolean + * @param string $type + * @param string $column + * @param string $operator + * @param mixed $value + * @param string $boolean * @return $this */ protected function addDateBasedWhere($type, $column, $operator, $value, $boolean = 'and') @@ -333,7 +334,7 @@ protected function addDateBasedWhere($type, $column, $operator, $value, $boolean $script = "doc.{$column}.size() > 0 && doc.{$column}.date.{$dateType} {$operator} params.value"; - $options['params'] = ['value' => (int) $value]; + $options['params'] = ['value' => (int)$value]; $this->wheres[] = compact('script', 'options', 'type', 'boolean'); @@ -343,8 +344,8 @@ protected function addDateBasedWhere($type, $column, $operator, $value, $boolean /** * Add another query builder as a nested where to the query builder. * - * @param \Illuminate\Database\Query\Builder|static $query - * @param string $boolean + * @param \Illuminate\Database\Query\Builder|static $query + * @param string $boolean * @return self */ public function addNestedWhereQuery($query, $boolean = 'and'): self @@ -377,7 +378,7 @@ public function whereWithOptions(...$args): self $this->$method(...$args); - $this->wheres[count($this->wheres) -1]['options'] = $options; + $this->wheres[count($this->wheres) - 1]['options'] = $options; return $this; } @@ -386,8 +387,8 @@ public function whereWithOptions(...$args): self * Add a filter query by calling the required 'where' method * and capturing the added where as a filter * - * @param string $method - * @param array $args + * @param string $method + * @param array $args * @return self */ public function dynamicFilter(string $method, array $args): self @@ -410,9 +411,9 @@ public function dynamicFilter(string $method, array $args): self /** * Add a text search clause to the query. * - * @param string $query - * @param array $options - * @param string $boolean + * @param string $query + * @param array $options + * @param string $boolean * @return self */ public function search($query, $options = [], $boolean = 'and'): self @@ -430,16 +431,16 @@ public function search($query, $options = [], $boolean = 'and'): self /** * Add a type clause to the query. * - * @param string $documentType - * @param string $boolean + * @param string $documentType + * @param string $boolean * @return \Illuminate\Database\Query\Builder|static */ public function whereType($documentType, $boolean = 'and') { $this->wheres[] = [ - 'type' => 'Type', - 'value' => $documentType, - 'boolean' => $boolean + 'type' => 'Type', + 'value' => $documentType, + 'boolean' => $boolean, ]; return $this; @@ -448,62 +449,75 @@ public function whereType($documentType, $boolean = 'and') /** * Add a where parent statement to the query. * - * @param string $documentType - * @param \Closure $callback - * @param array $options - * @param string $boolean + * @param string $documentType + * @param \Closure $callback + * @param array $options + * @param string $boolean * @return \Illuminate\Database\Query\Builder|static */ - public function whereParent(string $documentType, Closure $callback, array $options = [], string $boolean = 'and'): self - { + public function whereParent( + string $documentType, + Closure $callback, + array $options = [], + string $boolean = 'and' + ): self { return $this->whereRelationship('parent', $documentType, $callback, $options, $boolean); } /** * Add a where child statement to the query. * - * @param string $documentType - * @param \Closure $callback - * @param array $options - * @param string $boolean + * @param string $documentType + * @param \Closure $callback + * @param array $options + * @param string $boolean * @return \Illuminate\Database\Query\Builder|static */ - public function whereChild(string $documentType, Closure $callback, array $options = [], string $boolean = 'and'): self - { + public function whereChild( + string $documentType, + Closure $callback, + array $options = [], + string $boolean = 'and' + ): self { return $this->whereRelationship('child', $documentType, $callback, $options, $boolean); } /** * Add a where relationship statement to the query. * - * @param string $relationshipType - * @param string $documentType - * @param \Closure $callback - * @param array $options - * @param string $boolean + * @param string $relationshipType + * @param string $documentType + * @param \Closure $callback + * @param array $options + * @param string $boolean * * @return \Illuminate\Database\Query\Builder|static */ - protected function whereRelationship(string $relationshipType, string $documentType, Closure $callback, array $options = [], string $boolean = 'and'): self - { + protected function whereRelationship( + string $relationshipType, + string $documentType, + Closure $callback, + array $options = [], + string $boolean = 'and' + ): self { call_user_func($callback, $query = $this->newQuery()); $this->wheres[] = [ - 'type' => ucfirst($relationshipType), + 'type' => ucfirst($relationshipType), 'documentType' => $documentType, - 'value' => $query, - 'options' => $options, - 'boolean' => $boolean + 'value' => $query, + 'options' => $options, + 'boolean' => $boolean, ]; return $this; } /** - * @param string $key - * @param string $type - * @param null $args - * @param null $aggregations + * @param string $key + * @param string $type + * @param null $args + * @param null $aggregations * @return self */ public function aggregation($key, $type = null, $args = null, $aggregations = null): self @@ -512,9 +526,9 @@ public function aggregation($key, $type = null, $args = null, $aggregations = nu $aggregation = $key; $this->aggregations[] = [ - 'key' => $aggregation->getKey(), - 'type' => $aggregation->getType(), - 'args' => $aggregation->getArguments(), + 'key' => $aggregation->getKey(), + 'type' => $aggregation->getType(), + 'args' => $aggregation->getArguments(), 'aggregations' => $aggregation($this->newQuery()), ]; @@ -540,9 +554,9 @@ public function aggregation($key, $type = null, $args = null, $aggregations = nu } /** - * @param string $column - * @param int $direction - * @param array $options + * @param string $column + * @param int $direction + * @param array $options * @return self */ public function orderBy($column, $direction = 1, $options = null): self @@ -573,20 +587,20 @@ public function withInnerHits(): self /** * Adds a function score of any type * - * @param string $field - * @param array $options see elastic search docs for options - * @param string $boolean + * @param string $field + * @param array $options see elastic search docs for options + * @param string $boolean * @return self */ public function functionScore($functionType, $options = [], $boolean = 'and'): self { - $where = [ - 'type' => 'FunctionScore', + $where = [ + 'type' => 'FunctionScore', 'function_type' => $functionType, - 'boolean' => $boolean + 'boolean' => $boolean, ]; - $this->wheres[] = array_merge($where, $options); + $this->wheres[] = array_merge($where, $options); return $this; } @@ -606,7 +620,7 @@ public function getAggregationResults(): array /** * Execute the query as a "select" statement. * - * @param array $columns + * @param array $columns * @return \Illuminate\Support\Collection */ public function get($columns = ['*']) @@ -651,7 +665,7 @@ protected function runSelect() /** * Get the count of the total records for the paginator. * - * @param array $columns + * @param array $columns * @return int */ public function getCountForPagination($columns = ['*']) @@ -666,14 +680,15 @@ public function getCountForPagination($columns = ['*']) /** * Run a pagination count query. * - * @param array $columns + * @param array $columns * @return array */ protected function runPaginationCountQuery($columns = ['_id']) { return $this->cloneWithout(['columns', 'orders', 'limit', 'offset']) - ->limit(1) - ->get($columns)->all(); + ->limit(1) + ->get($columns) + ->all(); } /** @@ -738,9 +753,7 @@ public function insert(array $values): bool $values = [$values]; } - $result = $this->connection->insert($this->grammar->compileInsert($this, $values)); - - return empty($result['errors']); + return $this->connection->insert($this->grammar->compileInsert($this, $values)); } /** @@ -751,7 +764,7 @@ public function delete($id = null): bool // If an ID is passed to the method, we will set the where clause to check the // ID to let developers to simply and quickly remove a single row from this // database without manually specifying the "where" clauses on the query. - if (! is_null($id)) { + if (!is_null($id)) { $this->where($this->getKeyName(), '=', $id); }