본문으로 건너뛰기

다중 대상 참조와 빌드 변종

문서 목록으로

되돌렸습니다 — 참조가 내는 이름 §6. foreign의 대상은 테이블 하나이고, 여러 테이블 중 하나여도 되는 값은 참조가 아니라 검사입니다(refs=A;B). 이 문서가 세운 검사는 그대로 살아 있고, 없어진 것은 승격과 그 위에 선 생성 표면입니다. 아래는 그 결정에 이르기까지의 기록입니다.

상태: 단일 대상 접근자까지 구현됨. 다중 대상의 대상별 프로퍼티와 4절의 빌드 변종이 남았습니다

8절이 남겨 둔 대상별 프로퍼티는 대상별 접근자로 분리했습니다 — 실측하니 승격된 다중 대상이 3개가 아니라 31개이고, 레코드 멤버까지 합치면 187개였습니다

어떤 시트는 한 컬럼의 id가 여러 테이블 중 어느 하나에 있으면 된다고 적습니다. 그것을 표현할 자리가 없어서 그 시트의 검증이 막혀 있습니다.

이 개정은 검사까지입니다. 그 선언이 원본에서 무엇을 하는지 확인해 보니 대조뿐이었고, 그래서 먼저 대조를 세웠습니다. 대상을 컬럼 제약에 담은 것은 그 단계의 선택이지 최종 결론이 아닙니다.

다음 단계는 이것을 foreign으로 되돌립니다. 원본이 대조만 하는 것은 그쪽에 코드 생성이 없기 때문이고, 우리에게는 있습니다. 그러면 이 선언은 「한 컬럼에서 여러 테이블의 레코드에 닿는다」는 뜻이 되고, 대상별 가상 프로퍼티를 낼 수 있습니다 — 대조는 그때 부산물입니다. 8절에 그 설계를 적어 두었습니다.


1. 문제 — 검증 이식의 선행 조건

한 레이아웃의 제약 행은 대상을 여럿 적을 수 있습니다. 한 줄에 하나씩, 따옴표로 감싸서 적습니다.

:link "Mail" 단일 — 대상 하나를 적는 행
:links "CharGear"⏎"Item" 다중 — 둘 중 하나

선언이 576개 있고, 이 도구가 그 행을 읽지 않아 그 규칙이 전혀 검사되지 않았습니다.

막고 있는 것이 무엇인지는 실측되어 있습니다. 상점의 itemIdItem 하나에만 대조하니 4,400건이 걸렸는데, 그 컬럼이 실제로 선언한 것은 Item 하나가 아니었습니다.

「여러 테이블 중 어느 하나」가 그 시트의 실제 규칙이고, 그것이 표현되기 전까지 상점·보상 계열의 규칙은 카탈로그를 손으로 나열합니다. 로드맵이 이 항목에 「검증 이식이 이것을 기다립니다」라고 적어 둔 것이 그 뜻입니다.

카탈로그는 둘입니다. 앞선 조사가 GearSlot·CharGear·Item 셋이라고 적었는데, 시트를 읽어 보니 Shop.ItemId의 선언은 "CharGear""Item" 둘입니다. 셋으로 적힌 문서들은 이 개정에서 함께 고쳤습니다.

2. 결정 — 검사와 해석의 분리

참조는 두 가지 일을 합니다. 이 둘은 비용이 완전히 다릅니다.

무엇다중 대상에서
검사 — 이 id가 실재하는가셋 중 하나에 있으면 통과. 정의가 분명하고, 와이어도 생성 코드도 관여하지 않습니다
해석 — 소비하는 쪽에 레코드를 물려 줌어느 타입인가가 정해지지 않습니다. 모든 언어에 합 타입을 내는 일이고, 각 언어의 결과가 다릅니다

결정. 이 단계는 검사까지입니다. 그 컬럼은 int 컬럼 그대로이고, 값이 나열된 테이블 중 하나의 행 id인지를 변환 단계에서 검사합니다. 해석은 8절이 담습니다.

레코드 멤버별 옵셔널과 같은 갈래입니다 — 막고 있는 것은 검사이지 표현이 아니었습니다. 4,400건을 0건으로 만드는 것은 검사 쪽이고, 그것은 형식도 생성기도 건드리지 않습니다.

