본문으로 건너뛰기

참조가 가리킬 수 있는 키 — int32 가정 걷어내기

문서 목록으로

상태: 설계 · 구현 전

인덱스가 될 수 있는 타입은 넓어졌는데 참조가 따라가지 않았습니다. int가 아닌 키를 잡은 테이블은 읽고 코드를 생성하는 데 문제가 없지만 가리킬 수 없습니다.


1. 문제 — 반만 적용된 규칙

인덱스가 될 수 있는 타입은 「어느 타입인가」가 아니라 「그 값으로 행을 구별할 수 있는가」 하나입니다. int·bigint·string·uuid·enum이 키가 됩니다 — 생성되는 조회가 인덱스 필드 자신의 타입에 대한 사전이기 때문입니다.

참조만 그 규칙 밖에 있습니다.

Field `Clip.Anim` references `Animation`, whose index is `string`. A reference stores
the target's index and the binary format stores that as an `int`, so only a table with
an `int` index can be referenced. Give `Animation` an `int` index, or carry the key here
as a plain `string` and look the row up through `Animation`'s own index.

거부 메시지가 스스로 증거입니다. 마지막 문장이 「키를 그냥 들고 다니면서 직접 찾아보라」인데, 그것은 참조가 해 주는 일을 작성자에게 넘기는 것입니다. 할 수 없는 일이라서가 아니라 형식이 int32로 고정되어 있어서 그렇게 적혀 있습니다.

int32 가정이 고정되어 있는 자리

자리무엇
검증대상 인덱스가 Int32가 아니면 거부
바이너리 익스포터참조면 값 타입을 Int32로 덮어씀
형식의 element 매핑참조면 ElementI32 반환
생성기 13개ForeignRecordreadInt32()로. 파일당 1~7곳, 합계 54곳
쿠킹 컨텍스트ForeignRecord => typeof(int)
SQL 익스포터 3종INT NOT NULL · integer NOT NULL

한 곳의 실수가 아니라 한 가정이 파이프라인 전체에 퍼진 것입니다. 그래서 이것은 조건 하나를 지우는 일이 아니라, 「참조가 담는 값의 타입」이라는 사실을 모델이 들고 다니게 만드는 일입니다.

이미 서 있는 쪽 — 가리켜지는 테이블

막힌 것은 가리키는 컬럼 하나뿐입니다. 대상 테이블 쪽 — 키가 string·bigint·uuid·enum인 테이블과 그 조회 — 은 이미 서 있고 게이트도 있습니다.

이미 있는 것무엇을 고정하는가
string-index 시나리오「생성기는 아무것도 바꿀 필요가 없었다 — 내는 조회가 인덱스 필드 자신의 타입에 대한 사전이기 때문」이라고 그 recipe가 적어 두었습니다
key-types 시나리오bigint·uuid·enum이 키가 되는 것을 모든 언어의 골든으로

그러므로 이 개정이 만들어야 하는 것은 GetByXOrThrow에 넘길 값이 참조 컬럼에서 제 타입으로 나오게 하는 것이고, 받는 쪽 서명은 이미 맞습니다. 6절의 13번이 새 기능의 확인이 아니라 고정을 푼 자리의 확인인 이유가 그것입니다.

2. 결정 — 대상 인덱스의 타입 그대로

foreign 컬럼이 담는 것은 대상의 기본 인덱스 값이고, 그 값의 타입은 대상이 정합니다. int32는 그중 한 경우이지 규칙이 아닙니다.

대상의 인덱스참조 컬럼이 담는 것
inti32 — 지금과 같습니다
biginti64
string문자열
uuiduuid가 실리는 방식 그대로
enum그 enum의 바탕 타입

「그 값으로 행을 구별할 수 있는가」가 인덱스의 규칙이므로, 참조의 규칙도 그것 하나입니다. 인덱스가 될 수 있으면 가리킬 수 있습니다. 별도의 목록을 두지 않는 것이 이 개정의 요지입니다.

3. 기존 출력의 무변경

지금 int32가 아닌 키를 가리키는 참조는 존재할 수 없습니다 — 거부되기 때문입니다. 그래서 이 개정은 받아들이는 경우를 늘릴 뿐이고, 통과하던 워크북이 내는 바이트는 그대로입니다.

  • 형식 버전이 올라가지 않습니다. 새 element 코드가 필요 없습니다 — ElementString·ElementI64는 이미 있고, 참조 컬럼이 그중 맞는 것을 고르게 되는 것뿐입니다.
  • 리더도 그대로입니다. 리더는 파일이 선언한 element를 읽지 참조인지를 가리지 않습니다.
  • 골든이 판정합니다. 한 바이트라도 움직이면 그것은 이 개정이 검출한 것이 아니라 부작용이고 diff를 봐야 합니다.

4. 파싱 순서 — 해석 뒤에 얻는 타입

이 개정에서 실제로 어려운 자리는 여기 하나입니다.

참조 셀의 값은 대상이 해석되기 전에 파싱됩니다. 레이아웃이 필드를 읽을 때 아는 것은 「이 컬럼은 Animation을 가리킨다」는 이름뿐이고, Animation이 무슨 키를 쓰는지는 모든 테이블이 읽힌 뒤에야 정해집니다. 그래서 지금은 참조 컬럼의 타입을 int32로 고정해 두고 데이터 패스가 그것으로 파싱합니다.

결정: 참조 셀은 원문으로 들고 있다가, 해석된 뒤에 대상의 키 타입으로 변환합니다.

