get_charset_collate()` * (per §17.2) and relies on `InnoDB` as the storage engine (§17.1). * * Scope explicitly NOT covered here: * * - Foreign keys. Per §11.2 and §13.4, AFFILUN does not declare foreign * keys at the DB level; referential integrity is enforced by the * application layer. * * - `DEFAULT CURRENT_TIMESTAMP` on `created_at` / `updated_at`. Per * §13.4, environment-sensitive default clauses are avoided; * timestamps are supplied by the application layer on insert / * update. * * - Any DML. This migration creates the empty table only; seed data * and backfills belong in later migrations or runtime code paths. * * The shared DDL-assembly and installer-dispatch skeleton lives in * {@see AbstractCreateTableMigration}; this subclass only declares the * bits that actually differ between tables (target version, description, * logical name, column manifest, index manifest). See the base class's * file-level docblock for the idempotency and failure-propagation * guarantees inherited here. * * @package Affilun\Infrastructure\Persistence\Migrations */ declare(strict_types=1); namespace Affilun\Infrastructure\Persistence\Migrations; use Affilun\Infrastructure\Persistence\SchemaVersion; /** * Creates the `affilun_products` table at schema version 1. * * Concrete {@see AbstractCreateTableMigration} subclass. See the * file-level docblock for the scope of this migration and the columns * and indexes it installs; see the base class for the formatting rules * and failure semantics that apply to every AFFILUN table-creation * migration. */ final class CreateProductsTable extends AbstractCreateTableMigration { /** * Logical, unprefixed AFFILUN table name installed by this migration. * * Kept as a class-level constant so tests can reference the exact * name without duplicating the string literal, and so a future * rename has a single location to update. */ public const TABLE_LOGICAL_NAME = 'affilun_products'; /** * Target schema version installed by this migration. * * Returns version 1 — the "initial release" revision per * `database-schema.md` §13.2. */ public function targetVersion(): SchemaVersion { return SchemaVersion::of( 1 ); } /** * Short, human-readable description of this migration. * * Used by the diagnostics surface and migration logs. Kept terse * and operator-friendly per the * {@see \Affilun\Infrastructure\Persistence\Migration::description()} * contract. */ public function description(): string { return 'create affilun_products table'; } /** * Logical, unprefixed AFFILUN table name installed by this migration. * * Consumed by {@see AbstractCreateTableMigration::up()} and * {@see AbstractCreateTableMigration::tableName()} to resolve the * physical, prefix-applied name through the injected * {@see \Affilun\Infrastructure\Persistence\TableNameResolver}. */ protected function logicalTableName(): string { return self::TABLE_LOGICAL_NAME; } /** * Column definitions for `affilun_products`, in the canonical order * defined by `database-schema.md` §4.2 / §17.3. * * Each entry is a single column definition without its trailing * comma; the base class adds the indent and inter-column commas. * * @return list */ protected function columnDefinitions(): array { return array( 'id bigint unsigned NOT NULL AUTO_INCREMENT', 'provider varchar(32) NOT NULL', 'provider_item_id varchar(191) NOT NULL', 'canonical_hash char(64) NOT NULL', 'title text NOT NULL', 'brand varchar(191) NULL', 'description longtext NULL', 'image_primary_url text NULL', 'image_gallery_json longtext NULL', 'price_amount decimal(12,2) NULL', 'price_currency varchar(8) NULL', 'price_label varchar(191) NULL', 'availability_status varchar(32) NULL', 'detail_url text NOT NULL', 'affiliate_url text NULL', 'raw_payload_json longtext NULL', 'search_keywords varchar(512) NULL', 'search_keywords_source varchar(32) NULL', 'snapshot_version int unsigned NOT NULL DEFAULT 1', "sync_state varchar(16) NOT NULL DEFAULT 'fresh'", "lifecycle_state varchar(16) NOT NULL DEFAULT 'active'", 'archived_at datetime NULL', 'last_synced_at datetime NULL', 'last_rendered_at datetime NULL', 'last_sync_attempted_at datetime NULL', 'last_sync_error_code varchar(64) NULL', 'last_sync_error_message text NULL', 'created_at datetime NOT NULL', 'updated_at datetime NOT NULL', ); } /** * Index definitions for `affilun_products`, in the canonical order * defined by `database-schema.md` §4.3 / §17.3. * * The `PRIMARY KEY` entry uses the two-space separator that * `dbDelta()` relies on to detect the primary key line — this is * a WordPress-specific formatting quirk, not a typo. * * @return list */ protected function indexDefinitions(): array { return array( 'PRIMARY KEY (id)', 'UNIQUE KEY uq_provider_item (provider, provider_item_id)', 'KEY idx_canonical_hash (canonical_hash)', 'KEY idx_last_synced_at (last_synced_at)', 'KEY idx_sync_state (sync_state)', 'KEY idx_provider_sync_state (provider, sync_state)', 'KEY idx_lifecycle_state (lifecycle_state)', 'KEY idx_created_at (created_at)', ); } }