이 단계에서 해석을 담지 않는 근거

순서입니다. 검사가 있어야 접근자가 맞는 레코드를 물리는지 판정할 수 있습니다. 그리고 검사는 형식도 생성기도 안 건드리므로, 여기서 끊으면 되돌릴 것이 없는 지점에서 한 번 멈추게 됩니다.

합 타입 때문이 아닙니다. 처음에는 그렇게 적었습니다 — 대상이 셋이면 소비하는 쪽이 받는 것이 「StatOperator 또는 WorldPassiveEffect」이고 모든 언어에 각자의 합 타입을 골라 줘야 한다는 것이었습니다. 8절이 그 전제를 치웁니다: 대상별 프로퍼티를 따로 내면 합 타입이 필요 없습니다.

3. 이 단계에서 모델이 받는 것 — 컬럼 제약

컬럼 제약의 항목 하나를 더합니다. 「이 컬럼의 값은 이 테이블들 중 하나의 행 id여야 한다」이고, 그것이 이 선언이 뜻하는 전부입니다.

foreign (참조)이 제약
대상 테이블 하나대상 테이블 하나 이상
타입이 바뀝니다 — 값이 대상의 행이 됩니다타입은 int 그대로
생성 코드가 레코드 프로퍼티를 냅니다생성 코드 무관
와이어가 대상의 인덱스를 싣습니다와이어 무관 — 이미 실리던 int입니다

:min·:max·값 화이트리스트와 같은 자리입니다 — 타입으로 나타낼 수 없는 것을 시트에서 읽어 어느 셀인지 함께 검사하고, 그 외에는 아무것도 하지 않습니다. 화이트리스트가 값의 목록이라면 이것은 값의 출처 목록입니다.

  • foreign은 이 단계에서 손대지 않습니다. 8절이 손댑니다.
  • 형식·생성기·모든 런타임 무변경. 검사를 더할 뿐입니다.
  • 대상이 하나여도 같은 자리에 담습니다. 원본에서 그 선언은 대상이 하나든 셋이든 같은 역할이므로, 개수로 갈라 둘 곳을 나누지 않습니다.
  • 적힌 테이블이 이 빌드에 하나라도 없으면 그 컬럼은 검사하지 않습니다. 아래.

빌드에 없는 대상 — 전부이거나 아무것도 아니거나

recipe가 고른 워크북만 읽으므로, 선언이 적은 테이블이 이 빌드에 없을 수 있습니다. 그때 남은 것들로 대조하면 없는 테이블에 사는 id가 전부 위반이 됩니다 — 1절의 4,400건이 바로 그 형태이고, 카탈로그 하나를 빼고 검사한 결과였습니다.

그러므로 전부 있거나, 그 컬럼은 판정하지 않습니다. 부분 대조는 틀린 결과를 많이 내는 쪽이지 적게 내는 쪽이 아닙니다.

거부가 아니라 경고입니다. 모델은 오타와 「이 빌드가 안 읽는 워크북의 테이블」을 구별할 수 없고, 거부하면 넓은 빌드를 겨냥한 선언 하나가 좁은 빌드를 전부 멈춥니다. 더 엄격하게 읽고 싶은 파이프라인에는 이미 TreatWarningsAsErrors가 있습니다 — asset이 없는 파일을 다루는 방식과 같은 자리입니다.

검사의 정의

대상 중 하나라도 그 id를 가지면 통과합니다. 어느 것인지는 기록하지 않습니다 — 기록하면 그것이 해석이고, 2절이 담지 않기로 한 것입니다.

경우판정
셋 중 하나에 있음통과
어디에도 없음거부 — 셀 위치와, 어느 테이블들을 찾아봤는지 함께 적습니다
둘 이상에 있음통과. 그 시트의 규칙이 「어느 하나」이므로 모호함이 아닙니다
셀이 비어 있음통과 — 값이 없으면 대조할 것이 없습니다

세 번째가 판단이 필요한 자리인데, 거부하지 않는 이유는 id 대역이 테이블마다 갈라져 있는 것이 그 시트의 규칙이 아니기 때문입니다. 겹침을 문제로 보는 프로젝트라면 그것은 별도의 규칙이고, 검증 파이프라인이 이미 그런 규칙을 적는 자리입니다.

