Skip to main content
이 가이드는 C++ VDF 구현을 작성하기 위한 심층 참조입니다. 종단간 빌드 단계를 다루는 C++로 확장 만들기, 그리고 테스트-반복 루프를 다루는 C++ 테스트의 동반 자료입니다.
VEF 프로토콜 3은 v0.0.4부터 안정적입니다. 프로토콜 4는 개발 중이며, 선택적으로 활성화하는 개발 ABI 헤더(-DVSQL_USE_DEV_ABI=ON)를 통해서만 사용할 수 있습니다. 이전 프로토콜 2로 빌드된 확장은 서버에서 거부되며 재빌드가 필요합니다.

확장 함수 작성

확장 함수는 C++로 작성되며 VEF에 등록됩니다. SDK 전체에 액세스하려면 단일 헤더를 포함합니다:

인자 및 결과 타입

VDF 매개변수 및 결과는 타입 안전 인자 및 결과 타입으로 전달됩니다. 프레임워크는 함수 시그니처에서 이를 감지하고 자동으로 적응합니다 — make_func 등록 구문은 변경되지 않습니다. 인자 타입: IntArg, RealArg, StringArg, CustomArg — 각각 is_null()value()를 제공합니다. 매개변수화된 사용자 정의 타입의 경우, CustomArgWith<P>는 캐시된 구문 분석된 매개변수 구조체를 반환하는 params() 액세서를 추가합니다(참조: 매개변수화된 타입). 결과 타입: IntResult, RealResult, StringResult, CustomResult — 각각 set_null(), warning(msg), error(msg)를 제공합니다. 스칼라 결과는 추가로 set(value)를 제공합니다. 버퍼 결과는 buffer()set_length(len)을 제공합니다. StringResult는 추가로 set(std::string_view)를 제공하여, 뷰에서 최대 buffer().size() 바이트를 복사하고 길이를 한 번에 설정합니다. 매개변수화된 사용자 정의 타입의 경우, CustomResultWith<P>params() 액세서를 추가합니다. 스팬 타입: 바이트 기반 인자 및 결과 타입의 value()buffer()vsql::Span<T>를 반환합니다 — data(), size(), empty(), begin()/end(), operator[]를 갖는, 연속된 T 구간에 대한 소유하지 않는 뷰입니다. C++20에서는 std::span<T>의 별칭이며, C++17에서는 이 SDK가 최소한의 호환 구현을 제공하므로 동일한 코드가 두 표준에서 컴파일됩니다. <villagesql/vsql.h>를 통해 사용할 수 있습니다. warning(msg)는 행에 대해 SQL NULL을 반환하고 SQL 경고를 추가합니다. 엄격 모드(STRICT_TRANS_TABLES)에서는 MySQL이 INSERT/UPDATE에서 이를 문장 오류로 승격시키므로, 엄격 컨텍스트에서는 error(msg)처럼 동작합니다. 인코딩 함수의 구문 분석 불가능한 문자열과 같은 복구 가능한 잘못된 입력에 사용합니다. 손상된 저장 데이터 또는 계속 실행이 안전하지 않은 조건에는 error(msg)를 사용합니다. 두 메시지 모두 필요한 경우 서버의 내부 오류 버퍼에 맞게 잘립니다. 스칼라 예제 — 두 정수를 더합니다:
이진 예제 — 사용자 정의 타입 버퍼를 제자리에서 변환합니다:
StringResultCustomResult의 경우, buffer()에 쓴 다음 작성된 바이트 수와 함께 set_length()를 호출합니다. buffer().size()는 최대 용량입니다. 사용자 정의 타입을 반환하는 VDF(returns(CUSTOM(MYTYPE)))의 경우, 서버는 결과 버퍼를 해석된 반환 타입의 persisted_length에 맞춰 자동으로 크기 조정합니다 — 확장 작성자는 이 경우 함수 빌더에 .buffer_size(...)를 선언할 필요가 없습니다. prerun이 버퍼를 더 크게 늘리면 그 큰 크기가 유지됩니다. 예를 들어 이 덕분에 SVECTOR::from_string('[…1024 floats…]')이 결과 버퍼 공간이 부족해지는 일 없이 넓은 벡터를 인코딩할 수 있습니다. 동일한 확장 내에서 함수 간에 다른 스타일을 사용할 수 있습니다 — 각 함수의 스타일은 자체 시그니처에 따라 결정됩니다.

집계 VDF

집계 VDF는 각 GROUP BY 그룹 내에서 여러 행에 걸쳐 상태를 누적하고 그룹당 단일 결과를 반환합니다. SQL SUM 또는 COUNT와 유사합니다. 등록하려면 make_aggregate_func<State, &result_fn>("name")을 사용합니다. State 타입은 그룹별 누적 버퍼입니다. prerunpostrun은 이를 할당하고 삭제하기 위해 자동으로 생성됩니다. 결과 함수는 void(const State&, ResultType) 시그니처를 가져야 하며, ResultTypeIntResult, RealResult, StringResult, CustomResult, 또는 CustomResultWith<P> 중 하나입니다. out.set(value)를 호출하여 값을 반환하거나 out.set_null()을 호출하여 SQL NULL을 반환합니다. .clear<>().accumulate<>()가 모두 필요합니다. 빌더는 이를 컴파일 타임에(build()를 통해) 강제하고, 서버는 INSTALL EXTENSION 시점에 다시 검증합니다 — clear는 상태를 재설정하고, accumulate는 행을 결합하며, 결과 함수는 최종 상태를 읽습니다.
빌더 메서드 작동 방식:
  • make_aggregate_func<State, &result_fn>()prerunpostrun을 자동 생성합니다(State를 값 초기화하고 삭제합니다).
  • .clear<&fn>()은 사용자의 void(State&) 재설정 함수를 등록합니다.
  • .accumulate<&fn>()은 사용자의 void(State&, TypedArgs...) 결합 함수를 등록합니다. TypedArgs는 함수 시그니처에서 추론됩니다(IntArg, StringArg 등).
  • 결과 타입(IntResult, RealResult 등)은 결과 함수 시그니처에서 추론됩니다.
