> ## Documentation Index
> Fetch the complete documentation index at: https://villagesql.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# C++ 개발

> C++ 확장을 위한 VDF 작성 심층 참조 — 인자 및 결과 타입, 예외 안전성, 집계, prerun/postrun, 가변 인자, 등록.

이 가이드는 C++ VDF 구현을 작성하기 위한 심층 참조입니다. 종단간 빌드 단계를 다루는 [C++로 확장 만들기](/docs/ko/mysql-9.7/stable/create), 그리고 테스트-반복 루프를 다루는 [C++ 테스트](/docs/ko/mysql-9.7/stable/testing)의 동반 자료입니다.

<Warning>
  VEF 프로토콜 3은 v0.0.4부터 안정적입니다. 프로토콜 4는 개발 중이며, 선택적으로 활성화하는 개발 ABI 헤더(`-DVSQL_USE_DEV_ABI=ON`)를 통해서만 사용할 수 있습니다. 이전 프로토콜 2로 빌드된 확장은 서버에서 거부되며 재빌드가 필요합니다.
</Warning>

## 확장 함수 작성

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

```cpp theme={null}
#include <villagesql/vsql.h>
```

<h2 id="exception-safety">
  예외 안전성
</h2>

예외를 가두는 것은 확장의 책임입니다. SDK는 사용자의 구현을 `try`/`catch`로
감싸지 않으며 서버도 마찬가지이므로, 엔트리 포인트를 벗어난 예외는 그 위
어디에도 핸들러가 없는 상태로 C ABI 경계를 넘어 스택을 풀고 서버 프로세스가
종료됩니다. 일반적인 SQL 입력이 이 상황에 도달합니다: `std::stoi()`는 숫자가
아닌 문자열에서 예외를 던지고, `nlohmann::json::dump()`는 유효한 UTF-8이 아닌
텍스트에서 예외를 던집니다.

등록하는 모든 엔트리 포인트의 본문을 감싸세요: VDF, 집계 결과 함수, 그리고
사용자 정의 타입의 `from_string`/`to_string` 연산.

```cpp theme={null}
#include <villagesql/vsql.h>
#include <string>

using namespace vsql;

void to_int_impl(StringArg in, IntResult out) {
  if (in.is_null()) { out.set_null(); return; }
  try {
    out.set(std::stoi(std::string(in.value())));  // throws on bad input
  } catch (const std::exception &e) {
    out.warning(e.what());
  } catch (...) {
    out.warning("unexpected error");
  }
}
```

`const std::exception &`에 더해 `...`도 잡으세요: 의존성이 `std::exception`을
상속하지 않는 타입을 던질 수 있으며, 그런 예외도 여전히 서버를 종료시킵니다.
`warning()`과 `error()` 중 무엇을 사용할지는 다른 모든 실패와 동일한 기준으로
선택합니다 — 문장이 계속 진행되어야 하는 잘못된 입력에는 `warning()`, 계속
실행이 안전하지 않은 경우에는 `error()`입니다.

<h2 id="argument-and-result-types">
  인자 및 결과 타입
</h2>

VDF 매개변수 및 결과는 타입 안전 인자 및 결과 타입으로 전달됩니다.
프레임워크는 함수 시그니처에서 이를 감지하고 자동으로 적응합니다 —
`make_func` 등록 구문은 변경되지 않습니다.