고른 것미룬 것
참조 컬럼만 파싱을 한 단계 미룹니다. 다른 컬럼은 지금 그대로선언 전체를 먼저 읽고 데이터를 나중에 읽도록 파서를 두 패스로 나누는 것

두 번째가 더 깔끔하지만 레이아웃 파서의 구조를 바꿉니다. 첫 번째는 참조 컬럼에 국한되고, 참조의 「없음」이 이미 그 자리를 열어 두었습니다 — 참조 셀의 빈 칸을 값 파서에 넘기지 않기로 한 것이 같은 방향입니다.

변환이 실패하면 그때가 진단 자리입니다. 대상이 int 키인데 셀에 abc가 적혀 있으면, 그것은 지금 「Int32로 파싱할 수 없습니다」로 나오던 것이고 대상의 키 타입을 적을 수 있게 되므로 더 나은 메시지가 됩니다 — 어느 테이블의 어떤 키를 기대했는지 적을 수 있습니다.

5. 담지 않는 것

  • bool·float·double·시각·배열 키. 인덱스가 될 수 없는 것은 가리킬 수도 없습니다. 같은 규칙 하나라는 것이 2절의 요지이므로, 참조 쪽에만 예외를 두지 않습니다.
  • 점 표기(Table.Field)가 가리키는 필드의 타입. 그것은 이미 대상 필드의 타입을 따릅니다. 이 문서가 고치는 것은 전체 행 참조가 담는 키입니다.
  • 여러 테이블 중 하나여도 되는 값. 참조가 아니라 검사로 남았습니다 — 키를 담지 않으므로 이 규칙에 걸리지 않습니다(참조가 내는 이름 §6).
  • int32 키의 폭 최적화. 키가 int인 참조는 지금처럼 i32로 실립니다. 비트폭 패킹이 이미 그 위에서 처리합니다.

6. 검증 게이트

게이트확인하는 것
int 키 참조 무변경지금 있는 참조 테스트 전부. 한 글자도 달라지지 않아야 합니다
string 키 참조변환·왕복·생성 코드의 조회가 문자열 키로 서는지
bigint 키 참조i64로 실리는지
uuid 키 참조uuid가 실리는 방식 그대로인지
enum 키 참조바탕 타입으로 실리고 생성 코드가 enum으로 받는지
모든 언어 컴파일·되읽기readInt32() 고정을 풀었으므로 언어마다 확인해야 합니다. 게이트 없는 언어를 믿지 않습니다
거부의 반전string-index-ref 픽스처가 지금 거부를 고정합니다. 통과로 뒤집고, 그 시나리오의 골든을 새로 기록합니다
섞인 키한 워크북에서 여러 키 타입의 대상을 동시에 가리키는 테이블
골든한 바이트도 바뀌지 않아야 합니다 — 3절

가운데 줄이 이 개정의 비용입니다. 코어 변경은 작은데 확인이 13번입니다.

6.1 게이트가 놓친 자리 — 2026-08-25에 나온 넷

6절의 「모든 언어 컴파일·되읽기」가 실제로 닿지 않는 조합이 하나 있었습니다. 최상위 참조 컬럼(레코드 그룹 foreign)이 int 아닌 키의 테이블을 가리키는 형태입니다. 픽스처가 그 조합을 한 번도 만들지 않았습니다 — record-ref는 참조를 레코드 안에 두고, reference-keys는 C#·TypeScript만 냅니다. 그래서 다음 넷이 남아 있었습니다.

언어남아 있던 것
C++std::int32_t {키}; — 레코드 안쪽은 ToCppTypeName으로 옳았습니다
Dartint {키} = 0;
PHPpublic int ${키} = 0;
Rust{이름}: i32,

넷 다 같은 파일 안에 옳은 쪽이 있었습니다 — 레코드 멤버의 선언은 대상의 키 타입을 쓰고 있었고, 최상위 선언만 아니었습니다. 한 파일에 두 답이 있으면 게이트가 없는 쪽이 남습니다.

드러낸 것은 복합 키의 성분입니다. composite-key 시나리오에 연결 테이블 (BeastMove(BeastId, MoveId) — 하나는 문자열 키 대상, 하나는 정수 키 대상)을 넣자 그 자리에서 넷이 동시에 컴파일되지 않았습니다. 정수 키 대상만으로는 안 나옵니다 — 「행 타입을 적은 것」과 「엉뚱한 스칼라를 적은 것」이 갈리지 않기 때문입니다.

기존 골든은 한 파일도 안 움직였습니다. 지금까지의 시나리오는 최상위 참조의 대상이 전부 정수 키였고, 그래서 이 수정이 내는 코드가 이전과 같습니다.

7. 구현 순서

  1. 모델 — 참조가 담는 값의 타입을 필드가 들고 다니게. 해석 시점에 대상의 인덱스에서 옵니다.
  2. 파싱 — 4절. 참조 셀을 원문으로 두었다가 해석 뒤에 변환합니다.
  3. 거부 삭제 — 검증의 Int32 조건. 인덱스가 될 수 없는 타입은 대상이 애초에 인덱스를 가질 수 없으므로 따로 차단할 것이 없습니다.
  4. 익스포터 — 바이너리·형식 element 매핑, SQL 3종.
  5. 생성기 13개readInt32() 고정을 대상 키 타입으로.
  6. 게이트 — 6절. 픽스처를 먼저 뒤집고 언어를 하나씩.

1·2번을 건너뛰고 3번부터 하면 string 키 대상에서 파싱이 먼저 실패합니다. 순서에 이유가 있는 자리입니다.