Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
SET PERSIST vsql_allow_preview_extensions = ON;
INSTALL EXTENSION vsql_usage_counter;
# Counter starts at zero
# Both counters start at zero
SELECT VARIABLE_VALUE = 0 AS add_calls_starts_at_zero
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.add_calls';
add_calls_starts_at_zero
1
SELECT VARIABLE_VALUE = 0 AS null_calls_starts_at_zero
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.null_calls';
null_calls_starts_at_zero
1
# Basic addition works
SELECT vsql_usage_counter.add(3, 4);
vsql_usage_counter.add(3, 4)
Expand All @@ -28,24 +34,32 @@ FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.add_calls';
add_calls_is_three
1
# NULL input returns NULL but still increments counter
# NULL input returns NULL, increments both counters
SELECT vsql_usage_counter.add(NULL, 1) IS NULL AS null_returns_null;
null_returns_null
1
# Counter is now 4
# add_calls is now 4, null_calls is now 1
SELECT VARIABLE_VALUE = 4 AS add_calls_is_four
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.add_calls';
add_calls_is_four
1
# Counter visible in SHOW GLOBAL STATUS
SHOW GLOBAL STATUS LIKE 'vsql_usage_counter.add_calls';
SELECT VARIABLE_VALUE = 1 AS null_calls_is_one
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.null_calls';
null_calls_is_one
1
# Both counters visible in SHOW GLOBAL STATUS
SHOW GLOBAL STATUS LIKE 'vsql_usage_counter.%';
Variable_name Value
vsql_usage_counter.add_calls 4
vsql_usage_counter.null_calls 1
UNINSTALL EXTENSION vsql_usage_counter;
# Status variable gone after uninstall
SELECT COUNT(*) = 0 AS no_status_var_after_uninstall
# Status variables gone after uninstall
SELECT COUNT(*) = 0 AS no_status_vars_after_uninstall
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.add_calls';
no_status_var_after_uninstall
WHERE VARIABLE_NAME LIKE 'vsql_usage_counter.%';
no_status_vars_after_uninstall
1
SET PERSIST vsql_allow_preview_extensions = OFF;
RESET PERSIST vsql_allow_preview_extensions;
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# Test status variable increments for vsql_usage_counter extension.
# Verifies that add_calls increases by exactly 1 for each add() invocation
# and returns to zero after reinstall.
# Verifies that add_calls and null_calls increment correctly and are
# gone after uninstall.

SET PERSIST vsql_allow_preview_extensions = ON;
INSTALL EXTENSION vsql_usage_counter;

--echo # Counter starts at zero
--echo # Both counters start at zero
SELECT VARIABLE_VALUE = 0 AS add_calls_starts_at_zero
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.add_calls';
SELECT VARIABLE_VALUE = 0 AS null_calls_starts_at_zero
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.null_calls';

--echo # Basic addition works
SELECT vsql_usage_counter.add(3, 4);
Expand All @@ -26,20 +30,26 @@ SELECT VARIABLE_VALUE = 3 AS add_calls_is_three
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.add_calls';

--echo # NULL input returns NULL but still increments counter
--echo # NULL input returns NULL, increments both counters
SELECT vsql_usage_counter.add(NULL, 1) IS NULL AS null_returns_null;

--echo # Counter is now 4
--echo # add_calls is now 4, null_calls is now 1
SELECT VARIABLE_VALUE = 4 AS add_calls_is_four
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.add_calls';
SELECT VARIABLE_VALUE = 1 AS null_calls_is_one
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.null_calls';

--echo # Counter visible in SHOW GLOBAL STATUS
SHOW GLOBAL STATUS LIKE 'vsql_usage_counter.add_calls';
--echo # Both counters visible in SHOW GLOBAL STATUS
SHOW GLOBAL STATUS LIKE 'vsql_usage_counter.%';

UNINSTALL EXTENSION vsql_usage_counter;