원본이 하는 것 — 실제 검사기를 읽고 확인한 것

이 규칙의 출처는 원본의 검사 스크립트이고, 거기서 확인한 것이 셋입니다.

무엇원본
대조하는 키그 컬럼의 값 그 자체. 대상의 산출물이 id를 최상위 키로 하는 map이라 조회가 키 룩업입니다 — 즉 대상의 기본 인덱스입니다
대상 하나의 표기파일/테이블. /가 없으면 파일 이름이 곧 테이블 이름입니다. 코어가 받는 것은 테이블 이름 쪽입니다
값이 없을 때통과. 검사에 들어가기 전에 돌아갑니다
대상이 없는 빌드해당 없음. 원본은 언제나 산출물 전체를 놓고 돌므로 이 경우가 생기지 않습니다 — 3절의 마지막 항목이 그래서 이 도구의 결정입니다

단수 행도 있습니다. 대상 하나를 적는 행과 목록을 적는 행이 따로 있고, 역할은 대상의 개수만 다릅니다. 3절이 둘을 하나로 받는 근거입니다 — 개수는 목록의 길이일 뿐입니다.

0을 어떻게 볼지는 실측으로 정합니다. 원본은 0을 특별 취급하지 않습니다 — 값이 있는 것으로 보고 0이라는 키를 실제로 찾습니다. 저희 foreign0을 「아무것도 가리키지 않음」으로 통과시키는데, 이 제약은 출처가 다르므로 그 규약을 물려받을 이유가 없습니다. 통과시킬 때와 아닐 때 걸리는 행 수를 세면 그 시트가 0을 무슨 뜻으로 쓰는지 나오므로, 재고 나서 정합니다.

4. _BC 빌드 변종 — 다른 축

같은 항목에 묶여 있지만 다른 문제입니다.

한 레이아웃은 같은 테이블의 지역별 대체본을 이름의 접미사로 적습니다 — Profile_BCGL, Crest_BCCN. 산출물 615개 중 95개가 그 짝이고, 고르는 것은 로드 시점의 지역 코드입니다.

TargetSide와 같은 축이 아닙니다. 서버/클라는 한 테이블을 좁히는 것이고, 변종은 여러 테이블 중 하나를 고르는 것입니다.

결정. 이 개정에서는 담지 않습니다. 접미사가 붙은 것을 각각 별개의 테이블로 둡니다 — 지금 동작이 그것이고, 이름이 다르므로 충돌하지 않습니다.

미루는 근거가 둘입니다.

  • 고르는 시점이 빌드가 아니라 로드입니다. 이 도구가 고르면 산출물이 지역마다 갈라지고, 그것은 그쪽이 지금 하는 방식이 아닙니다. 한 번 내보내고 클라이언트가 고르는 구조라면 이 도구가 할 일은 둘 다 내보내는 것이고, 그것은 이미 되고 있습니다.
  • 표현을 정하려면 소비하는 쪽을 봐야 합니다. 생성 코드가 Profile이라는 이름 하나로 지역에 맞는 것을 돌려주려면 로드 시점의 선택을 모든 런타임이 알아야 합니다. 3절의 검사와 달리 이것은 런타임의 일이고, 요구가 확인되기 전에는 정할 것이 아닙니다.

이 문서가 그것을 여기 적어 두는 이유는 묶여 있던 두 문제를 갈라 두기 위해서입니다. 참조 쪽은 막고 있는 것이 분명하고, 변종 쪽은 그렇지 않습니다.

5. 담지 않는 것

아래는 이 단계가 담지 않는 것입니다. 앞의 둘은 8절이 담습니다.

  • 다중 대상의 해석. 2절 · 8절.
  • foreign을 다중 대상으로 넓히는 것. 3절 · 8절.
  • _BC 변종의 선택. 4절. 이것만은 다음 단계에도 없습니다.
  • 대상별 id 대역의 검사. :min/:max가 이 컬럼에 적히면 그것은 대역 선언이지만, 셋 중 어느 것인지를 대역으로 판정하는 것은 3절이 기록하지 않기로 한 것을 다시 들여오는 일입니다.
  • 대상 표기의 파일 쪽. 파일/테이블의 파일은 그 프로젝트가 산출물을 어떻게 쪼개는지이지 모델의 사실이 아닙니다. 레이아웃이 테이블 이름만 떼어 코어에 넘깁니다.
  • 제약 행 이름을 코어 표기로. 그 레이아웃의 행 이름입니다. 코어가 받는 것은 「이 컬럼의 값은 이 테이블들 중 하나의 행 id다」라는 모델 수준의 선언입니다 (CLAUDE.md).

