import { Association, Attribute, DbColumn, Entity, ModelId } from './types'; /** * FK 유래 컬럼 = 물리 컬럼을 보유한 관계 속성(RELATION* + dbAttrs). 형상(dataType/length/scale/ * physicalName/notNull 등)은 부모 식별자(PK) + 관계 identifying에서 파생된다. * derivedFrom이 아니라 이 불변식을 쓰는 이유: derivedFrom은 관계·전파가 심는 링크라 그 자체가 결손일 수 * 있는 반면, "관계 속성이 물리 컬럼을 갖는다"는 형상은 FK의 정의 자체다. * (과거 이 자리엔 "derivedFrom은 영속 데이터엔 0건"이라는 근거가 적혀 있었으나 **실측과 어긋난다** — * BNKR_SALES 32문서에서 FK 속성 666건 전량이 derivedFrom을 보유한다. v2는 문서에 그대로 저장되고, * v1은 어댑터 `assocFromHydrated`(diagramAdapter.ts:350)가 하이드레이션 시 합성한다. 두 판정은 이 * 데이터에서 100% 일치하므로 동작 차이는 없다.) * RELATION_REF(inverse nav, dbAttrs=0)는 물리 컬럼이 없어 제외. * * ── FK 판정 술어 인벤토리(리포 전역) ───────────────────────────────────────── * 이 술어 말고도 "FK인가"를 묻는 자리가 몇 곳 더 있고, **의도적으로 다르다**. 새 판정을 추가하기 전에 * 아래 중 하나로 답할 수 있는지 먼저 확인할 것(중복이면 여기로 위임). * * | 자리 | 술어 | 왜 다른가 | * |---|---|---| * | `isFkDerivedColumn`(여기) | RELATION* && dbAttrs>0 | 뷰 잠금·형상 해소. `findFkOwningAssociation`이 위임 | * | `isJoinColumnOnlyFk`(여기) | RELATION_OWN && 앵커 아님 && 형제가 앵커 | 복합 FK 형제 — **필드를 안 낸다**(아래) | * | `isMultiColumnFk`(여기) | RELATION_OWN && dbAttrs>1 | **형상 A** 서브컬럼 표현 대상. 뷰 셋이 공유 | * | `attributeOrder.isPhysicalFk` | derivedFrom && dbAttrs>0 | 표시 순서 정렬. **강등 이후** 호출 전제라 `derivedFrom`이 기준(경계 강등된 컬럼은 제자리에 남아야 한다) | * | `mermaid.isForeignKey` | RELATION_OWN‖RELATION_REF‖derivedFrom | ER **FK 마커**는 nav(dbAttrs=0)까지 표시 대상이라 컬럼 보유를 안 본다 | * | `ddl.ts` FK 제약 방출 | derivedFrom 단독 | 부모 컬럼을 알아야 REFERENCES를 쓸 수 있어 링크가 필수 | * | `propagation` `currentFk` | derivedFrom 단독 | 판정이 아니라 reconcile diff의 **매칭 키** | * * 실 데이터에서 두 축(RELATION* / derivedFrom)은 100% 일치하므로(위 문단) 이 차이는 대체로 관측되지 * 않는다. 발산했던 유일한 경로는 mermaid 임포트가 관계 없는 RELATION_OWN을 만들던 것이고 * (`parseMermaid.wireForeignKeyColumns` 주석), 그건 발생원에서 닫았다. */ export declare function isFkDerivedColumn(attr: Attribute): boolean; /** * **다중 컬럼 FK**(형상 A) — 관계 하나가 **컬럼 여럿**을 갖는 속성인가. * * JPA 는 관계당 필드 하나이고 컬럼이 여럿이다(`@ManyToOne` + `@JoinColumns`) ⇒ 「한 속성이 여러 컬럼을 * 갖는다」는 형상이 **임베드와 같다**. 그래서 뷰 세 곳이 임베드의 서브컬럼 관용구를 이 술어로 함께 탄다: * 캔버스 서브행(`EntityNode.hasSubColumns`) · 표시 해소(`columnResolution`) · 편집 슬롯 * (`useAttributeEditing.hasColumnSlots`). * * ⚠️**`> 1` 이다** — 단일 컬럼 FK 는 서브컬럼이 없어 대상이 아니다(실측 285/285 가 단일 컬럼 = 전원 * 무영향). `> 0` 으로 쓰면 FK 전량이 단일 컬럼 편집 블록에서 빠져 나간다. * ⚠️`RELATION_REF`(inverse nav)는 컬럼이 0이라 자연히 제외된다. */ export declare function isMultiColumnFk(attr: Attribute): boolean; /** * 관계 필드를 소유하는 **앵커 속성** 집합 — `end2.attributeRef`(owner, navigable 무관) + * `end1.attributeRef`(inverse, navigable일 때만). `scaffoldJava`의 `relByAttr` 키와 **같은 규칙**이며 * 그쪽이 이 함수로 위임한다(산식 이중화 금지). */ export declare function fkFieldAnchors(associations: readonly Association[]): Set; /** * 복합 FK의 **형제 컬럼**인가 — 즉 이 속성이 `@JoinColumns` 컬럼으로만 기여하고 **자기 필드는 내지 * 않는가**. * * 배경: 전파(`computeForeignAttributes`)는 부모 PK 멤버마다 속성을 하나씩 만들지만 * (`parentIdentifiers.map(...)`), JPA는 관계당 **필드 하나**다 — 앵커가 `@ManyToOne` + * `@JoinColumns`를 소유하고 형제는 컬럼만 보탠다. ⇒ 형제의 `name`은 **어디에도 방출되지 않는다**. * * 그래서 이 술어를 쓰는 자리가 셋이다: * 1. `scaffoldJava` — 방출 분기(형제는 `@Column`을 내면 같은 컬럼이 두 번 매핑돼 Hibernate가 * "Repeated column in mapping"으로 부트스트랩에 실패한다). * 2. `validation` `CHK-NAME-7` — 형제의 빈 이름은 **필드 미방출을 일으키지 않으므로** error가 아니다 * (info `CHK-NAME-4`로 강등). 「검증 규칙은 고칠 수 있는 자리를 지목해야 한다」의 적용 — * 여기선 *고칠 필요가 없는* 자리다. * 3. 편집 뷰 — 이름 칸을 잠그고 앵커를 지목한다(사문 슬롯에 입력을 유도하지 않는다). * * ⚠️앵커가 **없는** 그룹은 제외한다(false). 그건 컬럼이 통째로 사라지는 별개 결함이고 * `CHK-JPA-19`·코드젠 `fillIn`이 이미 지목한다 — 여기서 삼키면 무신호가 된다. * * ★실측(2026-08-27, 프로덕션 v2 29문서): 복합 FK 그룹 **2**(참고 전용 제외 시 1) · 형제 **2**(실효 1) · * `CHK-NAME-7` 21건 중 형제 지목 **0**. 발동은 작지만 유입 경로는 열려 있다(v1 잔재·다중 컬럼 FK 신설). */ export declare function isJoinColumnOnlyFk(attr: Attribute, entity: Entity, anchors: ReadonlySet): boolean; /** * FK 유래 컬럼 i의 원본(부모 식별자) 컬럼 — 물리명·타입·길이·스케일이 여기서 파생된다. FK 컬럼 자신의 * dbAttrs가 비어 있을 때(v1→v2 로드/레거시 데이터, 어댑터가 재전파를 하지 않음) placeholder·캔버스 * 표시에 쓴다(레거시 getForeignDatabaseAttributePlaceholder 동형). 1순위 derivedFrom.sourceAttributeRef * (전파가 심은 권위), 2순위 관계 역참조로 부모 식별자 컬럼(derivedFrom 부재하는 레거시 v1 로드 FK 폴백). * 못 찾으면 undefined. * * ★1순위 조회는 **관계가 지목한 부모 안을 먼저** 본다. 속성 modelId는 문서 안에서 유일해야 하지만 * 레거시 유래로 **엔티티 간 중복**인 문서가 실재하고(복제 후 이름만 바꾸고 id를 재발급하지 않은 지문), * 그 경우 전역 순회의 first-match는 **배열에서 앞선 엔티티**를 집어 남의 컬럼을 원본으로 삼는다. * 실측(2026-08-21 BNKR_SALES `module-marketing`): `FreeGiftPromo`(배열 35)와 `ProductGiftPromo`(65)가 * 속성·dbAttr 13쌍의 modelId를 공유해, 관계상 부모가 `ProductGiftPromo`인 자식 3건이 원본 컬럼을 * `product_gift_promo_sn` 대신 `promo_sn`으로 해석했다(DDL·코드젠·캔버스 전부에 전파). * 부모 안에 없으면 종전대로 전역 순회로 폴백하므로, 관계가 stale한 경우의 방어(1순위를 derivedFrom에 * 둔 본래 이유)는 그대로다. id가 유일한 문서에서는 두 경로가 같은 속성을 찾아 **결과가 동일하다**. */ export declare function resolveFkOriginColumn(attr: Attribute, childEntity: Entity | null | undefined, entities: readonly Entity[], associations: readonly Association[], i?: number): DbColumn | undefined; /** * **PK 멤버 술어 — 단일 출처**. 「이 속성이 엔티티의 식별자 멤버인가」를 한 곳에서 정한다. * * ★★**왜 여기 있나(2026-09-03)**: 같은 술어가 **세 곳에 복제**돼 있었고 `@EmbeddedId` 축(5.2.21)이 * 그중 하나(`scaffoldJava.pkMembers`)만 고쳐서 «축을 열었다» 고 보고됐다. 나머지 둘 * (`parentIdColumns` · `propagation.computeForeignAttributes`)이 여전히 임베더블 식별자를 못 봐, * 값 타입 PK 로 전환하면 **부모가 「PK 0」으로 보이고 FK spec 이 통째로 사라졌다**(게이트 실측: * `MemberPointWallet` 1→0 · `MemberCouponWallet` 2→0 · `UserAccount` **7→0**). * ⇒ 술어를 여기(의존 leaf · 두 소비처가 이미 import)로 올려 **한 곳만 고치는 것이 구조적으로 불가능**하게 한다. * * ★멤버십: 스칼라(`NORMAL`) · 식별 FK(`RELATION_OWN`) · **임베더블 식별자**(`@EmbeddedId` — 값 타입을 * PK 로 쓰는 JPA 정식 관용구 · 실물 10자리). `dbAttrs` 가 없는 임베드(inverse nav 등)는 컬럼 기여가 * 없으므로 제외한다. * ⚠️`DIVIDER`·`RELATION_REF` 는 `identifier` 가 참일 수 없거나 컬럼이 없어 자연히 빠진다. */ export declare function isPkMemberAttr(attr: Attribute): boolean; /** * PK 멤버 중 **임베더블**(`@EmbeddedId` 방출 대상) — 코드젠이 `@Embedded` 대신 낼 표기를 가르는 판정. * `isPkMemberAttr` 의 부분집합이고, 멤버십을 재기술하지 않도록 그쪽이 이 함수를 호출한다. */ export declare function isEmbeddedPkAttr(attr: Attribute): boolean; /** * 부모 식별자 컬럼 평탄화(선언 순서) — 복합 PK 를 한 속성이 담는 **형상 A** 의 슬롯 원본. * `scaffoldJava.parentIdColumns` 와 같은 규칙이며 그쪽이 이 함수로 위임한다(산식 이중화 금지). * 멤버십 판정은 **`isPkMemberAttr` 단일 출처**다(위 주석의 「세 곳 복제」 사고 이후). */ export declare function parentIdColumns(parent: Entity | undefined): DbColumn[]; /** * FK 유래 컬럼에서 **관계가 소유하는** patch 필드 — 값의 파생원이 모델에 실재한다(부모 PK 컬럼 형상 + * 부모 end 다중성 + 관계 `identifying`). 뷰는 이 자리를 disabled 로 잠그고(`useAttributeEditing.fkDerived` * 게이트 · 프로브 `verify-fk-derived-locking.mjs` 가 11 단언으로 그 집합을 성문화), AI 경로는 **구조 거부** * 한다 — 두 경로가 **같은 목록**을 봐야 *「GUI 로는 못 하는데 AI 로는 조용히 된다」* 가 안 난다. * * ★★**`type`·`attrType` 은 여기 없다** — 그 축은 `core/javaTypes.typeOwner`(=`derived`)가 소유하고 AI * 경로도 그것으로 이미 거부한다(`attribute-type-conversion-unsupported` reason=`derived`). 두 번 적으면 * 판정이 갈린다(이 리포가 반복해 싸운 미러 드리프트). * ★**`groupCode` 도 없다** — FK 의 `type` 이 `GroupCodeEnum` 이 될 수 없으므로 `isGroupCodeCouplingBroken` * 이 그 조합을 **이미** 거부한다(`groupcode-type-mismatch`). * ⚠️`notNull` 은 실해가 0 이다(`deriveFkNotNull` 이 **매 로드** 재주장하고 호스트 `/text` 도 그 로드를 * 지난다) — 그래도 목록에 두는 이유는 **조용한 no-op 이 에러보다 나쁘기** 때문이다: 쓰면 저장되고 다음 * 로드에 사라져 「썼는데 없다」가 되는데 그 시점이 예측되지 않는다. * ⚠️★**비우기(`null`)도 함께 막는다** — 뷰는 이 자리를 «설정도 해제도» 못 하게 잠그므로 대칭이 그쪽이고, * 정렬 수단은 파생원(관계 인스펙터의 다중성·식별관계)과 reconcile 이다. */ export declare const FK_RELATION_OWNED_FIELDS: { /** 속성 층 — 파생원이 **관계**다(다중성·identifying). */ readonly attr: readonly ["identifier", "notNull"]; /** 컬럼 층 — 파생원이 **부모 PK 컬럼**이다(`propagation.diffSyncedFields` 가 재주장하는 그 셋). */ readonly column: readonly ["dataType", "length", "scale"]; }; /** * FK 유래 컬럼에 **부적합한** 필드 — 파생값이 아니라 «그 자리에 의미가 없는» 축이라 위와 **근거가 다르다** * (레거시 FK 폼이 이 입력을 렌더하지 않는다 — `EntityColumnsSection` 의 `v-if="!fkDerived(a)"` 주석). * * ⇒ ★**처방도 다르다: 거부가 아니라 «안내»**(`fk-derived-inapplicable-field`). 파생값 축과 달리 재주장하는 * 자리가 **없어서** 값이 **영구 잔존**하는데, 뷰가 그 입력을 아예 안 내므로 사람이 되짚을 수 없다 — * 즉 조용히 남는 쪽이 문제이고 쓰기 자체가 구조 위반은 아니다. * ⚠️**비우기는 안내하지 않는다** — 부적합 값을 «지우는» 것은 정당한 정리다(방향이 반대). * ⚠️★**`sequenceName`·`multiValueSeparator` 는 여기 없다** — 이 목록의 근거는 *「FK 폼이 안 내는데 NORMAL * 폼은 낸다」* 는 대조인데 그 둘은 **뷰가 어디에도 없다** ⇒ FK 특정 근거를 세울 수 없다(무★ 규율 — * 근거 없는 항목을 목록에 넣으면 그 자체가 발명이다). `naturalIdMutable` 은 `naturalId` **종속** 플래그라 * FK 축이 아니라 의존 축이다. */ export declare const FK_INAPPLICABLE_FIELDS: { readonly attr: readonly ["transient", "naturalId", "defaultValue", "personalInfoAttributeType"]; /** * 컬럼 층. ⚠️`notNull` 이 **양쪽 목록에 다 있는 것이 아니다** — 속성 층 `notNull` 은 파생값(위)이고, * 여기의 것은 **컬럼 3-상태**(임베드 슬롯 축)라 FK 자리에서는 읽히지 않는다(FK 의 NOT NULL 은 속성 층이 * 정한다) ⇒ 층이 다르고 근거가 다르다. */ readonly column: readonly ["unique", "autoIncrement", "multiValue", "notNull"]; };