--echo # Status variable gone after uninstall
SELECT COUNT(*) = 0 AS no_status_var_after_uninstall
--echo # Status variables gone after uninstall
SELECT COUNT(*) = 0 AS no_status_vars_after_uninstall
FROM performance_schema.global_status
WHERE VARIABLE_NAME = 'vsql_usage_counter.add_calls';
WHERE VARIABLE_NAME LIKE 'vsql_usage_counter.%';

SET PERSIST vsql_allow_preview_extensions = OFF;
RESET PERSIST vsql_allow_preview_extensions;
84 changes: 84 additions & 0 deletions villagesql/sdk/include/villagesql/abi/preview/status_var.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) 2026 VillageSQL Contributors
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, see <https://www.gnu.org/licenses/>.

#ifndef VILLAGESQL_ABI_PREVIEW_STATUS_VAR_H
#define VILLAGESQL_ABI_PREVIEW_STATUS_VAR_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

// Preview capability: "vsql::status_var"
//
// Allows extensions to expose read-only counters and gauges via SHOW STATUS /
// performance_schema.global_status. Declare a StatusVarCapability, populate it
// with add_int() / add_double() descriptors, and pass
// it to .with() on the extension builder.
//
// The server registers the declared variables when the extension loads and
// unregisters them when it unloads.

#define VEF_PREVIEW_STATUS_VAR_NAME "vsql::status_var"

// Capability ABI version compiled into this SDK snapshot.
#define VEF_PREVIEW_STATUS_VAR_ABI_VERSION 1

// Status variable type. Unlike system variables (which are configurable),
// status variables are read-only counters and gauges exposed via SHOW STATUS.
typedef enum {
VEF_STATUS_VAR_INT = 0, // long long counter/gauge (shown as unsigned)
VEF_STATUS_VAR_DOUBLE = 1, // double gauge
} vef_status_var_type_t;

typedef struct {
// Variable name (without extension prefix). Encoded using UTF-8.
const char *name;

vef_status_var_type_t type;

// Pointer to storage in the extension .so. Must remain valid for the
// lifetime of the extension. The extension writes to this; the server reads
// it at SHOW STATUS time.
union {
long long *integer_ptr;
double *double_ptr;
};
} vef_status_var_desc_t;

// Descriptor list passed from extension to server at populate time.
// The extension keeps this struct alive for its entire lifetime; the server
// reads it only during on_populate and stores its own copies of the data.
typedef struct {
// Array of pointers to status variable descriptors. Must remain valid for
// the lifetime of the extension.
const vef_status_var_desc_t *const *vars;
uint32_t var_count;
} vef_status_var_descriptor_list_t;

typedef struct {
// Capability ABI version. Always the first field in every capability vtable.
uint32_t version;

// version >= 1: no extension-callable functions needed; the server reads the
// descriptor list from the extension's StatusVarCapability at populate time.
} vef_preview_status_var_t;

#ifdef __cplusplus
}
#endif

#endif // VILLAGESQL_ABI_PREVIEW_STATUS_VAR_H
28 changes: 0 additions & 28 deletions villagesql/sdk/include/villagesql/abi/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -877,30 +877,6 @@ typedef struct {
};
} vef_sys_var_desc_t;

// Status variable type. Unlike system variables (which are configurable),
// status variables are read-only counters and gauges exposed via SHOW STATUS.
typedef enum {
VEF_STATUS_VAR_INT = 0, // long long counter/gauge (shown as unsigned)
VEF_STATUS_VAR_DOUBLE = 1, // double gauge
} vef_status_var_type_t;

typedef struct {
vef_protocol_t protocol;

// Variable name (without extension prefix). Encoded using UTF-8.
const char *name;

vef_status_var_type_t type;

// Pointer to storage in the extension .so. Must remain valid for the
// lifetime of the extension. The extension writes to this; the server reads
// it at SHOW STATUS time.
union {
long long *integer_ptr;
double *double_ptr;
};
} vef_status_var_desc_t;

// Forward declaration so vef_required_capability_t can reference it.
typedef struct vef_registration_t vef_registration_t;