6. 검증 게이트

게이트확인하는 것
foreign 무변경지금 있는 참조 테스트 전부. 한 글자도 달라지지 않아야 합니다 — 이 개정은 foreign을 건드리지 않습니다
다중 대상 통과셋 중 하나에 있는 id
다중 대상 거부어디에도 없는 id. 셀 위치와 찾아본 테이블 이름들을 적는지까지
대상 하나단수 행과 목록 하나짜리가 같은 판정을 내는지
없는 테이블을 적은 것선언 자체의 거부. 오타가 「대조할 것이 없어 통과」가 되면 안 됩니다
골든한 바이트도 바뀌지 않아야 합니다. 검사를 더할 뿐 값을 바꾸지 않습니다
샘플 재생성576개 선언이 무엇을 검출하는지. 1절의 4,400건이 0건이 되는지가 이 개정의 판정 기준입니다

마지막 줄이 이 개정이 산 것입니다 — 손으로 나열하던 카탈로그가 시트의 선언으로 바뀝니다.

실측

이전 대규모 코퍼스의 부분 빌드(7개 테이블)에서:

무엇얼마
읽힌 선언114개 — 대상 1개가 92, 2개가 19, 6개가 3
실제로 대조된 컬럼16개, 95,709행
위반0건
대조하지 못한 컬럼98개 — 적힌 테이블이 이 빌드에 없음

Shop.ItemId 29,574행이 전부 통과합니다. 1절이 막고 있다고 적은 그 컬럼이고, 이것이 이 개정의 결과입니다.

「0건」 옆의 「16개 컬럼·95,709행」이 세어야 하는 수입니다 — 그것이 없으면 이 결과는 「검사가 안 돌았다」와 구별되지 않고, 실제로 처음 돌렸을 때는 안 돌던 것에 가까웠습니다(아래).

한 번 59,352건이 나왔습니다. 검사한 모든 컬럼의 모든 행이었고, 원인은 데이터가 아니라 비교였습니다 — 키 컬럼은 int로 좁혀지고 보통의 수 컬럼은 double로 남는데, 박싱된 75203300.0은 박싱된 75203300과 같지 않습니다. 모든 행이 걸리면 그것은 데이터에 대한 답이 아니라 「양쪽 타입이 다르다」는 답입니다. 게이트 하나가 이것을 지킵니다.

7. 구현 순서

  1. 모델ColumnConstraints에 대상 테이블 목록을 담는 항목 하나.
  2. 레이아웃 — 단수 행과 목록 행을 읽어 그 목록에 넣습니다. 파일/테이블에서 테이블 이름만 뗍니다.
  3. 검증 패스 — 3절의 정의로 판정하고 어느 셀인지 함께 보고합니다. 적힌 테이블이 이 빌드에 없으면 그 컬럼을 판정하지 않고 경고합니다.
  4. 계측 — 무엇이 잡히는지, 몇 개를 검사하고서 그런지 셉니다.
  5. 게이트 — 6절.

0은 따로 다루지 않기로 하였습니다. 원본이 특별 취급하지 않고, 실측에서 위반이 0건이라 특별 취급할 이유가 생기지 않았습니다. 0을 「없음」으로 쓰는 컬럼은 그 값을 가진 행이 대상에 있거나, 셀을 비워 두거나입니다 — 둘 다 지금 통과합니다.

생성기도 형식도 이 목록에 없습니다. 이 단계에서 끊은 자리가 그것이고, 되돌릴 것이 없는 지점이 거기까지입니다.

8. 대상별 가상 프로퍼티

이것은 결국 foreign입니다. 한 컬럼에서 여러 테이블의 레코드에 닿는 foreign이고, 원본이 대조까지만 한 것은 그쪽에 코드 생성이 없기 때문입니다. 우리에게는 있으므로 거기서 멈출 이유가 없습니다.

