addSql(<<<'SQL' CREATE TABLE IF NOT EXISTS composant_piece_slots ( id VARCHAR(36) NOT NULL, "composantid" VARCHAR(36) NOT NULL, "typepieceid" VARCHAR(36) DEFAULT NULL, "selectedpieceid" VARCHAR(36) DEFAULT NULL, quantity INT NOT NULL DEFAULT 1, position INT NOT NULL DEFAULT 0, "createdat" TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, "updatedat" TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id) ) SQL); $this->addSql(<<<'SQL' CREATE TABLE IF NOT EXISTS composant_subcomponent_slots ( id VARCHAR(36) NOT NULL, "composantid" VARCHAR(36) NOT NULL, alias VARCHAR(255) DEFAULT NULL, "familycode" VARCHAR(255) DEFAULT NULL, "typecomposantid" VARCHAR(36) DEFAULT NULL, "selectedcomposantid" VARCHAR(36) DEFAULT NULL, position INT NOT NULL DEFAULT 0, "createdat" TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, "updatedat" TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id) ) SQL); $this->addSql(<<<'SQL' CREATE TABLE IF NOT EXISTS composant_product_slots ( id VARCHAR(36) NOT NULL, "composantid" VARCHAR(36) NOT NULL, "typeproductid" VARCHAR(36) DEFAULT NULL, "selectedproductid" VARCHAR(36) DEFAULT NULL, "familycode" VARCHAR(255) DEFAULT NULL, position INT NOT NULL DEFAULT 0, "createdat" TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, "updatedat" TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY (id) ) SQL); // ── Indexes (idempotent) ───────────────────────────────────────────── $this->addSql('CREATE INDEX IF NOT EXISTS idx_comp_piece_slot_composant ON composant_piece_slots("composantid")'); $this->addSql('CREATE INDEX IF NOT EXISTS idx_comp_piece_slot_piece ON composant_piece_slots("selectedpieceid")'); $this->addSql('CREATE INDEX IF NOT EXISTS idx_comp_piece_slot_type ON composant_piece_slots("typepieceid")'); $this->addSql('CREATE INDEX IF NOT EXISTS idx_comp_sub_slot_composant ON composant_subcomponent_slots("composantid")'); $this->addSql('CREATE INDEX IF NOT EXISTS idx_comp_sub_slot_typecomp ON composant_subcomponent_slots("typecomposantid")'); $this->addSql('CREATE INDEX IF NOT EXISTS idx_comp_sub_slot_selected ON composant_subcomponent_slots("selectedcomposantid")'); $this->addSql('CREATE INDEX IF NOT EXISTS idx_comp_prod_slot_composant ON composant_product_slots("composantid")'); $this->addSql('CREATE INDEX IF NOT EXISTS idx_comp_prod_slot_type ON composant_product_slots("typeproductid")'); $this->addSql('CREATE INDEX IF NOT EXISTS idx_comp_prod_slot_selected ON composant_product_slots("selectedproductid")'); // ── Foreign keys (idempotent via DO $$ block) ──────────────────────── // composant_piece_slots FKs $this->addSql(<<<'SQL' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_comp_piece_slot_composant') THEN ALTER TABLE composant_piece_slots ADD CONSTRAINT fk_comp_piece_slot_composant FOREIGN KEY ("composantid") REFERENCES composants (id) ON DELETE CASCADE; END IF; END $$ SQL); $this->addSql(<<<'SQL' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_comp_piece_slot_type') THEN ALTER TABLE composant_piece_slots ADD CONSTRAINT fk_comp_piece_slot_type FOREIGN KEY ("typepieceid") REFERENCES model_types (id) ON DELETE SET NULL; END IF; END $$ SQL); $this->addSql(<<<'SQL' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_comp_piece_slot_piece') THEN ALTER TABLE composant_piece_slots ADD CONSTRAINT fk_comp_piece_slot_piece FOREIGN KEY ("selectedpieceid") REFERENCES pieces (id) ON DELETE SET NULL; END IF; END $$ SQL); // composant_subcomponent_slots FKs $this->addSql(<<<'SQL' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_comp_sub_slot_composant') THEN ALTER TABLE composant_subcomponent_slots ADD CONSTRAINT fk_comp_sub_slot_composant FOREIGN KEY ("composantid") REFERENCES composants (id) ON DELETE CASCADE; END IF; END $$ SQL); $this->addSql(<<<'SQL' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_comp_sub_slot_typecomp') THEN ALTER TABLE composant_subcomponent_slots ADD CONSTRAINT fk_comp_sub_slot_typecomp FOREIGN KEY ("typecomposantid") REFERENCES model_types (id) ON DELETE SET NULL; END IF; END $$ SQL); $this->addSql(<<<'SQL' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_comp_sub_slot_selected') THEN ALTER TABLE composant_subcomponent_slots ADD CONSTRAINT fk_comp_sub_slot_selected FOREIGN KEY ("selectedcomposantid") REFERENCES composants (id) ON DELETE SET NULL; END IF; END $$ SQL); // composant_product_slots FKs $this->addSql(<<<'SQL' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_comp_prod_slot_composant') THEN ALTER TABLE composant_product_slots ADD CONSTRAINT fk_comp_prod_slot_composant FOREIGN KEY ("composantid") REFERENCES composants (id) ON DELETE CASCADE; END IF; END $$ SQL); $this->addSql(<<<'SQL' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_comp_prod_slot_type') THEN ALTER TABLE composant_product_slots ADD CONSTRAINT fk_comp_prod_slot_type FOREIGN KEY ("typeproductid") REFERENCES model_types (id) ON DELETE SET NULL; END IF; END $$ SQL); $this->addSql(<<<'SQL' DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_comp_prod_slot_selected') THEN ALTER TABLE composant_product_slots ADD CONSTRAINT fk_comp_prod_slot_selected FOREIGN KEY ("selectedproductid") REFERENCES products (id) ON DELETE SET NULL; END IF; END $$ SQL); // ── Data migration: composant.structure.pieces → composant_piece_slots ── $this->addSql(<<<'SQL' INSERT INTO composant_piece_slots (id, "composantid", "typepieceid", "selectedpieceid", quantity, position, "createdat", "updatedat") SELECT 'cl' || encode(gen_random_bytes(12), 'hex'), c.id, NULLIF(piece->'definition'->>'typePieceId', ''), NULLIF(piece->>'selectedPieceId', ''), 1, (ordinality - 1)::int, NOW(), NOW() FROM composants c, LATERAL jsonb_array_elements(c.structure::jsonb->'pieces') WITH ORDINALITY AS t(piece, ordinality) WHERE c.structure IS NOT NULL AND (c.structure::jsonb->'pieces') IS NOT NULL AND jsonb_array_length(c.structure::jsonb->'pieces') > 0 AND NOT EXISTS (SELECT 1 FROM composant_piece_slots cps WHERE cps."composantid" = c.id) AND (NULLIF(piece->'definition'->>'typePieceId', '') IS NULL OR EXISTS (SELECT 1 FROM model_types mt WHERE mt.id = piece->'definition'->>'typePieceId')) AND (NULLIF(piece->>'selectedPieceId', '') IS NULL OR EXISTS (SELECT 1 FROM pieces p WHERE p.id = piece->>'selectedPieceId')) SQL); // ── Data migration: composant.structure.subcomponents → composant_subcomponent_slots ── $this->addSql(<<<'SQL' INSERT INTO composant_subcomponent_slots (id, "composantid", alias, "familycode", "typecomposantid", "selectedcomposantid", position, "createdat", "updatedat") SELECT 'cl' || encode(gen_random_bytes(12), 'hex'), c.id, COALESCE(sub->'definition'->>'alias', ''), COALESCE(sub->'definition'->>'familyCode', ''), NULLIF(sub->'definition'->>'typeComposantId', ''), NULLIF(sub->>'selectedComponentId', ''), (ordinality - 1)::int, NOW(), NOW() FROM composants c, LATERAL jsonb_array_elements(c.structure::jsonb->'subcomponents') WITH ORDINALITY AS t(sub, ordinality) WHERE c.structure IS NOT NULL AND (c.structure::jsonb->'subcomponents') IS NOT NULL AND jsonb_array_length(c.structure::jsonb->'subcomponents') > 0 AND NOT EXISTS (SELECT 1 FROM composant_subcomponent_slots css WHERE css."composantid" = c.id) AND (NULLIF(sub->'definition'->>'typeComposantId', '') IS NULL OR EXISTS (SELECT 1 FROM model_types mt WHERE mt.id = sub->'definition'->>'typeComposantId')) AND (NULLIF(sub->>'selectedComponentId', '') IS NULL OR EXISTS (SELECT 1 FROM composants sc WHERE sc.id = sub->>'selectedComponentId')) SQL); // ── Data migration: composant.structure.products → composant_product_slots ── $this->addSql(<<<'SQL' INSERT INTO composant_product_slots (id, "composantid", "typeproductid", "selectedproductid", "familycode", position, "createdat", "updatedat") SELECT 'cl' || encode(gen_random_bytes(12), 'hex'), c.id, NULLIF(prod->'definition'->>'typeProductId', ''), NULLIF(prod->>'selectedProductId', ''), prod->'definition'->>'familyCode', (ordinality - 1)::int, NOW(), NOW() FROM composants c, LATERAL jsonb_array_elements(c.structure::jsonb->'products') WITH ORDINALITY AS t(prod, ordinality) WHERE c.structure IS NOT NULL AND (c.structure::jsonb->'products') IS NOT NULL AND jsonb_array_length(c.structure::jsonb->'products') > 0 AND NOT EXISTS (SELECT 1 FROM composant_product_slots cps WHERE cps."composantid" = c.id) AND (NULLIF(prod->'definition'->>'typeProductId', '') IS NULL OR EXISTS (SELECT 1 FROM model_types mt WHERE mt.id = prod->'definition'->>'typeProductId')) AND (NULLIF(prod->>'selectedProductId', '') IS NULL OR EXISTS (SELECT 1 FROM products p WHERE p.id = prod->>'selectedProductId')) SQL); } public function down(Schema $schema): void { $this->addSql('DROP TABLE IF EXISTS composant_product_slots'); $this->addSql('DROP TABLE IF EXISTS composant_subcomponent_slots'); $this->addSql('DROP TABLE IF EXISTS composant_piece_slots'); } }