Expand Down Expand Up @@ -966,10 +942,6 @@ typedef struct vef_registration_t {
unsigned int sys_var_count;
vef_sys_var_desc_t **sys_vars;

// protocol >= VEF_PROTOCOL_2
unsigned int status_var_count;
vef_status_var_desc_t **status_vars;

// protocol >= VEF_PROTOCOL_2
// Preview capabilities required by this extension. Each entry names a
// capability the extension needs (e.g. "vsql::ping"). The server populates
Expand Down
26 changes: 9 additions & 17 deletions villagesql/sdk/include/villagesql/detail/vef_register.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,14 @@ void vef_fill_sys_var_ptrs(vef_sys_var_desc_t **arr, const Ext &e,
...);
}

// Fills arr[I] with the vef_status_var_desc_t* for each status variable.
template <typename Ext, size_t... Is>
void vef_fill_status_var_ptrs(vef_status_var_desc_t **arr, const Ext &e,
std::index_sequence<Is...>) {
((arr[Is] = const_cast<vef_status_var_desc_t *>(
&e.template status_var_at<Is>().desc)),
...);
}
// has_extension_data<T>: true when CapabilityTraits<T> provides extension_data.
template <typename Traits, typename Cap, typename = void>
struct has_extension_data : std::false_type {};
template <typename Traits, typename Cap>
struct has_extension_data<
Traits, Cap,
std::void_t<decltype(Traits::extension_data(std::declval<Cap *>()))>>
: std::true_type {};