3절이 담은 검사는 없어지지 않습니다 — 대상이 이 빌드에 없어 접근자를 낼 수 없는 컬럼이 그대로 그 자리에 남고, 실측에서 그것이 294건입니다.

필드 타입에 따른 두 경우

대상필드 타입생성되는 것
1개ForeignRecord지금 그대로레코드 프로퍼티 하나. 기존 machinery 전부 재사용
2개 이상대상 키의 타입id + 대상별 가상 프로퍼티 N개
shopRow.ItemId id — 지금도 있는 것
shopRow.ItemIdAsCEquip CharGear 레코드 또는 없음
shopRow.ItemIdAsItem Item 레코드 또는 없음

합 타입이 필요 없습니다. 대상별로 프로퍼티를 따로 내면 각 프로퍼티의 타입이 하나이고, 그것은 모든 언어가 전부 낼 수 있는 것입니다. 2절이 미룬 근거가 여기서 사라집니다.

세 번째 상태가 생기지 않는 이유

「레코드 하나로 해석되지 않음」이 새 상태가 아니라 ForeignRecord가 아닌 것으로 표현됩니다. 그래서 ResolvedRefTable is null의 뜻이 안 바뀌고, IsRef도 「정확히 한 레코드로 풀린다」로 유지됩니다 — 그것을 읽는 165곳과 51곳을 감사할 일이 없습니다. 다중 대상은 생성기에 새 view 하나로 들어갑니다.

필드가 드는 것무엇
대상 이름 목록진짜 소유자. 하나짜리도 길이 1의 목록입니다
기존의 단수 대상 이름목록이 하나일 때만 그것. IsRef가 이것을 봅니다

초판이 적어 둔 함정이 이렇게 해소됩니다. 초판은 다중 대상에 세 번째 상태가 필요하고 정하지 않으면 src/의 51곳이 그것을 실패로 읽는다고 하였습니다. 맞는 관찰이었고, 답은 상태를 늘리는 것이 아니라 다중 대상을 ForeignRecord로 만들지 않는 것입니다.

실측에서 나온 것

대상이 여럿인 선언 58개, 94,748행에서 대상끼리 키 겹침이 0이고 모든 값이 정확히 하나의 대상에 있습니다. 즉 위의 프로퍼티 중 언제나 하나만 값을 돌려줍니다.

그것을 우연이 아니라 검사로 고정합니다 — 선언당 대상 키 집합의 교집합 한 번이면 됩니다. 겹치면 그 선언을 보고합니다. 겹침이 정상인 프로젝트라면 그때 판단할 일이고, 지금 데이터에는 없습니다.

단일 대상 — 그냥 foreign

조사한 선언 1,425개 중 1,345개가 대상 하나입니다. 그중 실제로 승격되는 것은 621개이고, 나머지가 어디로 가는지는 아래 표에 있습니다.

먼저 풀어야 할 것

무엇얼마상태
참조의 int32 가정접근자가 대상 키로 조회하므로 먼저 걷어냅니다 (설계)
자기 참조5건. foreign이 지금 거부합니다아래
대상이 이 빌드에 없음294건아래

자기 참조를 여는 근거

EventShop.PriorId · ZonePerk.GroupNo · WorldBuff.GroupNo · WorldBuff.AddBuff0Id · Character.VoiceOverride. 전부 정상적인 자기 참조입니다 — GroupNo는 그룹 대표 행을 가리키고, 21,261행이 전부 유효한 id입니다.

순환과 다릅니다. 전체 행 참조는 대상을 찾은 즉시 해석이 끝나므로 체인이 생기지 않고, 순환은 점 표기(Table.Field)에서만 생깁니다. 그러므로 자기 참조 금지를 점 표기에만 겁니다.

빌드에 없는 대상 — 접근자를 내지 않는 이유

참조가 되면 생성 코드가 대상 타입을 필요로 하므로, 없는 테이블을 가리키는 컬럼에 접근자를 내면 컴파일되지 않는 코드가 나옵니다. 그 컬럼은 3절의 검사로 남깁니다 — 값은 id 그대로이고 경고 하나가 붙습니다. 빌드를 거부하지 않는 것이 3절의 결정이고 여기서도 같습니다.

이 개정이 바꾸는 데이터

