From 40de0eecb0b0f39e1d130a0e8cd36963ef3f424a Mon Sep 17 00:00:00 2001 From: Alex Standiford Date: Mon, 27 Apr 2026 19:07:00 -0400 Subject: [PATCH] Use wpdb insert identity field --- lib/Traits/CanQueryWordPressDatabase.php | 19 +- .../Traits/CanQueryWordPressDatabaseTest.php | 208 ++++++++++++++++++ 2 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/Traits/CanQueryWordPressDatabaseTest.php diff --git a/lib/Traits/CanQueryWordPressDatabase.php b/lib/Traits/CanQueryWordPressDatabase.php index 451aeb1..4191fa6 100644 --- a/lib/Traits/CanQueryWordPressDatabase.php +++ b/lib/Traits/CanQueryWordPressDatabase.php @@ -104,7 +104,24 @@ protected function wpdbInsert(Table $table, array $data): array return $ids; } - return ['id' => $wpdb->insert_id]; + if (count($fields) !== 1) { + throw new DatastoreErrorException(sprintf( + 'Insert succeeded for table "%s", but the record identity could not be resolved. Expected identity fields: %s.', + $table->getName(), + json_encode($fields, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) ?: '[unserializable fields]' + )); + } + + $insertId = (int)$wpdb->insert_id; + + if ($insertId <= 0) { + throw new DatastoreErrorException(sprintf( + 'Insert succeeded for table "%s", but WordPress did not report an insert ID.', + $table->getName() + )); + } + + return [Arr::first($fields) => $insertId]; } /** diff --git a/tests/Unit/Traits/CanQueryWordPressDatabaseTest.php b/tests/Unit/Traits/CanQueryWordPressDatabaseTest.php new file mode 100644 index 0000000..53adc3d --- /dev/null +++ b/tests/Unit/Traits/CanQueryWordPressDatabaseTest.php @@ -0,0 +1,208 @@ +insert($table, [ + 'externalId' => 123, + 'source' => 'wc_order', + 'status' => 'active', + ]); + + $this->assertSame([ + 'externalId' => 123, + 'source' => 'wc_order', + ], $result); + } + + /** + * @covers \PHPNomad\Integrations\WordPress\Traits\CanQueryWordPressDatabase::wpdbInsert + */ + public function testInsertReturnsAutoIncrementIdUsingTableIdentityField(): void + { + $GLOBALS['wpdb'] = new FakeWpdb(44); + $table = new FakeTable('wp_records', ['recordId']); + $strategy = new WordPressDatabaseQueryHarness(); + + $result = $strategy->insert($table, ['status' => 'active']); + + $this->assertSame(['recordId' => 44], $result); + } + + /** + * @covers \PHPNomad\Integrations\WordPress\Traits\CanQueryWordPressDatabase::wpdbInsert + */ + public function testInsertThrowsWhenAutoIncrementIdCannotBeResolved(): void + { + $GLOBALS['wpdb'] = new FakeWpdb(0); + $table = new FakeTable('wp_records', ['id']); + $strategy = new WordPressDatabaseQueryHarness(); + + $this->expectException(DatastoreErrorException::class); + $this->expectExceptionMessage('Insert succeeded for table "wp_records", but WordPress did not report an insert ID.'); + + $strategy->insert($table, ['status' => 'active']); + } + + /** + * @covers \PHPNomad\Integrations\WordPress\Traits\CanQueryWordPressDatabase::wpdbInsert + */ + public function testInsertThrowsWhenCompoundIdentityCannotBeResolved(): void + { + $GLOBALS['wpdb'] = new FakeWpdb(44); + $table = new FakeTable('wp_records', ['externalId', 'source']); + $strategy = new WordPressDatabaseQueryHarness(); + + $this->expectException(DatastoreErrorException::class); + $this->expectExceptionMessage('Insert succeeded for table "wp_records", but the record identity could not be resolved.'); + + $strategy->insert($table, ['externalId' => 123]); + } +} + +class WordPressDatabaseQueryHarness +{ + use CanQueryWordPressDatabase; + + /** + * Inserts the provided data through the protected WordPress query helper. + * + * @param Table $table The table to insert into. + * @param array $data The insert payload. + * @return array + */ + public function insert(Table $table, array $data): array + { + return $this->wpdbInsert($table, $data); + } +} + +class FakeWpdb +{ + public string $last_error = ''; + public string $error = ''; + + /** + * @param int $insert_id The ID exposed through wpdb::insert_id. + */ + public function __construct(public int $insert_id) + { + } + + /** + * Fakes wpdb::insert(). + * + * @param string $table The table name. + * @param array $data The row data. + * @param string[] $formats The value formats. + * @return int|false + */ + public function insert(string $table, array $data, array $formats) + { + return 1; + } + + /** + * Fakes wpdb::query(). + * + * @param string $query The query. + * @return int|false + */ + public function query(string $query) + { + return 1; + } +} + +class FakeTable implements Table +{ + /** + * @param string $name The full table name. + * @param non-empty-array $identityFields The identity fields. + */ + public function __construct(private string $name, private array $identityFields) + { + } + + /** @inheritDoc */ + public function getName(): string + { + return $this->name; + } + + /** @inheritDoc */ + public function getAlias(): string + { + return 'fake'; + } + + /** @inheritDoc */ + public function getTableVersion(): string + { + return '1'; + } + + /** @inheritDoc */ + public function getColumns(): array + { + return []; + } + + /** @inheritDoc */ + public function getIndices(): array + { + return []; + } + + /** @inheritDoc */ + public function getCharset(): ?string + { + return null; + } + + /** @inheritDoc */ + public function getCollation(): ?string + { + return null; + } + + /** @inheritDoc */ + public function getFieldsForIdentity(): array + { + return $this->identityFields; + } + + /** @inheritDoc */ + public function getUnprefixedName(): string + { + return 'records'; + } + + /** @inheritDoc */ + public function getSingularUnprefixedName(): string + { + return 'record'; + } +}