**인자 타입:** `IntArg`, `RealArg`, `StringArg`, `CustomArg` — 각각
`is_null()` 및 `value()`를 제공합니다. 매개변수화된 사용자 정의 타입의 경우,
`CustomArgWith<P>`는 캐시된 구문 분석된 매개변수 구조체를 반환하는 `params()`
액세서를 추가합니다(참조: [매개변수화된 타입](/docs/ko/mysql-9.7/stable/type-operations#parameterized-types)).

**결과 타입:** `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)`를 사용합니다. 두 메시지 모두 필요한 경우 서버의 내부 오류 버퍼에 맞게 잘립니다.

**스칼라 예제** — 두 정수를 더합니다:

```cpp theme={null}
using namespace vsql;

void add_impl(IntArg a, IntArg b, IntResult out) {
  if (a.is_null() || b.is_null()) { out.set_null(); return; }
  out.set(a.value() + b.value());
}

// Registration is unchanged:
make_func<&add_impl>("add").returns(INT).param(INT).param(INT).build();
```

**이진 예제** — 사용자 정의 타입 버퍼를 제자리에서 변환합니다:

```cpp theme={null}
using namespace vsql;

void rot13_impl(CustomArg in, CustomResult out) {
  if (in.is_null()) { out.set_null(); return; }
  auto src = in.value();   // vsql::Span<const unsigned char>
  auto dst = out.buffer(); // vsql::Span<unsigned char>
  for (size_t i = 0; i < src.size(); i++) { dst[i] = transform(src[i]); }
  out.set_length(src.size());
}
```

`StringResult` 및 `CustomResult`의 경우, `buffer()`에 쓴 다음 작성된 바이트
수와 함께 `set_length()`를 호출합니다. `buffer().size()`는 최대 용량입니다.

사용자 정의 타입을 반환하는 VDF(`returns(CUSTOM(MYTYPE))`)의 경우, 서버는
결과 버퍼를 해석된 반환 타입의 `persisted_length`에 맞춰 자동으로 크기
조정합니다 — 확장 작성자는 이 경우 함수 빌더에 `.buffer_size(...)`를 선언할
필요가 없습니다. `prerun`이 버퍼를 더 크게 늘리면 그 큰 크기가 유지됩니다.
예를 들어 이 덕분에 `SVECTOR::from_string('[…1024 floats…]')`이 결과 버퍼
공간이 부족해지는 일 없이 넓은 벡터를 인코딩할 수 있습니다.

동일한 확장 내에서 함수 간에 다른 스타일을 사용할 수 있습니다 — 각 함수의
스타일은 자체 시그니처에 따라 결정됩니다.

<h2 id="aggregate-vdfs">
  집계 VDF
</h2>

집계 VDF는 각 `GROUP BY` 그룹 내에서 여러 행에 걸쳐 상태를 누적하고 그룹당 단일
결과를 반환합니다. SQL `SUM` 또는 `COUNT`와 유사합니다. 등록하려면
`make_aggregate_func<State, &result_fn>("name")`을 사용합니다. State 타입은
그룹별 누적 버퍼입니다. `prerun` 및 `postrun`은 이를 할당하고 삭제하기 위해
자동으로 생성됩니다.

결과 함수는 `void(const State&, ResultType)` 시그니처를 가져야 하며,
`ResultType`은 `IntResult`, `RealResult`, `StringResult`, `CustomResult`,
또는 `CustomResultWith<P>` 중 하나입니다. `out.set(value)`를 호출하여 값을
반환하거나 `out.set_null()`을 호출하여 SQL NULL을 반환합니다.

`.clear<>()` 및 `.accumulate<>()`가 모두 필요합니다. 빌더는 이를 컴파일
타임에(`build()`를 통해) 강제하고, 서버는 `INSTALL EXTENSION` 시점에 다시
검증합니다 — `clear`는 상태를 재설정하고, `accumulate`는 행을 결합하며, 결과
함수는 최종 상태를 읽습니다.

```cpp theme={null}
#include <villagesql/vsql.h>
#include <optional>

using namespace vsql;

// State type: nullopt means no non-NULL rows seen yet.
using SumState = std::optional<long long>;

void my_clear(SumState &s) { s = std::nullopt; }
void my_acc(SumState &s, IntArg v) {
  if (!v.is_null()) s = s.value_or(0) + v.value();
}
void my_result(const SumState &s, IntResult out) {
  if (!s.has_value()) { out.set_null(); return; }
  out.set(s.value());
}

// Registration:
// make_aggregate_func<SumState, &my_result>("my_sum")
//     .returns(INT)
//     .param(INT)
//     .clear<&my_clear>()
//     .accumulate<&my_acc>()
//     .build()
```

빌더 메서드 작동 방식:

* `make_aggregate_func<State, &result_fn>()`은 `prerun` 및 `postrun`을 자동 생성합니다(`State`를 값 초기화하고 삭제합니다).
* `.clear<&fn>()`은 사용자의 `void(State&)` 재설정 함수를 등록합니다.
* `.accumulate<&fn>()`은 사용자의 `void(State&, TypedArgs...)` 결합 함수를 등록합니다. `TypedArgs`는 함수 시그니처에서 추론됩니다(`IntArg`, `StringArg` 등).
* 결과 타입(`IntResult`, `RealResult` 등)은 결과 함수 시그니처에서 추론됩니다.

NULL을 절대 반환하지 않는 카운터의 경우, 단순한 상태 타입을 사용합니다:

```cpp theme={null}
using CountState = long long;
void count_clear(CountState &s) { s = 0; }
void count_acc(CountState &s, IntArg v) { if (!v.is_null()) s++; }
void count_result(const CountState &s, IntResult out) { out.set(s); }
```

`StringResult` 집계 VDF는 텍스트를 반환합니다: 결과는 `utf8mb4_bin` 문자
집합 및 콜레이션을 보고하므로, 클라이언트는 이를 16진수가 아닌 문자로 표시합니다
— 스칼라 VDF STRING 경로와 동일합니다. 또한 `.max_result_length(n)`(선택적으로
활성화하는 개발 ABI 헤더(`-DVSQL_USE_DEV_ABI=ON`)와 프로토콜 4가 필요합니다)을
동일한 방식으로 존중하여, 구체화된 집계 결과(`GROUP BY`/`DISTINCT` 임시 테이블,
`CREATE TABLE ... SELECT`, 또는 UNION)의 크기를 조정하여 인자 너비에서
잘리지 않도록 합니다. 크기 조정 규칙 및 상한선은
[사용자 정의 버퍼 크기](/docs/ko/mysql-9.7/stable/create#custom-buffer-sizes)를
참조하세요.

<h2 id="per-statement-state-prerun-and-postrun">
  문장별 상태 (Prerun 및 Postrun)
</h2>

일부 VDF는 단일 쿼리가 처리하는 모든 행에 걸친 상태를 필요로 합니다 — 호출
카운터, 캐시된 결과, 열린 리소스. **prerun** 훅에서 할당하고, VDF 본문에서
액세스하고, **postrun** 훅에서 해제합니다. 두 훅은 문장당 한 번 실행되며,
VDF 본문은 행당 한 번 실행됩니다.

`.prerun<&Hook>()` 및 `.postrun<&Hook>()`으로 등록합니다. 필수 시그니처는
다음과 같습니다:

| 훅       | 필수 시그니처                                      |
| ------- | -------------------------------------------- |
| Prerun  | `void(vsql::PrerunArgs, vsql::PrerunResult)` |
| Postrun | `void(vsql::PostrunArgs)`                    |

`PrerunResult::set_user_data(void*)`를 사용하여 상태를 저장하고,
`PostrunArgs::delete_state<T>()`를 사용하여 이를 해제합니다. `prerun`이
`set_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)`을 호출하여 결과
버퍼 크기를 조정하는 데 사용합니다.

```cpp theme={null}
#include <villagesql/vsql.h>
using namespace vsql;

struct CallCounter { long long n = 0; };

void ba_call_index_prerun(PrerunArgs, PrerunResult out) {
  out.set_user_data(new CallCounter{});
}

void ba_call_index(CallCounter &state, IntResult out) {
  state.n++;
  out.set(state.n);
}

void ba_call_index_postrun(PostrunArgs args) {
  args.delete_state<CallCounter>();
}

// Registration:
// make_func<&ba_call_index>("ba_call_index")
//     .returns(INT).no_params()
//     .prerun<&ba_call_index_prerun>()
//     .postrun<&ba_call_index_postrun>()
//     .build()
```

<h2 id="varargs-vdfs">
  가변 인자 VDF
</h2>

**가변 인자** VDF는 임의의 SQL 타입의 임의 개수의 인자를 수용합니다. func
빌더에 `.varargs()`로 선언하며, 이는 `.no_params()` 및 `.param(TYPE)`과 상호
배타적입니다. 본문은 일반적인 고정 인자 타입 대신 `vsql::VarArgs` 인자를
받습니다.

<Warning>
  가변 인자 등록은 VEF 프로토콜 3을 필요로 합니다. 이전 서버는 설치 시 확장을
  거부합니다.
</Warning>

프레임워크는 가변 인자 VDF의 인자 개수 또는 타입을 검증할 수 없습니다. 모든
가변 인자 등록은 잘못된 입력에 `PrerunResult::error()`를 호출하거나 결과
버퍼 크기를 조정하기 위해 `PrerunResult::request_buffer_size(n)`을 호출하는
prerun 훅과 짝을 이루어야 합니다.

범위-기반 for 루프로 인자를 반복합니다. 각 `AnyArg` 요소는 값을 읽기 전에
타입 검사를 요구합니다:

| 조건식           | 액세서           | 반환 타입                             |
| ------------- | ------------- | --------------------------------- |
| `is_int()`    | `as_int()`    | `long long`                       |
| `is_real()`   | `as_real()`   | `double`                          |
| `is_str()`    | `as_str()`    | `std::string_view`                |
| `is_custom()` | `as_custom()` | `vsql::Span<const unsigned char>` |

액세서를 사용하기 전에 `is_null()`을 확인하세요 — 네 가지 모두 NULL 인자에서 정의되지 않습니다.

`PrerunArgType::custom_type()`은 인자의 사용자 정의 타입 이름을
`std::string_view`로 반환합니다 — 사용자 정의 타입이 아닌 인자에서는 비어
있습니다. `is_custom()`만으로는 서버에 등록된 모든 사용자 정의 타입을
수용합니다. `custom_type()` 비교와 짝을 이루면 가변 인자 호출을 하나의 특정
타입으로 좁힐 수 있습니다:

```cpp theme={null}
#include <villagesql/vsql.h>
#include <cstring>
using namespace vsql;

// POINT2D stores two little-endian int32 (x, y) in 8 bytes — registration
// omitted here; see Custom Types for make_type<>().

void point_path_prerun(PrerunArgs args, PrerunResult out) {
  if (args.size() == 0) {
    out.error("point_path requires at least one point");
    return;
  }
  for (size_t i = 0; i < args.size(); i++) {
    auto t = args.type_at(i);
    if (!t.is_custom() || t.custom_type() != "POINT2D") {
      out.error("point_path: every argument must be a POINT2D");
      return;
    }
  }
  out.request_buffer_size(16 + args.size() * 32);
}

void point_path(VarArgs args, StringResult out) {
  auto buf = out.buffer();
  size_t off = 0;
  bool first = true;
  for (auto a : args) {
    if (a.is_null()) { out.set_null(); return; }
    auto bytes = a.as_custom();
    int x, y;
    memcpy(&x, bytes.data(), 4);
    memcpy(&y, bytes.data() + 4, 4);
    if (!first) {
      memcpy(buf.data() + off, " -> ", 4);
      off += 4;
    }
    first = false;
    off += static_cast<size_t>(
        snprintf(buf.data() + off, buf.size() - off, "(%d,%d)", x, y));
  }
  out.set_length(off);
}

// Registration:
// make_func<&point_path>("point_path")
//     .returns(STRING).varargs()
//     .prerun<&point_path_prerun>()
//     .build()
```

```sql theme={null}
SELECT point_path(POINT2D::from_string('0,0'), POINT2D::from_string('1,2'));
-- (0,0) -> (1,2)
SELECT point_path('1,2');
-- ERROR 1123 (HY000): Can't initialize function 'point_path'; point_path: every argument must be a POINT2D
```

```cpp theme={null}
#include <villagesql/vsql.h>
#include <cstring>
using namespace vsql;

constexpr size_t kBytearrayLen = 4;

void ba_concat_all_prerun(PrerunArgs args, PrerunResult out) {
  if (args.size() == 0) {
    out.error("ba_concat_all requires at least one argument");
    return;
  }
  for (size_t i = 0; i < args.size(); i++) {
    auto t = args.type_at(i);
    if (!t.is_custom() && !t.is_str()) {
      out.error("ba_concat_all: argument " + std::to_string(i) +
                " must be BYTEARRAY");
      return;
    }
  }
  out.request_buffer_size(args.size() * kBytearrayLen);
}

void ba_concat_all(VarArgs args, StringResult out) {
  auto dst = out.buffer();
  size_t off = 0;
  for (auto a : args) {
    if (a.is_null() || !a.is_custom()) { out.set_null(); return; }
    auto bytes = a.as_custom();
    std::memcpy(dst.data() + off, bytes.data(), bytes.size());
    off += bytes.size();
  }
  out.set_length(off);
}

// Registration:
// make_func<&ba_concat_all>("ba_concat_all")
//     .returns(STRING).varargs()
//     .prerun<&ba_concat_all_prerun>()
//     .build()
```

<h2 id="extension-load-and-unload-hooks">
  확장 로드 및 언로드 훅
</h2>

확장은 때때로 호출마다가 아니라 로드마다 한 번씩 수행해야 하는 작업이 있습니다
— CPU에 특화된 구현을 선택하거나, VDF가 이후에 읽을 조회 테이블을 구축하는
작업입니다. 그 작업은 확장 빌더의 `.on_init<&Fn>()`으로 등록하고, 대응하는
정리 작업은 `.on_deinit<&Fn>()`으로 등록합니다. 둘 다 `void()` 시그니처를 갖는
함수를 받습니다.

<Warning>
  `.on_init<&Fn>()` 및 `.on_deinit<&Fn>()`은 선택적으로 활성화하는 개발 ABI
  헤더(`-DVSQL_USE_DEV_ABI=ON`)에만 선언되어 있습니다. 기본 안정 ABI 빌드에는 두
  메서드가 모두 없으므로, 훅을 등록하면 컴파일에 실패합니다.
</Warning>

`on_init`은 확장이 로드될 때마다 실행됩니다: `INSTALL EXTENSION` 시점, 그리고
서버가 확장을 검증하고 수락한 후 서버가 시작될 때마다 실행됩니다. 서버가
거부한 확장에 대해서는 절대 실행되지 않습니다. `on_deinit`은 언로드 시점 —
`UNINSTALL EXTENSION` 또는 서버 종료 시 — 에 실행됩니다.

두 훅은 모두 **서버에 액세스할 수 없는** 상태로 확장 프로세스 내부에서
실행됩니다: SQL을 실행하거나, 시스템 변수를 읽거나, 서버 상태에 도달할 수
없습니다. 그렇기 때문에 서버와 통신해야 하는 설정 작업에는 적합하지 않습니다 —
그런 작업에는 미리보기 기능의 populate 단계를 사용하세요.

```cpp theme={null}
#include <villagesql/vsql.h>
#include <cmath>
using namespace vsql;

static float *g_sqrt_table = nullptr;

void build_table() {
  g_sqrt_table = new float[256];
  for (int i = 0; i < 256; i++) g_sqrt_table[i] = std::sqrt(float(i));
}

void free_table() {
  delete[] g_sqrt_table;
  g_sqrt_table = nullptr;
}

void fast_sqrt(IntArg n, RealResult out) {
  if (n.is_null() || n.value() < 0 || n.value() > 255) {
    out.set_null();
    return;
  }
  out.set(g_sqrt_table[n.value()]);
}

VEF_GENERATE_ENTRY_POINTS(
    make_extension()
        .on_init<&build_table>()
        .on_deinit<&free_table>()
        .func(make_func<&fast_sqrt>("fast_sqrt")
                  .returns(REAL)
                  .param(INT)
                  .build()))
```

`INSTALL EXTENSION my_extension` 후에 `SELECT fast_sqrt(16)`은 `4`를
반환합니다 — `build_table`이 이미 실행된 경우에만 도달할 수 있는 결과입니다.

<Note>
  `on_deinit`은 `VEF_GENERATE_ENTRY_POINTS`가 생성하는 `vef_unregister` 엔트리
  포인트에서 호출됩니다. `VEF_GENERATE_REGISTRATION`은 그 엔트리 포인트를
  정의하지 않으므로, 이를 사용하는 확장은 직접 작성한 `vef_unregister`에서 정리
  작업을 호출해야 합니다.
</Note>

## VEF\_GENERATE\_REGISTRATION

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

```cpp theme={null}
VEF_GENERATE_REGISTRATION(
    make_extension()
        .func(make_func<&my_impl>("my_func").returns(INT).build()))

// Then define your own extern "C" vef_register/vef_unregister that call
// _vef_do_register() and optionally modify the result.
```

## 사용자 정의 타입 연산

전체 타입 연산 빌더 참조 — 인코딩, 디코딩, 비교, 해시, 내장 기본값, 매개변수화된 타입 — 는 [타입 연산](/docs/ko/mysql-9.7/stable/type-operations)을 참조하세요.

## 미리보기 기능

다음 VEF 기능은 선택적으로 활성화하는 미리보기 헤더로 사용할 수 있습니다. ABI 및 API는 여전히 활발히 개발 중입니다. 전체 참조는 [미리보기 기능](/docs/ko/mysql-9.7/stable/preview-capabilities)을 참조하세요.

* **확장 시스템 변수** — [미리보기 기능 → 시스템 변수](/docs/ko/mysql-9.7/stable/preview-capabilities#system-variables)
* **확장 상태 변수** — [미리보기 기능 → 상태 변수](/docs/ko/mysql-9.7/stable/preview-capabilities#status-variables)
* **키링 액세스** — [미리보기 기능 → 키링 액세스](/docs/ko/mysql-9.7/stable/preview-capabilities#keyring-access)
* **컬럼 저장** — [미리보기 기능 → 컬럼 저장](/docs/ko/mysql-9.7/stable/preview-capabilities#column-storage)

## 확장 등록 메타데이터 검사

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

```sql theme={null}
SELECT EXTENSION_NAME, NEGOTIATED_PROTOCOL, REGISTRATION_JSON
FROM INFORMATION_SCHEMA.EXTENSION_REGISTRATION
WHERE EXTENSION_NAME = 'my_ext';
```

| 열                     | 타입                | 설명                                                                |
| --------------------- | ----------------- | ----------------------------------------------------------------- |
| `EXTENSION_NAME`      | `VARCHAR(64)`     | 설치된 확장의 이름입니다.                                                    |
| `NEGOTIATED_PROTOCOL` | `BIGINT UNSIGNED` | 확장과 서버 간에 협상된 VEF 프로토콜 버전입니다.                                     |
| `REGISTRATION_JSON`   | `VARCHAR(65535)`  | `funcs` 및 `types` 배열을 포함하는 `vef_registration_t` 구조체의 JSON 직렬화입니다. |

## 참고 자료

* [C++로 확장 만들기](/docs/ko/mysql-9.7/stable/create) — 종단간 빌드 단계, CMake 설정 및 설치
* [C++ 테스트](/docs/ko/mysql-9.7/stable/testing) — 로컬 개발 서버, MTR 및 실패 디버깅
* [타입 연산](/docs/ko/mysql-9.7/stable/type-operations) — 인코딩, 디코딩, 비교, 해시, 매개변수화된 타입
* [C++ API 참조](/docs/ko/mysql-9.7/stable/extension-api-reference) — VDF 계약, null 처리 및 버퍼 크기 조정
* [확장 아키텍처](/docs/ko/mysql-9.7/stable/architecture) — 라이프사이클, Victionary 캐싱, 성능 패턴 및 보안 모델