여기가 앞선 세 개정과 다른 자리입니다. 5d·5e·참조의 「없음」· 참조 키 타입은 전부 「골든이 한 바이트도 안 움직인다」였는데, 이것은 움직입니다.

:links가 붙은 컬럼은 지금 평범한 수 컬럼이고, 그 레이아웃이 number를 좁히지 않으므로 double로 실립니다. 참조가 되면 대상의 키 타입(그 데이터에서는 int)으로 실립니다.

무엇지금참조가 된 뒤
와이어double 8바이트대상 키 — 그 데이터에서는 int32 4바이트
생성 코드id 하나id + 레코드 프로퍼티
같은 수같은 수

값은 같고 폭이 줄어듭니다. 그래도 이것은 산출물이 바뀌는 변경이므로, 이전 대규모 코퍼스의 커밋된 출력이 움직이는 것을 미리 재고 diff를 봐야 합니다. 이전 소규모 코퍼스는 무변경이 어야 합니다.

구현 순서

#무엇상태
1모델 — 필드가 대상 이름 목록을 들게
2승격 — 적힌 테이블이 전부 이 빌드에 있으면 참조로
3해석 — 하나면 ForeignRecord, 여럿이면 키 타입 그대로
4자기 참조 — 금지를 점 표기에만
5겹침 검사 — 대상끼리 같은 id를 들면 보고
6계측 — 샘플 재생성 — 아래
7모든 언어 — 다중 대상의 대상별 프로퍼티대상별 접근자로 분리

7번만 남았고, 그것이 다중 대상 접근자입니다. 단일 대상은 승격되어 지금 레코드 프로퍼티를 냅니다 — 조사한 1,425개 선언 중 624개가 그 자리입니다. 다중 대상 컬럼은 id를 싣고 3절의 검사를 받으며, 대상 테이블은 모델에 해석되어 있으므로 생성기가 읽을 것은 이미 있습니다.

이 문서가 적어 둔 「다중 대상은 3개」가 조사 범위의 수였습니다. all 빌드를 계측하니 승격된 것이 31개이고, 레코드 멤버의 홀드백을 걷으면 156개가 더 옵니다. 그리고 대상이 16개인 선언이 있습니다 — 대상마다 슬롯을 두는 이 문서의 전제가 그 폭에서는 성립하지 않으므로, 대상별 접근자가 담는 것을 다시 정했습니다.

선언 1,425개의 행방

경우선언
승격됨 — 레코드 멤버가 아니고 대상이 전부 있음624 (단일 621 · 다중 3)
레코드 멤버, 대상은 전부 있음507
대상이 이 조사에 없음 (레코드 멤버 아님)196
대상이 이 조사에 없음 (레코드 멤버)98

막고 있던 것이 다중 대상이 아니라 레코드 멤버였습니다. 507개가 대상까지 전부 갖춰 놓고 레코드 안의 참조 하나 때문에 서 있었습니다. 다중 대상 쪽은 레코드 멤버를 빼면 3개뿐입니다.

그래서 다음에 한 것은 대상별 프로퍼티가 아니라 레코드 안의 참조였고, 그것은 끝났습니다. 여기 남은 것은 대상이 여럿인 55개이고, 그것이 이 문서가 아직 여는 것입니다.

실측 — 승격이 바꾼 것

무엇결과
이전 소규모 코퍼스무변경. 그 선언이 없습니다
이전 대규모 코퍼스의 부분 빌드값 무변경. JSON 한 줄도 안 바뀝니다
이전 대규모 코퍼스의 부분 빌드 바이너리GuildCrest.tcb 544 → 543바이트
레코드 프로퍼티Roster.characterIdCharacterRecord를 돌려줍니다

값이 같고 폭만 줄어드는 것이 예측대로 나왔습니다. 승격된 컬럼은 double로 실리던 것이 대상의 키 타입으로 실립니다.

레코드 그룹의 멤버도 이제 승격합니다레코드 안의 참조가 끝나면서 그 제외가 없어졌습니다. 승격에서 아직 빠지는 것은 대상이 여럿인 레코드 멤버와, 번호로만 닿는 이름 없는 레벨의 멤버뿐입니다. 앞의 것은 이 문서가 여는 것이고, 뒤의 것은 키를 담을 이름이 없습니다. 빠진 컬럼들은 3절의 검사를 그대로 받습니다.