Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,12 @@

> [中文](README.zh.md) | English

[![Latest Version](https://img.shields.io/packagist/v/ykan/elastickit)](https://packagist.org/packages/ykan/elastickit)
[![Total Downloads](https://img.shields.io/packagist/dt/ykan/elastickit)](https://packagist.org/packages/ykan/elastickit)
[![Tests](https://github.com/ykan821/ElasticKit/actions/workflows/ci.yml/badge.svg)](https://github.com/ykan821/ElasticKit/actions/workflows/ci.yml)
[![PHP](https://img.shields.io/packagist/php-v/ykan/elastickit)](https://packagist.org/packages/ykan/elastickit)
[![License](https://img.shields.io/packagist/l/ykan/elastickit)](https://packagist.org/packages/ykan/elastickit)

A PHP Elasticsearch DSL query builder covering queries, aggregations, CRUD, bulk writes, and zero-downtime rebuilds.

## Integrations

- **[ElasticKit Laravel](https://github.com/ykan821/ElasticKitLaravel)** — Laravel integration
(native pagination, artisan rebuild). `composer require ykan/elastickit-laravel`.

## Installation

```
Expand Down Expand Up @@ -313,10 +306,6 @@ EventDispatcher::listen('search.*', function (Event $e) {
- [Changelog](CHANGELOG.md)
- [Elasticsearch official docs](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html) — query types and parameter reference

## AI-assisted development

This project is developed with AI assistance; core paths and tests are human-reviewed.

## License

MIT
10 changes: 0 additions & 10 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@

> 中文 | [English](README.md)

[![Latest Version](https://img.shields.io/packagist/v/ykan/elastickit)](https://packagist.org/packages/ykan/elastickit)
[![Total Downloads](https://img.shields.io/packagist/dt/ykan/elastickit)](https://packagist.org/packages/ykan/elastickit)
[![Tests](https://github.com/ykan821/ElasticKit/actions/workflows/ci.yml/badge.svg)](https://github.com/ykan821/ElasticKit/actions/workflows/ci.yml)
[![PHP](https://img.shields.io/packagist/php-v/ykan/elastickit)](https://packagist.org/packages/ykan/elastickit)
[![License](https://img.shields.io/packagist/l/ykan/elastickit)](https://packagist.org/packages/ykan/elastickit)

PHP Elasticsearch DSL 查询构建库,覆盖查询、聚合、CRUD、批量写入、零停机重建。

## 集成

- **[ElasticKit Laravel](https://github.com/ykan821/ElasticKitLaravel)** — Laravel 集成(原生分页、artisan 重建)。`composer require ykan/elastickit-laravel`。

## 安装

```
Expand Down Expand Up @@ -312,10 +306,6 @@ EventDispatcher::listen('search.*', function (Event $e) {
- [更新日志](CHANGELOG.md)
- [Elasticsearch 官方文档](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html)——查询类型和参数参考

## AI 辅助开发

本项目使用 AI 辅助开发,核心路径和测试经人工审查。

## License

MIT
6 changes: 6 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@ foreach ($docs as $id => $doc) {
$bulk->flush(); // the tail (< 500)
```

`options()` sets top-level bulk API params (`refresh`, `timeout`, `pipeline`, ...) applied to **every** flush — including the `batchSize()` auto-flush. It persists across flushes; arguments passed to `flush($options)` override them for a single call.

```php
$bulk->options(['refresh' => true, 'timeout' => '30s']);
```

### Error handling

`flush()` throws a `RuntimeException` by default when the response contains errors. Use `onError()` to customize — the callback receives three raw materials and decides what to do:
Expand Down
6 changes: 6 additions & 0 deletions docs/index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@ foreach ($docs as $id => $doc) {
$bulk->flush(); // 尾部(< 500 那批)
```

`options()` 设置顶层 bulk API 参数(`refresh`、`timeout`、`pipeline` 等),对**每次** flush 生效——包括 `batchSize()` 的自动 flush。跨 flush 持久;传给 `flush($options)` 的参数仅在单次调用时覆盖。

```php
$bulk->options(['refresh' => true, 'timeout' => '30s']);
```

### 错误处理

`flush()` 默认在响应包含错误时抛出 `RuntimeException`。用 `onError()` 自定义处理——回调收到三样原材料,自行决定:
Expand Down
34 changes: 31 additions & 3 deletions src/Index/Bulk.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ class Bulk
*/
private int $retryOnConflict = 0;

/**
* Top-level bulk API params (refresh, timeout, ...) applied to every flush.
*
* @var array<string, mixed>
*/
private array $defaultOptions = [];

/**
* @var string|null
*/
Expand Down Expand Up @@ -94,7 +101,7 @@ public function batchSize(int $size): static
* On error the callback receives three tools and decides what to do:
* - $response: the raw ES response (items[] carry per-item status/error);
* - $body: the full original batch in native ES format (successes included);
* - $newbulk: a fresh Bulk bound to the same index and target, for re-send.
* - $newbulk: a fresh Bulk for re-send, inheriting target, options, and retry_on_conflict.
*
* Extract the failures from $body using $response (items[k] matches the k-th
* action), re-enqueue them on $newbulk, and call $newbulk->flush() to retry.
Expand Down Expand Up @@ -123,6 +130,21 @@ public function retryOnConflict(int $count): static
return $this;
}

/**
* Top-level bulk API params applied to every flush, including the auto-flush
* triggered by batchSize(). Persists across flush() calls; flush($options)
* overrides these per-call.
*
* @param array<string, mixed> $options
* @return $this
*/
public function options(array $options): static
{
$this->defaultOptions = $options;

return $this;
}

/**
* Queue an index (create/overwrite) action.
*
Expand Down Expand Up @@ -223,7 +245,8 @@ public function delete(string|int $id): static
* preserved for the caller to retry. Call this at the end of a batch to flush
* the remainder — batchSize() auto-flushes full batches during enqueue.
*
* @param array<string, mixed> $options top-level bulk API params (refresh, timeout, etc)
* @param array<string, mixed> $options top-level bulk API params (refresh, timeout, etc),
* overriding the instance-level options() for this flush only
* @return array<string, mixed>
* @throws \RuntimeException when the response has errors and no handler swallowed them
*/
Expand All @@ -242,7 +265,7 @@ public function flush(array $options = []): array

$start = microtime(true);
$response = $this->index->getClient()->bulk(
array_merge(['body' => $actions], $options)
array_merge(['body' => $actions], $this->defaultOptions, $options)
)->asArray();
$duration = microtime(true) - $start;

Expand All @@ -257,8 +280,13 @@ public function flush(array $options = []): array
// Hand the caller the raw materials: the response, the full body,
// and a fresh Bulk on the same index/target. The caller extracts
// the failures and re-sends them however it likes.
// Inherits passive request settings (target, options, retry_on_conflict)
// so retries reproduce the original request. batchSize/errorHandler stay
// off: auto-flush would disrupt re-enqueueing, a handler could recurse.
$newbulk = new Bulk($this->index);
$newbulk->targetIndex = $this->targetIndex;
$newbulk->defaultOptions = $this->defaultOptions;
$newbulk->retryOnConflict = $this->retryOnConflict;
($this->errorHandler)($response, $actions, $newbulk);
} else {
$json = json_encode($response, JSON_UNESCAPED_UNICODE);
Expand Down
27 changes: 27 additions & 0 deletions tests/Integration/Index/BulkContractTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,33 @@ public function testBatchSizeAutoFlush(): void
$this->assertTrue($index->newDoc('22')->exists());
}

public function testOptionsApplyToAutoFlush(): void
{
// batchSize triggers auto-flush mid-chain; options() must ride along so the
// bulk request carries refresh=true — without it the doc is not yet searchable.
$index = $this->makeIndex();
(new Bulk($index))
->batchSize(1)
->options(['refresh' => true])
->index('40', ['title' => 'refreshed via options']);

// No manual refreshIndex() — proves refresh reached the auto-flushed bulk.
$docs = $index->newQuery()->match('title', 'refreshed')->size(10)->get()->docs();
$this->assertCount(1, $docs);
}

public function testFlushArgumentOptionsApply(): void
{
// Per-call flush($options) must reach the bulk request too.
$index = $this->makeIndex();
(new Bulk($index))
->index('41', ['title' => 'flusharg refreshed'])
->flush(['refresh' => true]);

$docs = $index->newQuery()->match('title', 'flusharg')->size(10)->get()->docs();
$this->assertCount(1, $docs);
}

public function testOnErrorReceivesFailures(): void
{
// create on the already-seeded id '1' triggers a bulk error -> onError
Expand Down
Loading