use crate::build::actor::{BuildSqlSurfaceFlags, BuildSqlUpdatePolicy};
use proc_macro2::TokenStream;
use quote::quote;
pub(super) struct SqlSurfaceTokens {
surfaces: BuildSqlSurfaceFlags,
update_policy: Option<BuildSqlUpdatePolicy>,
has_entities: bool,
reset_statements: TokenStream,
}
impl SqlSurfaceTokens {
pub(super) fn empty(
surfaces: BuildSqlSurfaceFlags,
update_policy: Option<BuildSqlUpdatePolicy>,
) -> Self {
Self {
surfaces,
update_policy,
has_entities: false,
reset_statements: quote!(),
}
}
pub(super) fn push_entity(&mut self, entity_name: &str) {
self.has_entities = true;
self.reset_statements
.extend(sql_surface_reset_statement(entity_name));
}
fn readonly_dispatch_tokens(&self) -> TokenStream {
let introspection_guard = if self.surfaces.introspection_enabled() {
quote!()
} else {
quote! {
if dispatch.requires_introspection() {
return Err(::icydb::Error::from_runtime_boundary(
::icydb::diagnostic::RuntimeBoundaryCode::SqlIntrospectionDisabled,
::icydb::ErrorOrigin::Interface,
));
}
}
};
let entity_dispatch = if self.has_entities {
quote! {
db()?.execute_trusted_sql_query_with_perf_attribution(sql)
}
} else {
empty_sql_surface_query_dispatch()
};
let show_entities_dispatch = if self.has_entities {
show_entities_dispatch()
} else {
empty_sql_surface_query_dispatch()
};
quote! {
#[cfg(feature = "sql")]
fn __icydb_query_dispatch(
sql: &str,
) -> Result<
(
::icydb::db::sql::SqlQueryResult,
::icydb::db::SqlQueryPerfAttribution,
),
::icydb::Error,
> {
let dispatch = ::icydb::__macro::sql_statement_dispatch(sql)?;
#introspection_guard
match dispatch.entity_name() {
None => #show_entities_dispatch,
Some(_entity) => #entity_dispatch,
}
}
}
}
fn reset_helper_tokens(&self) -> TokenStream {
let reset_statements = &self.reset_statements;
if reset_statements.is_empty() {
quote! {
#[cfg(feature = "sql")]
const fn icydb_sql_surface_reset_all_tables() {}
}
} else {
quote! {
#[cfg(feature = "sql")]
fn icydb_sql_surface_reset_all_tables() -> Result<(), ::icydb::Error> {
#reset_statements
Ok(())
}
}
}
}
fn ddl_dispatch_tokens(&self) -> TokenStream {
let entity_dispatch = if self.has_entities {
quote! {
db()?.execute_admin_sql_ddl(sql)
}
} else {
quote! {
Err(::icydb::Error::from_runtime_boundary(
::icydb::diagnostic::RuntimeBoundaryCode::SqlDdlEntityNotConfigured,
::icydb::ErrorOrigin::Interface,
))
}
};
quote! {
#[cfg(feature = "sql")]
fn icydb_sql_surface_ddl_dispatch(
sql: &str,
) -> Result<::icydb::db::sql::SqlQueryResult, ::icydb::Error> {
match ::icydb::__macro::sql_statement_entity_name(sql)?.as_deref() {
None => Err(::icydb::Error::from_runtime_boundary(
::icydb::diagnostic::RuntimeBoundaryCode::SqlDdlTargetRequired,
::icydb::ErrorOrigin::Interface,
)),
Some(_entity) => #entity_dispatch,
}
}
}
}
fn update_dispatch_tokens(&self) -> TokenStream {
let entity_dispatch = match (self.has_entities, self.update_policy) {
(true, Some(BuildSqlUpdatePolicy::PublicPrimaryKeyOnly)) => {
quote! { db()?.execute_sql_public_primary_key_update(sql) }
}
(true, Some(BuildSqlUpdatePolicy::PublicBoundedDeterministic)) => {
quote! { db()?.execute_sql_public_bounded_update(sql) }
}
(false, _) | (true, None) => empty_sql_surface_query_dispatch(),
};
quote! {
#[cfg(feature = "sql")]
fn icydb_sql_surface_update_dispatch(
sql: &str,
) -> Result<::icydb::db::sql::SqlQueryResult, ::icydb::Error> {
match ::icydb::__macro::sql_statement_entity_name(sql)?.as_deref() {
None => Err(::icydb::Error::from_runtime_boundary(
::icydb::diagnostic::RuntimeBoundaryCode::SqlQueryNoConfiguredEntities,
::icydb::ErrorOrigin::Interface,
)),
Some(_entity) => #entity_dispatch,
}
}
}
}
}
impl quote::ToTokens for SqlSurfaceTokens {
fn to_tokens(&self, tokens: &mut TokenStream) {
let controller_guard = sql_surface_controller_guard();
let readonly_enabled = self.surfaces.readonly_enabled();
let ddl_enabled = self.surfaces.ddl_enabled();
let fixtures_enabled = self.surfaces.fixtures_enabled();
let perf_result = readonly_enabled.then(sql_surface_perf_result);
let readonly_dispatch = self
.surfaces
.readonly_enabled()
.then(|| self.readonly_dispatch_tokens());
let ddl_dispatch = ddl_enabled.then(|| self.ddl_dispatch_tokens());
let update_dispatch = self
.update_policy
.is_some()
.then(|| self.update_dispatch_tokens());
let endpoints = sql_surface_endpoint_exports(
self.surfaces,
self.update_policy,
!self.reset_statements.is_empty(),
);
let reset_helper = fixtures_enabled.then(|| self.reset_helper_tokens());
tokens.extend(quote! {
#controller_guard
#perf_result
#readonly_dispatch
#reset_helper
#ddl_dispatch
#update_dispatch
#endpoints
});
}
}
fn sql_surface_controller_guard() -> TokenStream {
quote! {
#[cfg(feature = "sql")]
fn icydb_sql_surface_require_controller(_action: &str) -> Result<(), ::icydb::Error> {
let caller = ::icydb::__reexports::ic_cdk::api::msg_caller();
if !::icydb::__reexports::ic_cdk::api::is_controller(&caller) {
return Err(::icydb::Error::from_runtime_boundary(
::icydb::diagnostic::RuntimeBoundaryCode::SqlSurfaceControllerRequired,
::icydb::ErrorOrigin::Interface,
));
}
Ok(())
}
}
}
fn sql_surface_perf_result() -> TokenStream {
quote! {
#[cfg(feature = "sql")]
#[derive(::icydb::__reexports::candid::CandidType, Clone, Debug, Eq, PartialEq)]
struct IcydbSqlQueryPerfResult {
result: ::icydb::db::sql::SqlQueryResult,
instructions: u64,
planner_instructions: u64,
store_instructions: u64,
executor_instructions: u64,
pure_covering_decode_instructions: u64,
pure_covering_row_assembly_instructions: u64,
decode_instructions: u64,
compiler_instructions: u64,
}
#[cfg(feature = "sql")]
impl IcydbSqlQueryPerfResult {
fn from_attribution(
result: ::icydb::db::sql::SqlQueryResult,
attribution: ::icydb::db::SqlQueryPerfAttribution,
) -> Self {
Self {
result,
instructions: attribution.total_local_instructions,
planner_instructions: attribution.execution.planner_local_instructions,
store_instructions: attribution.execution.store_local_instructions,
executor_instructions: attribution.execution.executor_local_instructions,
pure_covering_decode_instructions: attribution
.pure_covering
.map_or(0, |pure_covering| pure_covering.decode_local_instructions),
pure_covering_row_assembly_instructions: attribution
.pure_covering
.map_or(0, |pure_covering| {
pure_covering.row_assembly_local_instructions
}),
decode_instructions: attribution.response_decode_local_instructions,
compiler_instructions: attribution.compile_local_instructions,
}
}
}
}
}
fn sql_surface_endpoint_exports(
surfaces: BuildSqlSurfaceFlags,
update_policy: Option<BuildSqlUpdatePolicy>,
reset_is_fallible: bool,
) -> TokenStream {
let query_endpoint = surfaces.readonly_enabled().then(|| {
quote! {
#[cfg(feature = "sql")]
#[::icydb::__reexports::ic_cdk::query(name = "icydb_query")]
fn __icydb_query(
sql: String,
) -> Result<IcydbSqlQueryPerfResult, ::icydb::Error> {
icydb_sql_surface_require_controller("query")?;
let (result, attribution) = __icydb_query_dispatch(sql.as_str())?;
Ok(IcydbSqlQueryPerfResult::from_attribution(
result,
attribution,
))
}
}
});
let ddl_endpoint = surfaces.ddl_enabled().then(|| {
quote! {
#[cfg(feature = "sql")]
#[::icydb::__reexports::ic_cdk::update(name = "icydb_ddl")]
fn __icydb_ddl(sql: String) -> Result<::icydb::db::sql::SqlQueryResult, ::icydb::Error> {
icydb_sql_surface_require_controller("DDL")?;
icydb_sql_surface_ddl_dispatch(sql.as_str())
}
}
});
let fixture_endpoints = surfaces.fixtures_enabled().then(|| {
let reset_endpoint_result = if reset_is_fallible {
quote! { icydb_sql_surface_reset_all_tables() }
} else {
quote! {
icydb_sql_surface_reset_all_tables();
Ok(())
}
};
let reset_before_load = if reset_is_fallible {
quote! { icydb_sql_surface_reset_all_tables()?; }
} else {
quote! { icydb_sql_surface_reset_all_tables(); }
};
quote! {
#[cfg(feature = "sql")]
#[::icydb::__reexports::ic_cdk::update(name = "icydb_fixtures_reset")]
fn __icydb_fixtures_reset() -> Result<(), ::icydb::Error> {
icydb_sql_surface_require_controller("lifecycle reset")?;
#reset_endpoint_result
}
#[cfg(feature = "sql")]
#[::icydb::__reexports::ic_cdk::update(name = "icydb_fixtures_load")]
fn __icydb_fixtures_load() -> Result<(), ::icydb::Error> {
icydb_sql_surface_require_controller("lifecycle load")?;
let hook: fn() -> Result<(), ::icydb::Error> = crate::icydb_fixtures_load;
#reset_before_load
hook()
}
}
});
let update_endpoint = update_policy.is_some().then(|| {
quote! {
#[cfg(feature = "sql")]
#[::icydb::__reexports::ic_cdk::update(name = "icydb_update")]
fn __icydb_update(sql: String) -> Result<::icydb::db::sql::SqlQueryResult, ::icydb::Error> {
icydb_sql_surface_require_controller("SQL update")?;
icydb_sql_surface_update_dispatch(sql.as_str())
}
}
});
let integrity_endpoint = surfaces.integrity_enabled().then(|| {
quote! {
#[cfg(feature = "sql")]
#[::icydb::__reexports::ic_cdk::update(name = "icydb_integrity")]
fn __icydb_integrity(
sql: String,
) -> Result<::icydb::db::IntegrityCheckResult, ::icydb::db::SqlIntegrityError> {
icydb_sql_surface_require_controller("integrity")
.map_err(::icydb::db::SqlIntegrityError::Sql)?;
let caller = ::icydb::__reexports::ic_cdk::api::msg_caller();
let owner = ::icydb::db::IntegrityJobOwner::new(caller.to_text()).map_err(|error| {
::icydb::db::SqlIntegrityError::Integrity(
::icydb::db::IntegrityCheckError::Job(error),
)
})?;
let session = db().map_err(|error| {
::icydb::db::SqlIntegrityError::Integrity(
::icydb::db::IntegrityCheckError::Database(error),
)
})?;
session.execute_admin_integrity_sql(sql.as_str(), owner)
}
}
});
quote! {
#query_endpoint
#ddl_endpoint
#fixture_endpoints
#update_endpoint
#integrity_endpoint
}
}
fn sql_surface_reset_statement(entity_name: &str) -> TokenStream {
let delete_sql = format!("DELETE FROM {entity_name}");
quote! {
let _ = db()?.execute_trusted_sql_mutation(#delete_sql)?;
}
}
fn empty_sql_surface_query_dispatch() -> TokenStream {
quote! {
Err(::icydb::Error::from_runtime_boundary(
::icydb::diagnostic::RuntimeBoundaryCode::SqlQueryNoConfiguredEntities,
::icydb::ErrorOrigin::Interface,
))
}
}
fn show_entities_dispatch() -> TokenStream {
quote! {
db()?.execute_trusted_sql_query_with_perf_attribution(sql)
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use quote::quote;
use crate::build::actor::{BuildSqlSurfaceFlags, BuildSqlUpdatePolicy};
use super::SqlSurfaceTokens;
fn compact_tokens(tokens: proc_macro2::TokenStream) -> String {
tokens
.to_string()
.chars()
.filter(|character| !character.is_whitespace())
.collect()
}
fn generated_export_names_and_signatures(
tokens: proc_macro2::TokenStream,
) -> BTreeSet<(String, String)> {
let file = syn::parse2::<syn::File>(tokens)
.expect("generated SQL surface should remain valid Rust syntax");
file.items
.into_iter()
.filter_map(|item| {
let syn::Item::Fn(function) = item else {
return None;
};
let exported = function.attrs.iter().any(|attribute| {
attribute.path().segments.last().is_some_and(|segment| {
matches!(segment.ident.to_string().as_str(), "query" | "update")
})
});
exported.then(|| {
let signature = function.sig;
(
signature.ident.to_string(),
semantic_export_signature(&signature),
)
})
})
.collect()
}
fn expected_export_signature(signature: syn::Signature) -> String {
semantic_export_signature(&signature)
}
fn semantic_export_signature(signature: &syn::Signature) -> String {
let inputs = signature
.inputs
.iter()
.map(|input| match input {
syn::FnArg::Receiver(receiver) => compact_tokens(quote!(#receiver)),
syn::FnArg::Typed(input) => {
let ty = &input.ty;
compact_tokens(quote!(#ty))
}
})
.collect::<Vec<_>>()
.join(",");
let output = match &signature.output {
syn::ReturnType::Default => "()".to_string(),
syn::ReturnType::Type(_, output) => compact_tokens(quote!(#output)),
};
format!("fn {}({inputs})->{output}", signature.ident)
}
fn all_sql_surface_flags() -> BuildSqlSurfaceFlags {
BuildSqlSurfaceFlags::default()
.with_readonly_enabled(true)
.with_ddl_enabled(true)
.with_fixtures_enabled(true)
.with_integrity_enabled(true)
.with_introspection_enabled(true)
}
fn sql_surface_flags_without_introspection() -> BuildSqlSurfaceFlags {
BuildSqlSurfaceFlags::default()
.with_readonly_enabled(true)
.with_ddl_enabled(true)
.with_fixtures_enabled(true)
}
#[test]
fn generated_sql_surface_exports_only_query_ddl_and_fixture_endpoints() {
let non_integrity_surfaces = BuildSqlSurfaceFlags::default()
.with_readonly_enabled(true)
.with_ddl_enabled(true)
.with_fixtures_enabled(true)
.with_introspection_enabled(true);
let surface = compact_tokens(super::sql_surface_endpoint_exports(
non_integrity_surfaces,
None,
true,
));
assert!(surface.contains("name=\"icydb_query\""));
assert!(surface.contains("name=\"icydb_ddl\""));
assert!(surface.contains("name=\"icydb_fixtures_reset\""));
assert!(surface.contains("name=\"icydb_fixtures_load\""));
assert!(!surface.contains("name=\"icydb_update\""));
assert!(!surface.contains("name=\"icydb_integrity\""));
assert!(surface.contains("fn__icydb_query("));
assert!(surface.contains("fn__icydb_ddl("));
assert!(surface.contains("fn__icydb_fixtures_reset("));
assert!(surface.contains("fn__icydb_fixtures_load("));
assert_eq!(surface.matches("::icydb::__reexports::ic_cdk::").count(), 4);
}
#[test]
fn generated_sql_export_contract_cannot_carry_resumable_state() {
for policy in [
BuildSqlUpdatePolicy::PublicPrimaryKeyOnly,
BuildSqlUpdatePolicy::PublicBoundedDeterministic,
] {
let tokens =
super::sql_surface_endpoint_exports(all_sql_surface_flags(), Some(policy), true);
let compact = compact_tokens(tokens.clone()).to_ascii_lowercase();
assert!(!compact.contains("resumable"));
assert!(!compact.contains("continuation"));
let exports = generated_export_names_and_signatures(tokens);
let expected = BTreeSet::from([
(
"__icydb_query".to_string(),
expected_export_signature(syn::parse_quote! {
fn __icydb_query(
sql: String,
) -> Result<IcydbSqlQueryPerfResult, ::icydb::Error>
}),
),
(
"__icydb_ddl".to_string(),
expected_export_signature(syn::parse_quote! {
fn __icydb_ddl(
sql: String,
) -> Result<::icydb::db::sql::SqlQueryResult, ::icydb::Error>
}),
),
(
"__icydb_fixtures_reset".to_string(),
expected_export_signature(syn::parse_quote! {
fn __icydb_fixtures_reset() -> Result<(), ::icydb::Error>
}),
),
(
"__icydb_fixtures_load".to_string(),
expected_export_signature(syn::parse_quote! {
fn __icydb_fixtures_load() -> Result<(), ::icydb::Error>
}),
),
(
"__icydb_update".to_string(),
expected_export_signature(syn::parse_quote! {
fn __icydb_update(
sql: String,
) -> Result<::icydb::db::sql::SqlQueryResult, ::icydb::Error>
}),
),
(
"__icydb_integrity".to_string(),
expected_export_signature(syn::parse_quote! {
fn __icydb_integrity(
sql: String,
) -> Result<
::icydb::db::IntegrityCheckResult,
::icydb::db::SqlIntegrityError
>
}),
),
]);
assert_eq!(exports, expected);
}
}
#[test]
fn generated_readonly_sql_surface_uses_trusted_query_and_admin_ddl() {
let mut surface_tokens = SqlSurfaceTokens::empty(all_sql_surface_flags(), None);
surface_tokens.push_entity("Character");
let surface = compact_tokens(quote!(#surface_tokens));
assert!(surface.contains("execute_trusted_sql_query_with_perf_attribution"));
assert!(surface.contains("execute_admin_sql_ddl"));
assert!(!surface.contains("identifiers_tail_match"));
assert!(!surface.contains("crate::Character"));
assert!(!surface.contains("SqlQueryEntityNotConfigured"));
assert!(!surface.contains("SqlDdlEntityNotConfigured"));
}
#[test]
fn generated_empty_sql_surface_rejects_entity_dispatch_before_runtime_access() {
let surface_tokens = SqlSurfaceTokens::empty(all_sql_surface_flags(), None);
let surface = compact_tokens(quote!(#surface_tokens));
assert!(surface.contains("SqlQueryNoConfiguredEntities"));
assert!(surface.contains("SqlDdlEntityNotConfigured"));
assert!(!surface.contains("execute_trusted_sql_query_with_perf_attribution"));
assert!(!surface.contains("execute_admin_sql_ddl"));
}
#[test]
fn generated_readonly_sql_surface_has_one_controller_query_endpoint() {
let mut surface_tokens = SqlSurfaceTokens::empty(all_sql_surface_flags(), None);
surface_tokens.push_entity("Character");
let endpoint = compact_tokens(super::sql_surface_endpoint_exports(
all_sql_surface_flags(),
None,
true,
));
let surface = compact_tokens(quote!(#surface_tokens));
assert!(endpoint.contains("name=\"icydb_query\""));
assert!(endpoint.contains("fn__icydb_query("));
assert_eq!(
endpoint
.matches("::icydb::__reexports::ic_cdk::query")
.count(),
1
);
assert!(surface.contains("execute_trusted_sql_query_with_perf_attribution"));
}
#[test]
fn generated_sql_query_endpoint_guards_explicit_trusted_dispatch() {
let mut surface_tokens = SqlSurfaceTokens::empty(all_sql_surface_flags(), None);
surface_tokens.push_entity("Character");
let endpoint = compact_tokens(super::sql_surface_endpoint_exports(
all_sql_surface_flags(),
None,
true,
));
let surface = compact_tokens(quote!(#surface_tokens));
assert!(endpoint.contains("name=\"icydb_query\""));
let controller_guard = endpoint
.find("icydb_sql_surface_require_controller(\"query\")")
.expect("generated query endpoint should require its controller guard");
let trusted_dispatch = endpoint
.find("__icydb_query_dispatch")
.expect("generated query endpoint should call its trusted dispatch");
assert!(controller_guard < trusted_dispatch);
assert!(surface.contains("execute_trusted_sql_query_with_perf_attribution"));
}
#[test]
fn generated_integrity_endpoint_guards_canonical_integrity_dispatch() {
let integrity_only = BuildSqlSurfaceFlags::default().with_integrity_enabled(true);
let endpoint = compact_tokens(super::sql_surface_endpoint_exports(
integrity_only,
None,
false,
));
assert!(endpoint.contains("name=\"icydb_integrity\""));
let controller_guard = endpoint
.find("icydb_sql_surface_require_controller(\"integrity\")")
.expect("generated integrity endpoint should require its controller guard");
let owner = endpoint
.find("IntegrityJobOwner::new")
.expect("generated integrity endpoint should bind jobs to the caller");
let canonical_dispatch = endpoint
.find("execute_admin_integrity_sql")
.expect("generated integrity endpoint should use canonical SQL lowering");
assert!(controller_guard < owner);
assert!(owner < canonical_dispatch);
assert!(!endpoint.contains("execute_admin_sql_ddl"));
assert!(!endpoint.contains("__icydb_query_dispatch"));
}
#[test]
fn generated_sql_surface_does_not_emit_dead_code_suppressions() {
let mut surface_tokens = SqlSurfaceTokens::empty(
all_sql_surface_flags(),
Some(BuildSqlUpdatePolicy::PublicPrimaryKeyOnly),
);
surface_tokens.push_entity("Character");
let surface = compact_tokens(quote!(#surface_tokens));
assert!(!surface.contains("allow(dead_code)"));
}
#[test]
fn generated_sql_fixture_reset_surface_does_not_emit_lint_suppressions() {
let empty_surface_tokens = SqlSurfaceTokens::empty(all_sql_surface_flags(), None);
let empty_surface = compact_tokens(quote!(#empty_surface_tokens));
let mut entity_surface_tokens = SqlSurfaceTokens::empty(all_sql_surface_flags(), None);
entity_surface_tokens.push_entity("Character");
let entity_surface = compact_tokens(quote!(#entity_surface_tokens));
assert!(!empty_surface.contains("allow("));
assert!(!entity_surface.contains("allow("));
assert!(empty_surface.contains("constfnicydb_sql_surface_reset_all_tables()"));
assert!(entity_surface.contains("fnicydb_sql_surface_reset_all_tables()"));
assert!(entity_surface.contains("Result<(),::icydb::Error>"));
}
#[test]
fn generated_sql_query_surface_can_reject_introspection() {
let mut surface_tokens =
SqlSurfaceTokens::empty(sql_surface_flags_without_introspection(), None);
surface_tokens.push_entity("Character");
let surface = compact_tokens(quote!(#surface_tokens));
assert!(surface.contains("sql_statement_dispatch"));
assert!(surface.contains("requires_introspection"));
assert!(surface.contains("SqlIntrospectionDisabled"));
assert!(surface.contains("execute_trusted_sql_query_with_perf_attribution"));
}
#[test]
fn generated_sql_update_surface_requires_explicit_primary_key_policy() {
let mut surface_tokens = SqlSurfaceTokens::empty(
all_sql_surface_flags(),
Some(BuildSqlUpdatePolicy::PublicPrimaryKeyOnly),
);
surface_tokens.push_entity("Character");
let endpoint = compact_tokens(super::sql_surface_endpoint_exports(
all_sql_surface_flags(),
Some(BuildSqlUpdatePolicy::PublicPrimaryKeyOnly),
true,
));
let surface = compact_tokens(quote!(#surface_tokens));
assert!(endpoint.contains("name=\"icydb_update\""));
assert!(endpoint.contains("fn__icydb_update("));
assert!(surface.contains("icydb_sql_surface_update_dispatch"));
assert!(surface.contains("execute_sql_public_primary_key_update"));
assert!(
!surface.contains("execute_sql_public_bounded_update"),
"first generated SQL update policy must not expose bounded multi-row UPDATE",
);
}
#[test]
fn generated_sql_update_surface_can_select_bounded_policy_without_broad_update() {
let mut surface_tokens = SqlSurfaceTokens::empty(
all_sql_surface_flags(),
Some(BuildSqlUpdatePolicy::PublicBoundedDeterministic),
);
surface_tokens.push_entity("Character");
let surface = compact_tokens(quote!(#surface_tokens));
assert!(surface.contains("icydb_sql_surface_update_dispatch"));
assert!(surface.contains("execute_sql_public_bounded_update"));
assert!(
!surface.contains("execute_sql_public_primary_key_update"),
"bounded generated SQL update policy must not silently select the primary-key-only helper",
);
}
}