Skip to content

Commit 71de46e

Browse files
committed
Refactor op_type handling
1 parent e47bc2d commit 71de46e

File tree

6 files changed

+66
-54
lines changed

6 files changed

+66
-54
lines changed

src/Eloquent/Builder.php

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -388,22 +388,27 @@ public function findOrNew($id, $columns = ['*']): Model
388388
return $model;
389389
}
390390

391-
public function withoutRefresh()
391+
public function withoutRefresh(): Model
392392
{
393-
$this->model->options()->add('refresh', false);
394-
395-
return $this->model;
393+
return $this->withRefresh(false);
396394
}
397395

398396
/**
399397
* Explicitly control the Elasticsearch refresh behavior for write ops.
400398
* Accepts: true, false, or 'wait_for'.
401399
*/
402-
public function withRefresh(bool|string $refresh): static
400+
public function withRefresh(bool|string $refresh): Model
403401
{
404-
$this->query->options()->add('refresh', $refresh);
402+
$this->model->options()->add('refresh', $refresh);
405403

406-
return $this;
404+
return $this->model;
405+
}
406+
407+
public function withOpType(string $value)
408+
{
409+
$this->model->options()->add('op_type', $value);
410+
411+
return $this->model;
407412
}
408413

409414
/**
@@ -660,12 +665,12 @@ public function rawDsl($dsl): array
660665
* Force insert operations to use op_type=create for dedupe semantics.
661666
* When set, attempts to create an existing _id will fail with a 409 from Elasticsearch.
662667
*/
663-
public function createOnly(): static
668+
public function createOnly(): Model
664669
{
665670
// mark insert op type on the underlying query options
666-
$this->query->options()->add('insert_op_type', 'create');
671+
$this->withOpType('create');
667672

668-
return $this;
673+
return $this->model;
669674
}
670675

671676
/**

src/Query/DSL/DslBuilder.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ public function setPostFilter(array $filter): self
138138
return $this->set(['post_filter'], $filter);
139139
}
140140

141+
public function setOpType(string $type): self
142+
{
143+
return $this->set(['op_type'], $type);
144+
}
145+
141146
public function setOption(array $keys, $value): self
142147
{
143148
return $this->set($keys, $value);

src/Query/DSL/DslFactory.php

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,6 @@ public static function indexOperation(string $index, mixed $id = null, array $op
1919
return ['index' => $operation];
2020
}
2121

22-
public static function createOperation(string $index, mixed $id = null, array $options = []): array
23-
{
24-
$operation = array_merge(['_index' => $index], $options);
25-
26-
if ($id !== null) {
27-
$operation['_id'] = $id;
28-
}
29-
30-
return ['create' => $operation];
31-
}
32-
3322
// ----------------------------------------------------------------------
3423
// Query
3524
// ----------------------------------------------------------------------

src/Query/Grammar.php

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ public function compileInsert($query, array $values): array
6666

6767
// Prepare main document operation options
6868
$options = [];
69-
$opType = null;
7069

7170
// Handle routing
7271
if (isset($doc['_routing'])) {
@@ -84,18 +83,6 @@ public function compileInsert($query, array $values): array
8483
unset($doc['_parent']);
8584
}
8685

87-
// Respect explicit op_type selection from document
88-
if (isset($doc['_op_type'])) {
89-
$opType = $doc['_op_type'];
90-
unset($doc['_op_type']);
91-
} elseif (isset($doc['op_type'])) {
92-
$opType = $doc['op_type'];
93-
unset($doc['op_type']);
94-
} else {
95-
// Also allow query option to drive op type
96-
$opType = $query->getOption('insert_op_type', null);
97-
}
98-
9986
// We don't want to save the ID as part of the doc
10087
// Unless the Model has explicitly set 'storeIdsInDocument'
10188
if ($query->getOption('store_ids_in_document', false)) {
@@ -104,21 +91,19 @@ public function compileInsert($query, array $values): array
10491
} else {
10592
unset($doc['id'], $doc['_id']);
10693
}
107-
108-
// Add the document operation (index or create)
109-
if ($opType && strtolower((string) $opType) === 'create') {
110-
$index = DslFactory::createOperation(
111-
index: $query->getFrom(),
112-
id: $docId,
113-
options: $options
114-
);
115-
} else {
116-
$index = DslFactory::indexOperation(
117-
index: $query->getFrom(),
118-
id: $docId,
119-
options: $options
120-
);
94+
if (! empty($doc['_op_type'])) {
95+
$options['op_type'] = $doc['_op_type'];
96+
unset($doc['_op_type']);
97+
} elseif ($optType = $query->getOption('op_type')) {
98+
$options['op_type'] = $optType;
12199
}
100+
101+
// Add the document operation
102+
$index = DslFactory::indexOperation(
103+
index: $query->getFrom(),
104+
id: $docId,
105+
options: $options
106+
);
122107
$dsl->appendBody($index);
123108

124109
// Process document properties to ensure proper formatting

src/Query/Processor.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -446,17 +446,16 @@ public function processBulkInsert(Builder $query, Elasticsearch $result): array
446446
];
447447
if (! empty($process['items'])) {
448448
foreach ($process['items'] as $item) {
449-
$action = array_key_first($item) ?? 'index';
450-
if (! empty($item[$action]['error'])) {
449+
if (! empty($item['index']['error'])) {
451450
$outcome['errors'][] = [
452-
'id' => $item[$action]['_id'] ?? null,
453-
'type' => $item[$action]['error']['type'] ?? null,
454-
'reason' => $item[$action]['error']['reason'] ?? null,
451+
'id' => $item['index']['_id'] ?? null,
452+
'type' => $item['index']['error']['type'] ?? null,
453+
'reason' => $item['index']['error']['reason'] ?? null,
455454
];
456455
$outcome['failed']++;
457456
} else {
458457
$outcome['success']++;
459-
if (($item[$action]['status'] ?? 200) == 201) {
458+
if ($item['index']['status'] == 201) {
460459
$outcome['created']++;
461460
} else {
462461
$outcome['modified']++;

tests/CreateOpTypeTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,35 @@
4040
})->toThrow(BulkInsertQueryException::class);
4141
});
4242

43+
it('creates a document with createOrFail and rejects duplicates', function () {
44+
$id = 'dataset:check-1:2025-01-01T00:00:00Z';
45+
46+
// First create should succeed
47+
User::query()
48+
->withRefresh('wait_for')
49+
->createOrFail([
50+
'id' => $id,
51+
'name' => 'First Insert',
52+
'title' => 'admin',
53+
'age' => 30,
54+
]);
55+
56+
$found = User::find($id);
57+
expect($found)->not()->toBeNull();
58+
expect($found->id)->toBe($id);
59+
60+
// Second create with same _id must fail with 409 (bulk error)
61+
expect(function () use ($id) {
62+
User::query()
63+
->createOrFail([
64+
'id' => $id,
65+
'name' => 'Second Insert',
66+
'title' => 'user',
67+
'age' => 31,
68+
]);
69+
})->toThrow(BulkInsertQueryException::class);
70+
});
71+
4372
it('supports per-document op_type via attribute', function () {
4473
$id = 'dataset:check-2:2025-01-01T00:00:00Z';
4574

0 commit comments

Comments
 (0)