명시적 호환성 커서를 위한 타입 지정 CAS 바인딩 설계 (#418) (한국어)¶
🌐 PREPARED_BINDING_DESIGN.md의 번역입니다. 영어 원문이 표준이며, CI가 영어 원문과의 구조 일치를 검사합니다.
상태: 검토를 마친 설계이며, 첫 번째 한정된 동기 공개 스칼라 부분집합은
#439에서 구현했습니다.
내부 FC2/FC3 스칼라 패킷 기반 작업은
#475에서 별도로 추적합니다. 이 작업만으로는
prepared 커서를 사용할 수 있게 되지 않으며, #439가 소유자/fetch/트랜잭션 경로를 추가합니다.
일반 pycubrid.Cursor.execute()와 pycubrid.aio.AsyncCursor.execute()는
계속 1.x SQL 리터럴을 렌더링하여 FC41로 보냅니다. 이 설계는 이들의 기본값을
바꾸지 않고, 측정된 속도 향상이나 플랜 캐시 효과를 약속하지 않으며, 현재의
SQL 인젝션 익스플로잇을 입증하지 않고, CUBRID TIME 정밀도를 바꾸지도 않습니다.
공식 공개 API 목록과
#438 additive 계약은
동기 전용 pycubrid.compat.native prepared 커서를 선택합니다. 공개
비동기 prepared 커서는 #439의 범위가 아닙니다. private 비동기 전송 테스트는
비동기 prepared API를 공표하지 않습니다.
래퍼 쿼리 인자, 컬렉션, LOB, 커서 탐색과 폭넓은
차등 증명은 별도의 #440–#446 작업으로 남아 있습니다. upstream 소스는
pycubrid에 복사하지 않습니다. 출처를 참고하십시오.
#439 동기 진입점은 기존 호환성 네이티브 연결을
cursor(), commit() -> None, rollback() -> None으로 확장합니다. 실제로 동작하는
set_autocommit()이나 캐시된 설정 멤버는 구현하지 않으며, 이는
#467에 속합니다. 공개 #439 연결은 현재의 호환성 기본값
autocommit=True를 유지합니다. 수동 모드 프로토콜 테스트는 명시적인 private 픽스처로
내부 드라이버 모드를 설정하며, #467 이전에는 공개 수동 모드
설정을 공표하지 않습니다. 첫 번째 커서 부분집합은 prepare(sql) -> None,
bind_param(index, value, bind_type=0, /) -> None,
execute(option=0, max_col_size=0, /) -> int,
fetch_row(how=0, /) -> tuple | None, close() -> None입니다. 인덱스는
1부터 시작합니다. 처음에는 bind_type=0, option=0, max_col_size=0과 튜플 fetch
how=0만 허용하며, 기본값이 아닌 모드는 I/O 전에 명시적으로 실패합니다.
dict 행과 변환기 콜백은 #466, 위치 지정은 #444, 확장
메타데이터는 #445입니다. 이는 의도적으로 불완전한 공식 공개 부분집합이며,
조건 없는 네이티브 또는 DB-API 패리티 주장이 아닙니다.
소스 및 라이브 증거 경계¶
소스 고정: 공식 Python 드라이버
e75ec36,
그 CCI gitlink
7d1eb8f,
브로커 10.2
d56a158
및 11.4
6b2bc75.
설계 프로브는 2026-09-28에 pycubrid main 0057f98b, Python
3.10.12, CUBRID 10.2.18.9024와 11.4.6.1963으로 실행했습니다. 고정된 네이티브
확장은 위의 소스/CCI 리비전에서 빌드했으며, 런타임 의존성으로
설치하지 않았습니다. 두 라이브 OPEN_DATABASE 응답 모두
statement_pooling=1을 알렸으며, pooling 비활성 상태는 측정하지 않았습니다. 네이티브 프로브는
실제 autocommit True와 False를 사용했고, DML에는 독립 관측자를 사용했습니다.
네이티브 CCI는 투명하게 다시 prepare할 수 있으므로, 네이티브 Python
커서 하나를 재사용한 것만으로는 서버 핸들이 재사용되었다는 증거가 되지 않습니다.
두 브로커 모두에서 독립적인 직접 CAS 프로브는 FC2 하나를 보낸 뒤
같은 숫자 서버 핸들과 물리 세션으로 여러 FC3 패킷을 보냈으며,
FC2를 다시 보내지 않았습니다. 명시적 commit 뒤의 부호 있는 INT 값 11, 12, 13과
명시적 rollback 뒤의 14에 대해, 각 SELECT는 두 autocommit 모드 모두에서
일치하는 행을 반환했습니다. CCI의 forward-only 바이트 0을 사용한
수동 모드 프로브도 같은 값을 반환했습니다. 타입 지정 INT INSERT의 반복도
두 모드 모두에서 하나의 FC2 핸들로 성공했습니다. 독립 관측자는
수동 작업을 commit 뒤에만, 자동 모드 작업을 각 execute 뒤에 확인했습니다. FC2
prepare 플래그 NORMAL=0과,
별도로 HOLDABLE=0x08은 바인딩 없는 반복 실행 사례에서
수락되었습니다. 네이티브의 활성 SELECT 결과는 commit 뒤에는 계속 fetch할 수 있었지만
rollback 뒤에는 그렇지 않았습니다. 이 결과 관측은 직접 핸들 재사용과는 별개입니다.
위치 2에 대한 직접 HOLDABLE=0x08 FC8 요청도 두 브로커 모두에서 수동
commit 뒤에는 성공하고 rollback 뒤에는 실패했습니다. 다만 그 첫 FC8
응답에 두 행이 모두 들어 있었으므로 부분적인 다중 FETCH 결과를 증명하지는 못했습니다.
네이티브 커서 close는 수동 DML을 커밋하지 않았습니다. 직접 FC6 관측자 프로브에서는
핸들만 보낸 경우와 명시적 false가 대기 중인 INSERT를 보이지 않는 상태로 남겼고,
true를 붙인 FC6는 두 서버 모두에서 이를 보이게 했습니다. 프로브 테이블은 제거했으며
존재하지 않음을 확인했습니다. 이 결과는 pooling이 켜진 두 기본 구성
레인에 적용되며, 임의의 브로커 설정, 재접속 또는 모든 공식 기능에는 적용되지 않습니다.
프로토콜 형태는 고정된 CCI의
prepare,
execute,
값 직렬화,
close에
근거하며, 불완전한 현재의 ExecutePacket.write()를 완성된
바인딩 지원으로 취급한 것이 아닙니다. 10.2와
11.4
브로커의 인자 수 분기는 FC3 인자 레이아웃에서 일치합니다.
확정된 첫 슬라이스 와이어 계약¶
모든 인자 앞에는 부호 있는 big-endian int32 바이트 길이가 붙습니다. 최상위 인자 수 필드는 없습니다. FC2와 FC3는 기존의 DATA_LENGTH/CAS_INFO 프레임 헤더를 사용하며, prepared 핸들의 범위는 하나의 물리 CAS 세션입니다. 첫 공개 구현은 쿼리 결과만이 아니라 정확한 바이트를 단언해야 합니다.
| 요청 | 순서가 정해진 인자와 초기 선택 | 응답 / 파서 의무 |
|---|---|---|
| FC2 PREPARE | NUL로 끝나는 UTF-8 SQL; 측정한 10.2/11.4 레인에서는 1바이트 HOLDABLE=0x08 prepare 플래그; 1바이트 실제 autocommit. 첫 슬라이스에서는 CCI 지연 닫기 ID를 덧붙이지 않습니다. |
서버 핸들; 캐시 수명; 문장 타입; 권위 있는 바인드 수; 갱신 가능 플래그; 컬럼 수/메타데이터. 음수 결과는 일반 CAS 오류 파서를 사용합니다. |
| FC3 EXECUTE | 고정 인자 열 개, 순서대로: 핸들 int32, NORMAL=0 플래그 바이트, max-column-size int32=0, max-rows int32=0, 빈 CALL 모드 인자, SELECT fetch 바이트(SELECT이면 1, 아니면 0), 실제 autocommit 바이트, forward-only 바이트 자동 모드 1 / 수동 모드 0, 0인 캐시 타임스탬프(8바이트), timeout int32=0. 그다음 바인드마다 길이 접두사가 붙은 인자 두 개: 타입 바이트와 타입 지정 값 바이트. 따라서 argc=10+2N입니다. |
건수, 캐시 재사용 가능 바이트, 결과 수/레코드; 프로토콜 >1에서는 샤드 ID와 인라인 FETCH 앞의 선택적 갱신 컬럼 정보 블록을 반영합니다. 그 플래그만 건너뛰어서는 안 됩니다. 파싱 오류가 나면 부분 성공으로 처리하지 않고 불확실한 전송을 폐기합니다. |
| FC6 CLOSE_REQ_HANDLE | 핸들 int32만(측정한 레인에서는 false와 동등); 연결이 자동 모드라는 이유만으로 실제 autocommit true를 덧붙이지 않습니다. | 브로커 결과를 확인합니다. 현재 물리 세대의 소유자 핸들만 닫으며, 폐기된 핸들을 교체 세션으로 보내지 않습니다. |
prepare(sql)은 내장 NUL이나 유효하지 않은 UTF-8 인코딩을 FC2
전에 거부합니다. 잘못된 형식의 SQL 문자열이 NUL 종결자에서 잘리거나
브로커 오류로 새어 나가는 일은 없습니다. FC2 HOLDABLE=0x08은 이 브로커들에서 CCI의 실제 네이티브 기본값을 따르지만,
플래그 0을 사용한 직접 FC2도 측정한 pooling 활성
레인에서 핸들을 재사용했습니다. 첫 호환성 슬라이스는 성공한 명시적 commit 뒤에
활성 SELECT fetch를 반드시 유지하고 rollback 뒤에는 이를 닫아, 측정한
네이티브 동작과 일치해야 합니다. 일반 pycubrid는 별도의 1.x 결과
무효화 계약을 유지합니다. 더 크고 일부만 버퍼링된 다중 FETCH 사례는
#439의 필수 라이브 게이트입니다. 증명할 수 없으면, 결과가 조용히 잘리는
prepared 커서를 공표하지 말고 #439를 중단하거나 분할합니다.
pooling 비활성과 알 수 없는 브로커 정보 값은 핸들 재사용의
허가가 아닙니다. 별도로 측정한 정책이 생길 때까지 첫 슬라이스는 prepared I/O 전에 명확한
미지원 기능 오류로 이를 거부해야 합니다.
스칼라 바인딩과 I/O 전 결정¶
선택된 기본 문자열 타입은 CHAR=1(고정된 네이티브
직렬화기와 일치)이지만, 직접 프로브는 이 브로커들에서 STRING=2도 수락했습니다.
첫 슬라이스는 이 대체 매핑을 노출하지 않습니다. bind_type=0은
아래 행 중 하나를 추론한다는 뜻이며, 0이 아닌 bind type은 그 계약이 설계될 때까지
와이어 I/O 전에 거부합니다. 바인딩되지 않은 상태와 명시적으로 바인딩한 SQL NULL은
서로 다른 로컬 상태입니다.
| Python 입력 | FC3 타입 인자 | FC3 값 인자 | 경계 |
|---|---|---|---|
int(#840부터 bool도 1/0으로), 범위 [-2^31, 2^31-1] |
길이 1, INT=8 | 길이 4, 부호 있는 big-endian int32 | 바이트는 바뀌지 않습니다. #840 이전에는 이 범위를 벗어나면 DataError가 발생했지만, 이제 BIGINT 또는 NUMERIC 행을 따릅니다. |
int32 밖이고 부호 있는 64비트 범위인 int (#840) |
길이 1, BIGINT=21 | 길이 8, 부호 있는 big-endian int64 | 잘라내지 않습니다. |
int64를 넘고 38자리 이하인 int (#840) |
길이 1, NUMERIC=7 | ASCII 10진 숫자와 NUL 하나 | 자릿수가 더 많으면 I/O 전에 DataError가 발생합니다. |
float (#840) |
길이 1, DOUBLE=12 | 길이 8, big-endian IEEE 754 binary64 | nan/inf는 I/O 전에 ProgrammingError가 발생합니다. 브로커는 NaN을 조용히 저장합니다. |
Decimal (#840) |
길이 1, NUMERIC=7 | 리터럴 경로의 고정소수점 텍스트에서 단독 선행 0을 뺀 값과 NUL 하나 |
NaN/Infinity는 ProgrammingError, 38자리 초과는 리터럴 경로와 같이 DataError입니다. 브로커는 부호와 소수점을 뺀 모든 문자를 정밀도로 셉니다. |
date, time, naive datetime (#840) |
길이 1, DATE=13 / TIME=14 / DATETIME=22 | 길이 14, big-endian int16 필드 7개(연, 월, 일, 시, 분, 초, 밀리초); DATE는 시간 필드를, TIME은 날짜와 밀리초 필드를 0으로 둡니다 | 브로커는 TIME을 오프셋 6에서 읽고 길이를 검사하지 않으므로 항상 14바이트 배치를 씁니다. 마이크로초는 리터럴 경로와 같이 절사합니다. |
tz 포함 datetime (#840) |
길이 1, DATETIMETZ=31 | 14바이트 필드 뒤에 리터럴 경로의 시간대 텍스트(지역, 지역과 약어, 또는 +hh:mm), NUL 없음 |
끝에 NUL이 있으면 브로커가 지역 이름을 거부합니다(-1162). 잘못된 tzinfo는 ProgrammingError입니다. |
bytes, bytearray (#840) |
길이 1, VARBIT=6 | 바이트 그대로 | 빈 값은 NotSupportedError가 발생합니다. 브로커가 길이 0인 값을 NULL로 읽기 때문입니다. |
내장 NUL이 없는 str |
길이 1, CHAR=1 | UTF-8 바이트와 NUL 하나; 빈 문자열은 NUL 1바이트 | 인코딩 실패나 내장 NUL은 I/O 전에 거부합니다. 따옴표/백슬래시는 값 바이트로 남으며 SQL로 렌더링하지 않습니다. |
None |
길이 1, NULL=0 | 길이 0, 페이로드 없음 | 완전하고 명시적으로 바인딩한 SQL NULL이며, 생략된 슬롯이 아닙니다. |
#439에서는 bool, float, 날짜/시간, bytes, 객체, 컬렉션과 LOB 핸들을
지원하지 않았고 I/O 전에 거부했습니다. #440과 #441이 컬렉션(bind_set())과
LOB 핸들(bind_lob())을, #840이 위의 스칼라 행을 추가했으므로 이제
bind_param()은 타입 지정 컬렉션과 빈 bytes를 제외하고 리터럴 경로가 렌더링하는 모든 Python
타입을 받습니다. 객체는 계속 지원하지 않습니다. 호스트 변수 변환 차이는
Prepared 호스트 변수 매핑에 정리되어 있습니다. 네이티브의 직접 bind_param(None)은 현재
SystemError를 발생시키며, 이 upstream 버그는 패리티 목표가 아닙니다. 지원하지 않는
execute 플래그, 0이 아닌 max_col_size, 불완전하거나 초과한 파라미터와 유효하지 않은
1부터 시작하는 인덱스도 마찬가지로 FC3 전에 실패합니다. 검증 실패는
이전의 사용 가능한 결과와 핸들을 그대로 두어야 하며, FC41 폴백은 허용하지 않습니다.
초기 오류 계약은 다음과 같습니다. 잘못된 인덱스/인자 수, 바인딩되지 않은
슬롯, SQL이나 값의 내장 NUL, 지원하지 않는 입력 타입 또는 옵션은 ProgrammingError,
38자리를 넘는 정수나 Decimal(#840 이전에는 INT 오버플로)과 SQL/값의 UTF-8 인코딩 실패는 DataError,
prepare되지 않았거나 닫혔거나 오래된 소유자는 InterfaceError,
pooling 비활성/알 수 없는 브로커 기능과 빈 bytes 값(#840)은 NotSupportedError,
불확실한 전송은 OperationalError입니다. 정확한 메시지 텍스트는 고정되어 있지 않습니다.
소유자와 수명 주기 상태 기계¶
명시적 호환성 커서 하나는 네 개의 독립된 축을 소유합니다. 호환성
연결은 일반 _DriverConnection._cursors가 아니라 별도의 prepared 소유자 레지스트리/트랜잭션 훅을
유지합니다. 일반 드라이버의 commit/rollback
무효화는 일반 커서의 쿼리 핸들을 null로 만들며, FC2 소유자를 지워서는 안 됩니다.
공개 호환성 commit()/rollback()은 내부
드라이버에 위임한 다음, 경계가 성공한 뒤에만 prepared 소유자에게 알립니다.
호환성 close()는 전송을 닫기 전에 현재 세대의 prepared 소유자를
FC6(false)로 닫으며, 실패했거나 폐기된 세션에는 FC6를 보내지 않습니다.
향후 #467의 실제 autocommit 설정자는 같은 소유자 훅에 알려야 합니다.
네 개의 축은 다음과 같습니다:
- 물리 세션: 연결 identity와 단조 증가하는 세대(현재 연결 복구 펜스와 동일).
- prepared 문장: 서버 핸들, 그 소유 세대, 바인드 수, 문장/컬럼 메타데이터, 로컬 핸들 에포크.
- 결과: 없음, 활성, 소진 또는 무효. 결과 무효화 자체는 문장 핸들의 무효화를 증명하지 않습니다.
- 트랜잭션: 현재 에포크와 경계 유형(암묵적 autocommit, 명시적 commit, rollback). 바인딩된 슬롯과 실행별 스냅샷은 이 축들과 별개입니다.
| 이벤트 | 핸들 정책 | 결과/바인드 정책 | 네트워크 정책 |
|---|---|---|---|
prepare(sql) 성공 |
안전하게 닫은 뒤에만 이전 소유 핸들을 교체합니다. FC2 응답의 바인드 수/컬럼, 세대와 에포크를 기록합니다. | 바인딩된 슬롯/결과를 지웁니다. | FC2 하나; 이전의 현재 세대 핸들만 닫습니다. |
bind_param(i, value, 0) |
변경 없음. | 1부터 시작하는 1..N, 지원 타입/범위를 검증하고 바로 인코딩합니다. None이어도 슬롯을 설정합니다. |
I/O 없음. |
execute(0, 0) |
협상된 pooling 활성 레인에서 현재 세대 핸들을 재사용합니다. 암묵적 FC2는 없습니다. | 모든 슬롯의 바인딩을 요구하고 타입 지정 쌍을 스냅샷합니다. 검증 뒤에만 이전 결과를 교체합니다. | 정확히 N쌍을 가진 FC3 하나. FC41이나 조용한 재prepare는 없습니다. |
| 성공한 명시적 commit | 측정한 pooling 활성 레인에서 문장 핸들을 유지합니다. 호환성 훅을 통해 트랜잭션 에포크를 진행시킵니다. | 살아 있는 HOLDABLE SELECT 결과를 이후 fetch를 위해 보존합니다. 새 스냅샷으로 의도적으로 재실행할 수 있도록 바인딩된 슬롯을 보존합니다. | 경계라는 이유만으로 FC6를 보내지 않습니다. 다중 FETCH 지속은 필수 라이브 증명입니다. |
| 성공한 명시적 rollback | 측정한 pooling 활성 레인에서 문장 핸들을 유지합니다. 호환성 훅을 통해 트랜잭션 에포크를 진행시킵니다. | 이전 활성 결과를 무효화합니다. 새 스냅샷으로 의도적으로 재실행할 수 있도록 바인딩된 슬롯을 보존합니다. | 경계라는 이유만으로 FC6를 보내지 않습니다. |
| FC3 암묵적 자동 경계 | pooling 활성 레인에서 같은 세대의 문장 핸들을 유지합니다. | 라이브 P05가 선택된 HOLDABLE/FETCH 정책을 증명하는 경우에만 방금 생성된 SELECT 결과를 소비할 때까지 fetch 가능하게 유지합니다. 그렇지 않으면 조용히 부분 행을 반환하지 말고 #439를 중단하거나 분할합니다. | autocommit은 FC3가 전달합니다. FC6(true)를 덧붙이지 않습니다. |
| 물리 재접속, 복원 실패, 불확실한 요청/응답, 또는 쓰기가 이루어졌을 수 있는 시점 이후의 취소 | 핸들과 소유자 세대를 폐기합니다. 숫자 핸들만으로 일치를 판단하지 않습니다. | 결과와 보내지 않은 스냅샷을 무효화합니다. | 새 세션에서 이전 소유자의 FC3/FC6는 없습니다. SQL/트랜잭션 자동 재실행도 없습니다. |
| 명시적 커서 또는 연결 close | 소유자를 로컬에서 폐기합니다. 같은 세대이고 전송을 신뢰할 수 있으면 핸들만/false인 FC6를 보냅니다. | 결과/슬롯을 무효화합니다. | 연결 close는 전송을 닫기 전에 레지스트리를 비웁니다. 물리 폐기 뒤에는 FC6가 없으며, best-effort 정리도 실패하면 주 오류를 보존합니다. |
이는 pooling이 켜진 상태에서 측정한 R1 동일 세대 유지와, 물리 무효화 뒤의 명시적 P1 재prepare를 선택한 것입니다. 자동 P2 재prepare는 보류합니다. 숨은 FC2는 SQL/세션 의미를 바꿀 수 있으며, FC3 전달이 불확실한 뒤에는 절대 일어나서는 안 됩니다. 실행마다 FC2+FC3를 보내는 구현은 반복적인 핸들 재사용을 충족하지 않습니다. pooling이 꺼져 있거나 알 수 없는 경우 첫 슬라이스는 FC2 전에 차단됩니다. 그 경우의 다른 유지 정책은 별도로 측정한 설계가 필요하며 조용히 FC41로 폴백할 수 없습니다.
세대 검사는 최종 직렬화/쓰기와 같은 요청/수명 주기 임계 구역 안에 있어야 합니다. 현재의 송신 경로는 쓰기 전 사전 점검 단계에서 재접속을 수행할 수 있으므로, 바깥쪽에서만 하는 검사는 충분하지 않습니다. 그 구역 안에서는 전송을 사전 점검하고, 대기 중인 경계/정리 전이를 적용하고, 연결 identity, 세대와 핸들 에포크를 비교한 다음에야 직렬화하고 씁니다. 한 바이트도 쓰지 않은 것이 확실한 검증 오류나 쓰기 전 취소 오류는 세션을 사용 가능한 상태로 둘 수 있습니다. 쓰기 시도 실패, 부분 전송, 불확실한 읽기, 또는 쓰기가 이루어졌을 수 있는 시점 이후의 취소가 있은 뒤에는 세션, 핸들과 결과를 폐기합니다. 교체 세션에 FC6를 보내거나 SQL을 자동으로 재시도하지 않습니다. 완료된 서버 보고 SQL 오류는 전송 실패를 꾸며 내지 않고 일반 DB-API 오류 매핑을 따릅니다. 공개 비동기 prepare는 여전히 범위 밖이지만, private 비동기 요청 취소는 같은 재실행 금지 규칙을 지켜야 합니다.
구현 전에 명시한 #439 수락 테스트¶
이 ID들은 이미 통과한 테스트가 아니라 기대하는 단언을 정의합니다. 각 테스트는 요청 수/함수 코드, 서버 버전, 브로커 정보의 pooling 비트, 물리 세대, 결과/오류, 정리와 skip 사유를 기록합니다. 측정한 두 설계 레인은 pooling이 켜진 Python 3.10/CUBRID 10.2와 Python 3.14/CUBRID 11.4입니다. #439가 공개 prepared 기능을 지원 대상 서버 전체에 대해 공표하기 전에, 그 라이브 구현 게이트는 11.0과 11.2에서도 통과해야 합니다 (또는 검증되지 않은 그 레인들을 prepared I/O 전에 명시적으로 거부하고 지원 공백을 열어 두어야 합니다). 지원하는 다른 Python 버전은 오프라인으로 다루며, 전체 호환성 매트릭스와 네이티브 차등 인증은 #446/#396 작업으로 남습니다. 사례가 0건이거나 설명되지 않은 skip은 실패입니다. 네이티브 비교는 추가로 고정된 e75/7d 소스/빌드 ID를 기록하며, 네이티브 의존성이 없는 것은 패리티 증거가 아닙니다.
| ID | 시나리오 / 기대하는 관측 결과 | 와이어 및 정리 단언 |
|---|---|---|
| P01 | FC2 SQL/플래그/autocommit과 바인드 수 0/1/N; 서버 메타데이터 유지. | 정확한 FC2 프레임; 음수 응답은 꾸며 낸 핸들을 해제하지 않습니다. |
| P02 | INT 최솟값/0/최댓값, CHAR ASCII/한국어/따옴표/백슬래시/빈 값, 명시적 NULL이 각각 값과 Python 타입 그대로 왕복합니다. | 정확한 FC3 10+2N 인자 길이; FC41이나 SQL 리터럴 치환은 없습니다. |
| P03 | 인덱스 0/N+1, 바인딩되지 않은 슬롯, 초과/부족한 인자 수, int 오버플로(#840부터 38자리 초과; bool은 #840부터 허용), 내장 NUL, 지원하지 않는 타입/옵션은 위에 명시한 예외 클래스로 거부됩니다. |
FC3/FC6/쓰기 0건; 이전의 사용 가능한 결과는 검증 실패 뒤에도 남습니다. |
| P04 | prepare; bind; execute; bind; execute는 두 모드 모두에서 서로 다른 SELECT 행을 반환하고 서로 다른 DML 값 두 개를 삽입합니다. |
FC2 하나, FC3 둘, 같은 현재 세대 핸들; 관측자는 수동 DML을 commit 뒤에만, 자동 DML을 각 FC3 뒤에 확인합니다. |
| P05 | 공개 commit()은 여러 FC8 페이지에 걸쳐 활성 HOLDABLE SELECT fetch를 보존합니다. 공개 rollback()은 활성 결과를 무효화하지만, 둘 다 같은 세대의 prepared 소유자를 유지합니다. private 수동 모드 테스트 설정으로 실제 트랜잭션 경계를 증명하며, 공개 기본 자동 모드 테스트는 별도로 유지합니다. |
네이티브 10.2/11.4 비교, 명시적 결과/fetch 단언, 재실행 전체에 걸쳐 FC2 하나, 조용한 부분 행 성공 없음. |
| P06 | FC3 갱신 컬럼 정보가 없는 경우와 있는 경우, 이어서 샤드와 인라인 FETCH. | 프레임 전체를 소비합니다. 갱신된 메타데이터를 행 디코딩 전에 파싱합니다. 잘못된 형식의 블록은 fail-closed로 실패합니다. |
| P07 | 대기 중인 수동 DML 뒤의 close는 커밋하지 않습니다. 자동 모드의 execute는 FC6가 아니라 FC3로 커밋합니다. | 현재 세대의 핸들만/false인 FC6; FC6(true) 없음; 독립 관측자. |
| P08 | 바깥쪽 검사와 쓰기 사이의 재접속은 FC3/FC6 전에 오래된 소유자를 거부합니다. | 숫자 ID가 우연히 같아도 교체 세션에 오래된 핸들 바이트는 0건입니다. |
| P09 | 바이트 전송 전 검증/취소와 부분 전송/읽기 타임아웃/잘못된 형식의 응답 및 비동기 취소의 대비. | 올바른 주 오류; 불확실한 전송만 폐기; 재시도 없음, 새 세션 FC6 없음. |
| P10 | 일반 동기/비동기 FC41과 SQLAlchemy 스모크는 현재의 시그니처/결과를 유지합니다. | 일반 execute나 연결 생성으로 prepared 경로가 활성화되지 않습니다. |
| P11 | 두 라이브 레인에서의 네이티브 e75 스칼라/NULL 비교; 알려진 네이티브 None 충돌은 차이로 분류합니다. | 정확한 빌드/소스 ID, 실행한 사례와 지원하지 않는 차이; 사용할 수 없는 오라클을 성공으로 처리하지 않습니다. |
| P12 | pooling 비활성/알 수 없는 브로커 정보. | 첫 슬라이스는 FC2 전에 거부합니다. 암시적인 핸들 재사용 보장이나 FC41 폴백은 없습니다. |
| P13 | 유효·무효한 SQL/값 문자열의 속성 기반/보안 관점 생성: 임의의 따옴표, 백슬래시, 빈 값, 내장 NUL과 서러게이트/UTF-8 실패. | 성공 사례는 SQL 템플릿과 타입 지정 값 바이트를 분리해 유지합니다. 유효하지 않은 SQL은 FC2를 0건 쓰고, 유효하지 않은 바인딩 값/옵션은 앞선 유효한 FC2 뒤에 FC3를 0건 씁니다. 기존 소유 핸들의 정리는 계속 P03/P07이 규정합니다. FC41 폴백이 없으며 오류/로그 출력에 SQL/DSN/파라미터 내용이 없습니다. |
#418의 성공은 설계 결정이자 증거 경계일 뿐입니다. #439는 공개 표면을 공표하기 전에 이 사례들을 구현하고 실행해야 합니다. #439의 잠정적인 M 범위가 커지면 검토 가능한 PR 두 개 이상으로 나누어 제공해야 합니다. 먼저 공개 API나 패리티 주장이 없는 내부 FC2/FC3 파서/직렬화기 테스트, 그다음 라이브/결함 게이트를 갖춘 호환성 소유자/fetch/트랜잭션 슬라이스입니다. 내부 패킷 기반 작업도, 일부만 통과하는 공개 PR도 #439를 닫지 않습니다. PR마다 수락 증거를 따로 유지합니다. #440/#441은 전체/원소 NULL, 빈 컬렉션, 순서/중복과 세션에 묶인 LOB 핸들/짧은 전송 사례를 별도로 추가합니다. #446은 여전히 차단 조건인 더 넓은 차등 게이트이며, #396은 이 RFC로 닫을 수 없습니다.