NULL을 절대 반환하지 않는 카운터의 경우, 단순한 상태 타입을 사용합니다:
StringResult 집계 VDF는 텍스트를 반환합니다: 결과는 utf8mb4_bin 문자 집합 및 콜레이션을 보고하므로, 클라이언트는 이를 16진수가 아닌 문자로 표시합니다 — 스칼라 VDF STRING 경로와 동일합니다. 또한 .max_result_length(n)을 동일한 방식으로 존중하여, 구체화된 집계 결과(GROUP BY/DISTINCT 임시 테이블, CREATE TABLE ... SELECT, 또는 UNION)의 크기를 조정하여 인자 너비에서 잘리지 않도록 합니다. 크기 조정 규칙 및 상한선은 사용자 정의 버퍼 크기를 참조하세요.

문장별 상태 (Prerun 및 Postrun)

일부 VDF는 단일 쿼리가 처리하는 모든 행에 걸친 상태를 필요로 합니다 — 호출 카운터, 캐시된 결과, 열린 리소스. prerun 훅에서 할당하고, VDF 본문에서 액세스하고, postrun 훅에서 해제합니다. 두 훅은 문장당 한 번 실행되며, VDF 본문은 행당 한 번 실행됩니다. .prerun<&Hook>().postrun<&Hook>()으로 등록합니다. 필수 시그니처는 다음과 같습니다: PrerunResult::set_user_data(void*)를 사용하여 상태를 저장하고, PostrunArgs::delete_state<T>()를 사용하여 이를 해제합니다. prerunset_user_data(new T{})를 호출하면, postrun반드시 delete_state<T>()를 호출해야 합니다 — SDK는 자동 해제하지 않습니다. PrerunArgs::type_at(i)는 행을 읽기 전에 각 인자의 선언된 SQL 타입을 노출합니다. 반환된 PrerunArgType의 조건식 is_int(), is_real(), is_str(), is_custom()은 열 타입을 반영합니다. 이를 prerun에서 인자 타입을 검증하거나 PrerunResult::request_buffer_size(n)을 호출하여 결과 버퍼 크기를 조정하는 데 사용합니다.

가변 인자 VDF

가변 인자 VDF는 임의의 SQL 타입의 임의 개수의 인자를 수용합니다. func 빌더에 .varargs()로 선언하며, 이는 .no_params().param(TYPE)과 상호 배타적입니다. 본문은 일반적인 고정 인자 타입 대신 vsql::VarArgs 인자를 받습니다.
가변 인자 등록은 VEF 프로토콜 3을 필요로 합니다. 이전 서버는 설치 시 확장을 거부합니다.
프레임워크는 가변 인자 VDF의 인자 개수 또는 타입을 검증할 수 없습니다. 모든 가변 인자 등록은 잘못된 입력에 PrerunResult::error()를 호출하거나 결과 버퍼 크기를 조정하기 위해 PrerunResult::request_buffer_size(n)을 호출하는 prerun 훅과 짝을 이루어야 합니다. 범위-기반 for 루프로 인자를 반복합니다. 각 AnyArg 요소는 값을 읽기 전에 타입 검사를 요구합니다: 액세서를 사용하기 전에 is_null()을 확인하세요 — 네 가지 모두 NULL 인자에서 정의되지 않습니다.

VEF_GENERATE_REGISTRATION

VEF_GENERATE_REGISTRATION은 확장 등록을 수행하지만 extern "C" 엔트리 포인트를 정의하지 않는 내부 _vef_do_register() 헬퍼를 생성합니다. vef_register 동작을 사용자 정의해야 할 때 사용합니다 — 예를 들어 테스트 빌드에서 등록 후 디스크립터를 패치하기 위해서입니다. 일반 확장의 경우 대신 VEF_GENERATE_ENTRY_POINTS를 사용합니다.

사용자 정의 타입 연산

전체 타입 연산 빌더 참조 — 인코딩, 디코딩, 비교, 해시, 내장 기본값, 매개변수화된 타입 — 는 타입 연산을 참조하세요.

미리보기 기능

다음 VEF 기능은 선택적으로 활성화하는 미리보기 헤더로 사용할 수 있습니다. ABI 및 API는 여전히 활발히 개발 중입니다. 전체 참조는 미리보기 기능을 참조하세요.

확장 등록 메타데이터 검사

INFORMATION_SCHEMA.EXTENSION_REGISTRATION은 로드된 각 확장의 인메모리 VEF 등록 구조체를 JSON 문서로 노출합니다. INSTALL EXTENSION 후 서버가 확장의 함수, 타입, 시스템 변수를 올바르게 해석했는지 확인하는 데 사용합니다.

참고 자료