// Detection idiom: true if CapabilityTraits has a DescriptorType member.
template <typename T, typename = void>
Expand Down Expand Up @@ -232,7 +232,7 @@ const char *vef_check_params_cache(const Ext &e, std::index_sequence<Is...>) {
// The counts are explicit template parameters so that array sizes are
// compile-time constants without relying on VLAs.
template <typename Ext, size_t FuncCount, size_t TypeCount, size_t SysVarCount,
size_t StatusVarCount, size_t RequiredCapabilityCount>
size_t RequiredCapabilityCount>
vef_registration_t *vef_register_impl(vef_registration_t &reg,
bool &initialized,
vef_register_arg_t *arg, const Ext &ext) {
Expand Down Expand Up @@ -264,8 +264,6 @@ vef_registration_t *vef_register_impl(vef_registration_t &reg,
static vef_func_desc_t *func_ptrs[FuncCount > 0 ? FuncCount : 1];
static vef_type_desc_t *type_ptrs[TypeCount > 0 ? TypeCount : 1];
static vef_sys_var_desc_t *sys_var_ptrs[SysVarCount > 0 ? SysVarCount : 1];
static vef_status_var_desc_t
*status_var_ptrs[StatusVarCount > 0 ? StatusVarCount : 1];
static vef_required_capability_t required_capability_reqs
[RequiredCapabilityCount > 0 ? RequiredCapabilityCount : 1];

Expand All @@ -281,10 +279,6 @@ vef_registration_t *vef_register_impl(vef_registration_t &reg,
vef_fill_sys_var_ptrs(sys_var_ptrs, ext,
std::make_index_sequence<SysVarCount>{});
}
if constexpr (StatusVarCount > 0) {
vef_fill_status_var_ptrs(status_var_ptrs, ext,
std::make_index_sequence<StatusVarCount>{});
}
if constexpr (RequiredCapabilityCount > 0) {
vef_fill_required_capability_reqs(
required_capability_reqs, ext,
Expand Down Expand Up @@ -318,8 +312,6 @@ vef_registration_t *vef_register_impl(vef_registration_t &reg,
reg.types = TypeCount > 0 ? type_ptrs : nullptr;
reg.sys_var_count = SysVarCount;
reg.sys_vars = SysVarCount > 0 ? sys_var_ptrs : nullptr;
reg.status_var_count = StatusVarCount;
reg.status_vars = StatusVarCount > 0 ? status_var_ptrs : nullptr;
reg.required_capability_count = RequiredCapabilityCount;
reg.required_capabilities =
RequiredCapabilityCount > 0 ? required_capability_reqs : nullptr;
Expand Down
71 changes: 34 additions & 37 deletions villagesql/sdk/include/villagesql/extension_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ struct ExtensionBuilder {
static constexpr size_t kFuncCount = std::tuple_size_v<FuncTuple>;
static constexpr size_t kTypeCount = std::tuple_size_v<TypeTuple>;
static constexpr size_t kSysVarCount = 0;
static constexpr size_t kStatusVarCount = 0;
static constexpr size_t kRequiredCapabilityCount = 0;
static constexpr bool kHasVsqlGlobals = false;

Expand Down Expand Up @@ -123,49 +122,47 @@ constexpr auto make_extension(std::string_view /*name*/,
// descriptors after registration for testing). Otherwise use
// VEF_GENERATE_ENTRY_POINTS which generates the full extern "C" entry points.

#define VEF_GENERATE_REGISTRATION(ext) \
namespace { \
vef_registration_t _vef_reg; \
bool _vef_reg_initialized = false; \
} \
\
static vef_registration_t *_vef_do_register(vef_register_arg_t *arg) { \
using namespace villagesql::extension_builder; \
static constexpr auto kExt = (ext); \
using ExtType = decltype(kExt); \
return villagesql::detail::vef_register_impl< \
decltype(kExt), ExtType::kFuncCount, ExtType::kTypeCount, \
ExtType::kSysVarCount, ExtType::kStatusVarCount, \
ExtType::kRequiredCapabilityCount>(_vef_reg, _vef_reg_initialized, \
arg, kExt); \
#define VEF_GENERATE_REGISTRATION(ext) \
namespace { \
vef_registration_t _vef_reg; \
bool _vef_reg_initialized = false; \
} \
\
static vef_registration_t *_vef_do_register(vef_register_arg_t *arg) { \
using namespace villagesql::extension_builder; \
static constexpr auto kExt = (ext); \
using ExtType = decltype(kExt); \
return villagesql::detail::vef_register_impl< \
decltype(kExt), ExtType::kFuncCount, ExtType::kTypeCount, \
ExtType::kSysVarCount, ExtType::kRequiredCapabilityCount>( \
_vef_reg, _vef_reg_initialized, arg, kExt); \
}

// VEF_GENERATE_ENTRY_POINTS
//
// Generates the extern "C" vef_register and vef_unregister functions.
// Must be called in a .cc file, not a header (defines functions/variables).

#define VEF_GENERATE_ENTRY_POINTS(ext) \
namespace { \
vef_registration_t vef_reg_; \
bool vef_reg_initialized_ = false; \
} \
\
extern "C" vef_registration_t *vef_register(vef_register_arg_t *arg) { \
using namespace villagesql::extension_builder; \
static constexpr auto kExt = (ext); \
using ExtType = decltype(kExt); \
return villagesql::detail::vef_register_impl< \
decltype(kExt), ExtType::kFuncCount, ExtType::kTypeCount, \
ExtType::kSysVarCount, ExtType::kStatusVarCount, \
ExtType::kRequiredCapabilityCount>(vef_reg_, vef_reg_initialized_, \
arg, kExt); \
} \
\
extern "C" void vef_unregister(vef_unregister_arg_t *arg, \
vef_registration_t *reg) { \
(void)arg; \
(void)reg; \
#define VEF_GENERATE_ENTRY_POINTS(ext) \
namespace { \
vef_registration_t vef_reg_; \
bool vef_reg_initialized_ = false; \
} \
\
extern "C" vef_registration_t *vef_register(vef_register_arg_t *arg) { \
using namespace villagesql::extension_builder; \
static constexpr auto kExt = (ext); \
using ExtType = decltype(kExt); \
return villagesql::detail::vef_register_impl< \
decltype(kExt), ExtType::kFuncCount, ExtType::kTypeCount, \
ExtType::kSysVarCount, ExtType::kRequiredCapabilityCount>( \
vef_reg_, vef_reg_initialized_, arg, kExt); \
} \
\
extern "C" void vef_unregister(vef_unregister_arg_t *arg, \
vef_registration_t *reg) { \
(void)arg; \
(void)reg; \
}

#endif // VILLAGESQL_SDK_EXTENSION_BUILDER_H
Loading
Loading