Skip to main content

icydb/
lib.rs

1//! Module: lib
2//!
3//! Responsibility: public facade crate surface and generated-code wiring.
4//! Does not own: core execution, storage internals, or schema mutation semantics.
5//! Boundary: re-exports stable runtime and generated actor-wiring surfaces.
6
7//! # icydb
8//!
9//! `icydb` is the **public facade crate** for the IcyDB runtime.
10//! It is the recommended dependency for downstream canister projects.
11//!
12//! This crate exposes:
13//! - the stable runtime surface used inside canister actor code,
14//! - accepted-schema-bound database request and response types,
15//! - and a small set of entry points that wire generated actor code.
16//!
17//! Low-level execution, storage, and engine internals live in
18//! `icydb-core` and are re-exposed selectively through stable facade modules.
19//!
20//! ## Crate layout
21//!
22//! - `build` *(host builds)*
23//!   Host-side build-script facade for generated actor glue. Downstream
24//!   canister `build.rs` files should use this module rather than depending on
25//!   lower-level implementation crates directly.
26//!
27//! - `traits` / `types` / `value`
28//!   Stable runtime building blocks used by generated code.
29//!
30//! - `metrics` *(internal)*
31//!   Runtime metrics internals exposed for generated administration endpoints.
32//!
33//! - `Error` / `ErrorKind` / `ErrorOrigin`
34//!   Shared error types for generated code and runtime boundaries.
35//!
36//! - `db`
37//!   The public database façade: sessions, SQL/dynamic reads, structural
38//!   mutations, and accepted-schema-bound typed adapters.
39//!
40//! Generated SQL endpoints are controller-gated by default. A declaration may
41//! instead install one synchronous application read guard; neither form is an
42//! anonymous public read endpoint template.
43//!
44//! The operational lane contract lives in
45//! `docs/contracts/READ_ADMISSION.md`.
46//! Endpoint migration recipes live in `docs/guides/read-intent.md`.
47//!
48//! ## Preludes
49//!
50//! - `prelude`
51//!   Opinionated runtime prelude for canister actor code.
52//!   Intended to be glob-imported in `lib.rs` to keep endpoints concise.
53//!
54//! ## Internal boundaries
55//!
56//! Generated code targets explicit facade surfaces (`traits`, `db`, and
57//! `__macro`) instead of a broad internal-export module.
58
59// Generated actor glue resolves this package through its canonical crate name.
60extern crate self as icydb;
61
62pub use icydb_model_macros::{request_execution, test};
63
64// core modules
65#[doc(hidden)]
66pub use icydb_core::types;
67
68pub mod value {
69    pub use icydb_core::value::{
70        InputValue, InputValueEnum, OutputValue, OutputValueEnum, ValueTag,
71    };
72}
73
74#[doc(hidden)]
75pub mod metrics {
76    pub use icydb_core::metrics::{
77        CompactEntityMetrics, CompactEventCounters, CompactMetric, CompactMetricsReport,
78        EntitySummary, EventCounters, EventOps, EventReport, MetricRatio, MetricsSink,
79        compact_metric_code, compact_metrics_report, metrics_report, metrics_reset_all,
80    };
81}
82
83// facade modules
84#[cfg(not(target_arch = "wasm32"))]
85pub mod build {
86    //! Host-side build-script facade for generated actor glue.
87    //!
88    //! This module is the advertised downstream build-script API. Add `icydb`
89    //! to `[build-dependencies]`, then call `icydb::build::build_canister!()`
90    //! from `build.rs`. Model-graph code generation is owned by `icydb-model`.
91    //! This module is host-only and is not part of Wasm runtime builds.
92
93    pub use crate::build_canister;
94
95    /// Emit one generated private actor module for a build script.
96    ///
97    /// This function is expansion support for [`build_canister!`].
98    ///
99    /// # Errors
100    ///
101    /// Returns an environment or filesystem error when Cargo does not provide
102    /// `OUT_DIR` or the generated actor cannot be written.
103    #[doc(hidden)]
104    pub fn __emit_canister_for_build_script(
105        canister_path: &str,
106    ) -> Result<(), Box<dyn std::error::Error>> {
107        println!("cargo:rerun-if-changed=build.rs");
108        println!("cargo:rustc-check-cfg=cfg(feature, values(\"test-admin-api\"))");
109        let out_dir = std::env::var("OUT_DIR")?;
110        let actor_file = std::path::PathBuf::from(out_dir).join("actor.rs");
111        let actor = icydb_model::build::generate(canister_path);
112        std::fs::write(actor_file, actor)?;
113
114        Ok(())
115    }
116}
117pub mod db;
118pub mod guards;
119pub mod diagnostic {
120    //! Compact diagnostic identity for CLI and canister callers.
121
122    pub use icydb_diagnostic_code::{
123        Diagnostic, DiagnosticAggregateKind, DiagnosticBacklogResource, DiagnosticCode,
124        DiagnosticComponentKind, DiagnosticConstraintContext, DiagnosticConstraintKind,
125        DiagnosticDecodeReason, DiagnosticDetail, DiagnosticExecutionBudgetResource,
126        DiagnosticExecutionBudgetScope, DiagnosticExecutionLane, DiagnosticFactSchemaMismatch,
127        DiagnosticFactTag, DiagnosticFunctionKind, DiagnosticMutationOperation,
128        DiagnosticOperatorKind, DiagnosticTypeFamily, ErrorClass, ErrorCode, ErrorOrigin,
129        MAX_PUBLIC_DIAGNOSTIC_FACTS, MAX_PUBLIC_QUERY_FIELD_BYTES, QueryErrorKind, QueryFieldRole,
130        QueryFieldSchemaMismatch, QueryProjectionCode, QueryReadAdmissionCode,
131        QueryResultShapeCode, RuntimeBoundaryCode, RuntimeErrorKind, SchemaDdlAdmissionCode,
132        SchemaMigrationCode, SqlFeatureCode, SqlLoweringCode, SqlSurfaceMismatchCode,
133        SqlWriteBoundaryCode, pack_u32_pair, unpack_u32_pair,
134        validate_known_diagnostic_fact_schema, validate_query_field_schema,
135        validate_raw_diagnostic_fact_schema,
136    };
137}
138mod error;
139pub mod traits;
140pub use error::{
141    ConstraintValidationFindingOutput, ConstraintValuePath, ConstraintValuePathComponent,
142    DiagnosticFact, Error, ErrorKind, ErrorOrigin, QueryErrorKind, QueryFieldDiagnostic,
143    RuntimeErrorKind,
144};
145pub use guards::{
146    ReadAuthorizationContext, ReadAuthorizationDecision, ReadAuthorizationGuard,
147    ReadAuthorizationSurface,
148};
149pub use icydb_diagnostic_code::ErrorCode;
150
151// Macro/runtime wiring surface used by generated code.
152// This is intentionally narrow and not semver-stable.
153#[doc(hidden)]
154pub mod __macro {
155    pub use crate::db::{
156        TypedFieldBindingRequest, TypedFieldType, ensure_default_memory_manager,
157        execute_generated_storage_report,
158    };
159    pub use crate::guards::{authorize_schema_read, authorize_sql_read};
160    pub use ic_memory::{ic_memory_declaration, ic_memory_key, ic_memory_range};
161    pub use icydb_core::db::{
162        CompositePrimaryKeyValue, DataStore, DbSession as CoreDbSession, EntityKey, EntityKeyBytes,
163        EntityKeyBytesError, IndexStore, JournalTailStore, KeyValueCodec, PrimaryKeyDecode,
164        PrimaryKeyEncode, PrimaryKeyEncodeError, PrimaryKeyValue, SchemaStore,
165        StoreAllocationIdentities, StoreAllocationIdentity, StoreRegistry,
166        StoreRuntimeStorageCapabilities, validate_entity_key_bytes_buffer,
167    };
168    #[cfg(feature = "sql")]
169    pub use icydb_core::db::{sql_statement_dispatch, sql_statement_entity_name};
170    pub use icydb_core::error::{ErrorClass, ErrorOrigin, InternalError};
171    pub use icydb_core::metrics::with_query_metrics_context;
172    pub use icydb_core::traits::{CanisterKind, Path};
173    pub use icydb_core::value::Value;
174    pub use icydb_schema::{DEFAULT_BIG_INT_MAX_BYTES, ScalarType};
175}
176
177// Dependencies used by generated actor glue. Application-model macro
178// dependencies are owned separately by `icydb-model`.
179#[doc(hidden)]
180pub mod __reexports {
181    pub use candid;
182    pub use ic_cdk;
183    pub use ic_timers;
184}
185
186//
187// Actor Prelude
188// using _ brings traits into scope and avoids name conflicts
189//
190
191pub mod prelude {
192    pub use crate::db::{
193        query,
194        query::{
195            FieldRef, FilterExpr, FilterValue, OrderExpr, OrderTerm, asc, count, count_by, desc,
196            exists, field, first, last, max, max_by, min, min_by, sum,
197        },
198    };
199    pub use crate::{
200        db,
201        traits::{Inner as _, Path as _},
202        types::*,
203        value::{InputValue, OutputValue},
204    };
205    pub use candid::CandidType;
206    pub use serde::{Deserialize, Serialize};
207}
208
209//
210// -------------------------- CODE -----------------------------------
211//
212
213//
214// Consts
215//
216
217// Workspace version re-export for downstream tooling/tests.
218pub const VERSION: &str = env!("CARGO_PKG_VERSION");
219
220//
221// Macros
222//
223
224/// Generate one canister's private actor module from its authored schema type.
225#[cfg(not(target_arch = "wasm32"))]
226#[macro_export]
227macro_rules! build_canister {
228    ($canister_ty:ty) => {{
229        let _ = ::std::any::TypeId::of::<$canister_ty>();
230        $crate::build::__emit_canister_for_build_script(stringify!($canister_ty))
231    }};
232}
233
234/// Include the generated private actor module emitted by [`build_canister!`].
235///
236/// The zero-argument form owns hidden install and post-upgrade lifecycle
237/// entries for canisters without application hooks. Applications that own
238/// the complete lifecycle root use participant mode and invoke the matching
239/// hidden IcyDB participant synchronously:
240///
241/// ```ignore
242/// icydb::start!(participant);
243///
244/// #[ic_cdk::init]
245/// fn init() {
246///     crate::__icydb_lifecycle_participant::init();
247/// }
248///
249/// #[ic_cdk::post_upgrade]
250/// fn post_upgrade() {
251///     crate::__icydb_lifecycle_participant::post_upgrade();
252/// }
253/// ```
254///
255/// Applications that want IcyDB to own the lifecycle exports while running
256/// application callbacks afterward use the composed form:
257///
258/// ```ignore
259/// icydb::start! {
260///     init(args: InitArgs) => application::init;
261///     post_upgrade() => application::post_upgrade;
262/// }
263/// ```
264///
265/// IcyDB registers its startup watchdog before invoking either callback. The
266/// callback remains application-owned and must observe `startup_state()`
267/// before restoring timers, caches, or other database-dependent state.
268#[macro_export]
269macro_rules! start {
270    () => {
271        $crate::__icydb_start_actor!();
272        $crate::__icydb_start_lifecycle!();
273    };
274
275    (participant) => {
276        $crate::__icydb_start_actor!();
277        $crate::__icydb_start_participant_lifecycle!();
278    };
279
280    (
281        init($($init_arg:ident : $init_ty:ty),* $(,)?) => $init:path;
282        post_upgrade($($upgrade_arg:ident : $upgrade_ty:ty),* $(,)?) => $post_upgrade:path;
283    ) => {
284        $crate::__icydb_start_actor!();
285        $crate::__icydb_start_lifecycle! {
286            init($($init_arg: $init_ty),*) => $init;
287            post_upgrade($($upgrade_arg: $upgrade_ty),*) => $post_upgrade;
288        }
289    };
290}
291
292#[doc(hidden)]
293#[macro_export]
294#[expect(
295    clippy::crate_in_macro_def,
296    reason = "participant functions must call the consuming canister's generated actor"
297)]
298macro_rules! __icydb_start_participant_lifecycle {
299    () => {
300        #[doc(hidden)]
301        #[allow(dead_code)]
302        pub(crate) mod __icydb_lifecycle_participant {
303            use std::cell::Cell;
304
305            #[derive(Clone, Copy)]
306            enum State {
307                Idle,
308                Running,
309                Completed,
310            }
311
312            std::thread_local! {
313                static STATE: Cell<State> = const { Cell::new(State::Idle) };
314            }
315
316            // Native traps unwind instead of rolling back a replicated IC
317            // message. Reset only that test/runtime model so retry exercises
318            // the same latch transition that message rollback provides on Wasm.
319            #[cfg(not(target_family = "wasm"))]
320            struct NativeRollbackGuard {
321                completed: bool,
322            }
323
324            #[cfg(not(target_family = "wasm"))]
325            impl NativeRollbackGuard {
326                const fn new() -> Self {
327                    Self { completed: false }
328                }
329
330                const fn complete(&mut self) {
331                    self.completed = true;
332                }
333            }
334
335            #[cfg(not(target_family = "wasm"))]
336            impl Drop for NativeRollbackGuard {
337                fn drop(&mut self) {
338                    if !self.completed {
339                        STATE.with(|state| state.set(State::Idle));
340                    }
341                }
342            }
343
344            // Both lifecycle phases deliberately converge here. Completed is
345            // shared across them so any duplicate returns before generated
346            // timer or recovery state can be observed or changed.
347            fn participate(work: fn() -> ()) {
348                let should_run = STATE.with(|state| match state.get() {
349                    State::Idle => {
350                        state.set(State::Running);
351                        true
352                    }
353                    State::Running => $crate::__reexports::ic_cdk::trap(
354                        "IcyDB lifecycle participant re-entered while running",
355                    ),
356                    State::Completed => false,
357                });
358
359                if !should_run {
360                    return;
361                }
362
363                #[cfg(not(target_family = "wasm"))]
364                let mut rollback = NativeRollbackGuard::new();
365
366                work();
367                STATE.with(|state| state.set(State::Completed));
368
369                #[cfg(not(target_family = "wasm"))]
370                rollback.complete();
371            }
372
373            /// Participate synchronously in the canister's init lifecycle.
374            #[doc(hidden)]
375            pub(crate) fn init() -> () {
376                let participate: fn() -> () = crate::__icydb_generated::__icydb_startup_init;
377                self::participate(participate);
378            }
379
380            /// Participate synchronously in the canister's post-upgrade lifecycle.
381            #[doc(hidden)]
382            pub(crate) fn post_upgrade() -> () {
383                let participate: fn() -> () =
384                    crate::__icydb_generated::__icydb_startup_post_upgrade;
385                self::participate(participate);
386            }
387        }
388    };
389}
390
391#[doc(hidden)]
392#[macro_export]
393#[expect(
394    clippy::crate_in_macro_def,
395    reason = "lifecycle wrappers must call the consuming canister's generated actor"
396)]
397macro_rules! __icydb_start_lifecycle {
398    () => {
399        #[$crate::__reexports::ic_cdk::init(hidden = true)]
400        fn __icydb_startup_init() {
401            crate::__icydb_generated::__icydb_startup_init();
402        }
403
404        #[$crate::__reexports::ic_cdk::post_upgrade(hidden = true)]
405        fn __icydb_startup_post_upgrade() {
406            crate::__icydb_generated::__icydb_startup_post_upgrade();
407        }
408    };
409
410    (
411        init($($init_arg:ident : $init_ty:ty),* $(,)?) => $init:path;
412        post_upgrade($($upgrade_arg:ident : $upgrade_ty:ty),* $(,)?) => $post_upgrade:path;
413    ) => {
414        #[$crate::__reexports::ic_cdk::init]
415        fn __icydb_startup_init($($init_arg: $init_ty),*) {
416            crate::__icydb_generated::__icydb_startup_init();
417            let (): () = $crate::db::with_request_execution(|| ($init)($($init_arg),*));
418        }
419
420        #[$crate::__reexports::ic_cdk::post_upgrade]
421        fn __icydb_startup_post_upgrade($($upgrade_arg: $upgrade_ty),*) {
422            crate::__icydb_generated::__icydb_startup_post_upgrade();
423            let (): () = $crate::db::with_request_execution(
424                || ($post_upgrade)($($upgrade_arg),*)
425            );
426        }
427    };
428}
429
430#[doc(hidden)]
431#[macro_export]
432#[expect(
433    clippy::crate_in_macro_def,
434    reason = "generated actor bindings must live in the consuming canister crate"
435)]
436macro_rules! __icydb_start_actor {
437    () => {
438        #[doc(hidden)]
439        struct __IcydbStartRootMarker;
440
441        #[doc(hidden)]
442        const fn __icydb_start_root_binding(_: __IcydbStartRootMarker) {}
443
444        const _: fn(__IcydbStartRootMarker) = crate::__icydb_start_root_binding;
445
446        #[allow(dead_code)]
447        mod __icydb_generated {
448            #[doc(hidden)]
449            pub(crate) const __ICYDB_START_BINDING: () = ();
450
451            include!(concat!(env!("OUT_DIR"), "/actor.rs"));
452        }
453
454        #[allow(unused_imports)]
455        use __icydb_generated::{db, db_with_request_root, startup_state};
456    };
457}
458
459#[doc(hidden)]
460#[cfg(feature = "sql")]
461#[macro_export]
462macro_rules! __icydb_with_sql_items {
463    ($($item:item)*) => { $($item)* };
464}
465
466#[doc(hidden)]
467#[cfg(not(feature = "sql"))]
468#[macro_export]
469macro_rules! __icydb_with_sql_items {
470    ($($item:item)*) => {};
471}
472
473#[doc(hidden)]
474#[cfg(feature = "migration")]
475#[macro_export]
476macro_rules! __icydb_with_migration_items {
477    ($($item:item)*) => { $($item)* };
478}
479
480#[doc(hidden)]
481#[cfg(not(feature = "migration"))]
482#[macro_export]
483macro_rules! __icydb_with_migration_items {
484    ($($item:item)*) => {};
485}
486
487#[doc(hidden)]
488#[cfg(feature = "sql")]
489#[macro_export]
490macro_rules! __icydb_with_sql_endpoint {
491    ($endpoint:literal; $($item:item)*) => { $($item)* };
492}
493
494#[doc(hidden)]
495#[cfg(feature = "migration")]
496#[macro_export]
497macro_rules! __icydb_with_migration_endpoint {
498    ($endpoint:literal; $($item:item)*) => { $($item)* };
499}
500
501#[doc(hidden)]
502#[cfg(not(feature = "migration"))]
503#[macro_export]
504macro_rules! __icydb_with_migration_endpoint {
505    ($endpoint:literal; $($item:item)*) => {
506        compile_error!(concat!(
507            "endpoint declaration `",
508            $endpoint,
509            "` requires the `icydb/migration` Cargo feature"
510        ));
511    };
512}
513
514#[doc(hidden)]
515#[cfg(not(feature = "sql"))]
516#[macro_export]
517macro_rules! __icydb_with_sql_endpoint {
518    ($endpoint:literal; $($item:item)*) => {
519        compile_error!(concat!(
520            "endpoint declaration `",
521            $endpoint,
522            "` requires the `icydb/sql` Cargo feature"
523        ));
524    };
525}
526
527#[doc(hidden)]
528#[cfg(feature = "migration")]
529#[macro_export]
530macro_rules! __icydb_require_migration_capability {
531    () => {};
532}
533
534#[doc(hidden)]
535#[cfg(not(feature = "migration"))]
536#[macro_export]
537macro_rules! __icydb_require_migration_capability {
538    () => {
539        compile_error!("source migration declarations require the `icydb/migration` Cargo feature");
540    };
541}
542
543/// Declare the complete fixed IcyDB endpoint surface exported by this canister.
544#[macro_export]
545#[expect(
546    clippy::crate_in_macro_def,
547    reason = "endpoints! must prove crate-root placement in the consuming canister"
548)]
549macro_rules! endpoints {
550    ($($declaration:tt)*) => {
551        #[doc(hidden)]
552        struct __IcydbEndpointsRootMarker;
553
554        #[doc(hidden)]
555        const fn __icydb_endpoints_root_binding(_: __IcydbEndpointsRootMarker) {}
556
557        const _: fn(__IcydbEndpointsRootMarker) = crate::__icydb_endpoints_root_binding;
558
559        #[doc(hidden)]
560        #[allow(unused_imports)]
561        use $crate as __icydb_facade;
562
563        #[used]
564        static __ICYDB_ENDPOINT_DECLARATIONS: () =
565            crate::__icydb_generated::__ICYDB_START_BINDING;
566
567        $crate::__icydb_endpoints_internal!($($declaration)*);
568    };
569}
570
571#[doc(hidden)]
572#[macro_export]
573#[expect(
574    clippy::crate_in_macro_def,
575    reason = "endpoint wrappers call the consuming canister's private generated module"
576)]
577macro_rules! __icydb_endpoints_internal {
578    () => {};
579
580    ($(#[cfg($($cfg:tt)*)])* icydb_sql_query(
581        introspection = false,
582        authorization = guard($guard:path) $(,)?
583    ); $($rest:tt)*) => {
584        $(#[cfg($($cfg)*)])*
585        #[used]
586        static __ICYDB_ENDPOINT_DECLARATION_QUERY: () = ();
587        $(#[cfg($($cfg)*)])*
588        $crate::__icydb_with_sql_endpoint! {
589            "icydb_sql_query";
590            #[$crate::__reexports::ic_cdk::query(name = "icydb_query")]
591            fn __icydb_export_icydb_query(
592                sql: String,
593            ) -> Result<__icydb_facade::db::sql::SqlQueryPerfResult, __icydb_facade::Error> {
594                let guard: $crate::ReadAuthorizationGuard = $guard;
595                $crate::__macro::authorize_sql_read(
596                    $crate::__reexports::ic_cdk::api::msg_caller(),
597                    guard,
598                )?;
599                $crate::__macro::with_query_metrics_context(|| {
600                    $crate::db::with_request_execution(|| {
601                        crate::__icydb_generated::endpoint_handlers::sql_query::<false>(sql)
602                    })
603                })
604            }
605        }
606        $crate::__icydb_endpoints_internal!($($rest)*);
607    };
608
609    ($(#[cfg($($cfg:tt)*)])* icydb_sql_query(
610        introspection = true,
611        authorization = guard($guard:path) $(,)?
612    ); $($rest:tt)*) => {
613        $(#[cfg($($cfg)*)])*
614        #[used]
615        static __ICYDB_ENDPOINT_DECLARATION_QUERY: () = ();
616        $(#[cfg($($cfg)*)])*
617        $crate::__icydb_with_sql_endpoint! {
618            "icydb_sql_query";
619            #[$crate::__reexports::ic_cdk::query(name = "icydb_query")]
620            fn __icydb_export_icydb_query(
621                sql: String,
622            ) -> Result<__icydb_facade::db::sql::SqlQueryPerfResult, __icydb_facade::Error> {
623                let guard: $crate::ReadAuthorizationGuard = $guard;
624                $crate::__macro::authorize_sql_read(
625                    $crate::__reexports::ic_cdk::api::msg_caller(),
626                    guard,
627                )?;
628                $crate::__macro::with_query_metrics_context(|| {
629                    $crate::db::with_request_execution(|| {
630                        crate::__icydb_generated::endpoint_handlers::sql_query::<true>(sql)
631                    })
632                })
633            }
634        }
635        $crate::__icydb_endpoints_internal!($($rest)*);
636    };
637
638    ($(#[cfg($($cfg:tt)*)])* icydb_sql_query(introspection = false); $($rest:tt)*) => {
639        $(#[cfg($($cfg)*)])*
640        #[used]
641        static __ICYDB_ENDPOINT_DECLARATION_QUERY: () = ();
642        $(#[cfg($($cfg)*)])*
643        $crate::__icydb_with_sql_endpoint! {
644            "icydb_sql_query";
645            #[$crate::__reexports::ic_cdk::query(name = "icydb_query")]
646            fn __icydb_export_icydb_query(
647                sql: String,
648            ) -> Result<__icydb_facade::db::sql::SqlQueryPerfResult, __icydb_facade::Error> {
649                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
650                $crate::__macro::with_query_metrics_context(|| {
651                    $crate::db::with_request_execution(|| {
652                        crate::__icydb_generated::endpoint_handlers::sql_query::<false>(sql)
653                    })
654                })
655            }
656        }
657        $crate::__icydb_endpoints_internal!($($rest)*);
658    };
659
660    ($(#[cfg($($cfg:tt)*)])* icydb_sql_query(introspection = true); $($rest:tt)*) => {
661        $(#[cfg($($cfg)*)])*
662        #[used]
663        static __ICYDB_ENDPOINT_DECLARATION_QUERY: () = ();
664        $(#[cfg($($cfg)*)])*
665        $crate::__icydb_with_sql_endpoint! {
666            "icydb_sql_query";
667            #[$crate::__reexports::ic_cdk::query(name = "icydb_query")]
668            fn __icydb_export_icydb_query(
669                sql: String,
670            ) -> Result<__icydb_facade::db::sql::SqlQueryPerfResult, __icydb_facade::Error> {
671                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
672                $crate::__macro::with_query_metrics_context(|| {
673                    $crate::db::with_request_execution(|| {
674                        crate::__icydb_generated::endpoint_handlers::sql_query::<true>(sql)
675                    })
676                })
677            }
678        }
679        $crate::__icydb_endpoints_internal!($($rest)*);
680    };
681
682    ($(#[cfg($($cfg:tt)*)])* icydb_ddl; $($rest:tt)*) => {
683        $(#[cfg($($cfg)*)])*
684        #[used]
685        static __ICYDB_ENDPOINT_DECLARATION_DDL: () = ();
686        $(#[cfg($($cfg)*)])*
687        $crate::__icydb_with_sql_endpoint! {
688            "icydb_ddl";
689            #[$crate::__reexports::ic_cdk::update(name = "icydb_ddl")]
690            fn __icydb_export_icydb_ddl(
691                sql: String,
692            ) -> Result<__icydb_facade::db::sql::SqlQueryResult, __icydb_facade::Error> {
693                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
694                $crate::db::with_request_execution(|| {
695                    crate::__icydb_generated::endpoint_handlers::sql_ddl(sql)
696                })
697            }
698        }
699        $crate::__icydb_endpoints_internal!($($rest)*);
700    };
701
702    ($(#[cfg($($cfg:tt)*)])* icydb_update(admission = primary_key_only); $($rest:tt)*) => {
703        $(#[cfg($($cfg)*)])*
704        #[used]
705        static __ICYDB_ENDPOINT_DECLARATION_UPDATE: () = ();
706        $(#[cfg($($cfg)*)])*
707        $crate::__icydb_with_sql_endpoint! {
708            "icydb_update";
709            #[$crate::__reexports::ic_cdk::update(name = "icydb_update")]
710            fn __icydb_export_icydb_update(
711                sql: String,
712            ) -> Result<__icydb_facade::db::sql::SqlQueryResult, __icydb_facade::Error> {
713                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
714                $crate::db::with_request_execution(|| {
715                    crate::__icydb_generated::endpoint_handlers::sql_update_primary_key(sql)
716                })
717            }
718        }
719        $crate::__icydb_endpoints_internal!($($rest)*);
720    };
721
722    ($(#[cfg($($cfg:tt)*)])* icydb_update(admission = bounded_deterministic); $($rest:tt)*) => {
723        $(#[cfg($($cfg)*)])*
724        #[used]
725        static __ICYDB_ENDPOINT_DECLARATION_UPDATE: () = ();
726        $(#[cfg($($cfg)*)])*
727        $crate::__icydb_with_sql_endpoint! {
728            "icydb_update";
729            #[$crate::__reexports::ic_cdk::update(name = "icydb_update")]
730            fn __icydb_export_icydb_update(
731                sql: String,
732            ) -> Result<__icydb_facade::db::sql::SqlQueryResult, __icydb_facade::Error> {
733                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
734                $crate::db::with_request_execution(|| {
735                    crate::__icydb_generated::endpoint_handlers::sql_update_bounded(sql)
736                })
737            }
738        }
739        $crate::__icydb_endpoints_internal!($($rest)*);
740    };
741
742    ($(#[cfg($($cfg:tt)*)])* icydb_integrity; $($rest:tt)*) => {
743        $(#[cfg($($cfg)*)])*
744        #[used]
745        static __ICYDB_ENDPOINT_DECLARATION_INTEGRITY: () = ();
746        $(#[cfg($($cfg)*)])*
747        $crate::__icydb_with_sql_endpoint! {
748            "icydb_integrity";
749            #[allow(clippy::result_large_err)]
750            #[$crate::__reexports::ic_cdk::update(name = "icydb_integrity")]
751            fn __icydb_export_icydb_integrity(
752                sql: String,
753            ) -> Result<__icydb_facade::db::IntegrityCheckResult, __icydb_facade::db::SqlIntegrityError> {
754                crate::__icydb_generated::endpoint_authorization::require_sql_controller()
755                    .map_err(__icydb_facade::db::SqlIntegrityError::Sql)?;
756                $crate::db::with_request_execution(|| {
757                    crate::__icydb_generated::endpoint_handlers::sql_integrity(sql)
758                })
759            }
760        }
761        $crate::__icydb_endpoints_internal!($($rest)*);
762    };
763
764    ($(#[cfg($($cfg:tt)*)])* icydb_fixtures_reset; $($rest:tt)*) => {
765        $(#[cfg($($cfg)*)])*
766        #[used]
767        static __ICYDB_ENDPOINT_DECLARATION_FIXTURES_RESET: () = ();
768        $(#[cfg($($cfg)*)])*
769        #[cfg(not(feature = "test-admin-api"))]
770        compile_error!("endpoint declaration `icydb_fixtures_reset` requires the canister `test-admin-api` Cargo feature");
771        $(#[cfg($($cfg)*)])*
772        #[cfg(feature = "test-admin-api")]
773        $crate::__icydb_with_sql_endpoint! {
774            "icydb_fixtures_reset";
775            #[$crate::__reexports::ic_cdk::update(name = "icydb_fixtures_reset")]
776            fn __icydb_export_icydb_fixtures_reset() -> Result<(), __icydb_facade::Error> {
777                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
778                $crate::db::with_request_execution(|| {
779                    crate::__icydb_generated::endpoint_handlers::fixtures_reset()
780                })
781            }
782        }
783        $crate::__icydb_endpoints_internal!($($rest)*);
784    };
785
786    ($(#[cfg($($cfg:tt)*)])* icydb_fixtures_load(handler = $handler:path); $($rest:tt)*) => {
787        $(#[cfg($($cfg)*)])*
788        #[used]
789        static __ICYDB_ENDPOINT_DECLARATION_FIXTURES_LOAD: () = ();
790        $(#[cfg($($cfg)*)])*
791        #[cfg(not(feature = "test-admin-api"))]
792        compile_error!("endpoint declaration `icydb_fixtures_load` requires the canister `test-admin-api` Cargo feature");
793        $(#[cfg($($cfg)*)])*
794        #[cfg(feature = "test-admin-api")]
795        $crate::__icydb_with_sql_endpoint! {
796            "icydb_fixtures_load";
797            #[$crate::__reexports::ic_cdk::update(name = "icydb_fixtures_load")]
798            fn __icydb_export_icydb_fixtures_load() -> Result<(), __icydb_facade::Error> {
799                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
800                let handler: fn() -> Result<(), $crate::Error> = $handler;
801                $crate::db::with_request_execution(|| {
802                    crate::__icydb_generated::endpoint_handlers::fixtures_load(handler)
803                })
804            }
805        }
806        $crate::__icydb_endpoints_internal!($($rest)*);
807    };
808
809    ($(#[cfg($($cfg:tt)*)])* icydb_metrics(authorization = public); $($rest:tt)*) => {
810        $(#[cfg($($cfg)*)])*
811        #[used]
812        static __ICYDB_ENDPOINT_DECLARATION_METRICS: () = ();
813        $(#[cfg($($cfg)*)])*
814        #[$crate::__reexports::ic_cdk::query(name = "icydb_metrics")]
815        fn __icydb_export_icydb_metrics(
816            window_start_ms: Option<u64>,
817        ) -> Result<__icydb_facade::metrics::CompactMetricsReport, __icydb_facade::Error> {
818            $crate::__macro::with_query_metrics_context(|| {
819                crate::__icydb_generated::endpoint_handlers::metrics(window_start_ms)
820            })
821        }
822        $crate::__icydb_endpoints_internal!($($rest)*);
823    };
824
825    ($(#[cfg($($cfg:tt)*)])* icydb_metrics(authorization = controller); $($rest:tt)*) => {
826        $(#[cfg($($cfg)*)])*
827        #[used]
828        static __ICYDB_ENDPOINT_DECLARATION_METRICS: () = ();
829        $(#[cfg($($cfg)*)])*
830        #[$crate::__reexports::ic_cdk::query(name = "icydb_metrics")]
831        fn __icydb_export_icydb_metrics(
832            window_start_ms: Option<u64>,
833        ) -> Result<__icydb_facade::metrics::CompactMetricsReport, __icydb_facade::Error> {
834            crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
835            $crate::__macro::with_query_metrics_context(|| {
836                crate::__icydb_generated::endpoint_handlers::metrics(window_start_ms)
837            })
838        }
839        $crate::__icydb_endpoints_internal!($($rest)*);
840    };
841
842    ($(#[cfg($($cfg:tt)*)])* icydb_metrics_extended(authorization = public); $($rest:tt)*) => {
843        $(#[cfg($($cfg)*)])*
844        #[used]
845        static __ICYDB_ENDPOINT_DECLARATION_METRICS_EXTENDED: () = ();
846        $(#[cfg($($cfg)*)])*
847        #[$crate::__reexports::ic_cdk::query(name = "icydb_metrics_extended")]
848        fn __icydb_export_icydb_metrics_extended(
849            window_start_ms: Option<u64>,
850        ) -> Result<__icydb_facade::metrics::EventReport, __icydb_facade::Error> {
851            $crate::__macro::with_query_metrics_context(|| {
852                crate::__icydb_generated::endpoint_handlers::metrics_extended(window_start_ms)
853            })
854        }
855        $crate::__icydb_endpoints_internal!($($rest)*);
856    };
857
858    ($(#[cfg($($cfg:tt)*)])* icydb_metrics_extended(authorization = controller); $($rest:tt)*) => {
859        $(#[cfg($($cfg)*)])*
860        #[used]
861        static __ICYDB_ENDPOINT_DECLARATION_METRICS_EXTENDED: () = ();
862        $(#[cfg($($cfg)*)])*
863        #[$crate::__reexports::ic_cdk::query(name = "icydb_metrics_extended")]
864        fn __icydb_export_icydb_metrics_extended(
865            window_start_ms: Option<u64>,
866        ) -> Result<__icydb_facade::metrics::EventReport, __icydb_facade::Error> {
867            crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
868            $crate::__macro::with_query_metrics_context(|| {
869                crate::__icydb_generated::endpoint_handlers::metrics_extended(window_start_ms)
870            })
871        }
872        $crate::__icydb_endpoints_internal!($($rest)*);
873    };
874
875    ($(#[cfg($($cfg:tt)*)])* icydb_metrics_reset; $($rest:tt)*) => {
876        $(#[cfg($($cfg)*)])*
877        #[used]
878        static __ICYDB_ENDPOINT_DECLARATION_METRICS_RESET: () = ();
879        $(#[cfg($($cfg)*)])*
880        #[$crate::__reexports::ic_cdk::update(name = "icydb_metrics_reset")]
881        fn __icydb_export_icydb_metrics_reset() -> Result<(), __icydb_facade::Error> {
882            crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
883            crate::__icydb_generated::endpoint_handlers::metrics_reset()
884        }
885        $crate::__icydb_endpoints_internal!($($rest)*);
886    };
887
888    ($(#[cfg($($cfg:tt)*)])* icydb_snapshot; $($rest:tt)*) => {
889        $(#[cfg($($cfg)*)])*
890        #[used]
891        static __ICYDB_ENDPOINT_DECLARATION_SNAPSHOT: () = ();
892        $(#[cfg($($cfg)*)])*
893        #[$crate::__reexports::ic_cdk::query(name = "icydb_snapshot")]
894        fn __icydb_export_icydb_snapshot() -> Result<__icydb_facade::db::StorageReport, __icydb_facade::Error> {
895            crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
896            $crate::__macro::with_query_metrics_context(|| {
897                $crate::db::with_request_execution(|| {
898                    crate::__icydb_generated::endpoint_handlers::snapshot()
899                })
900            })
901        }
902        $crate::__icydb_endpoints_internal!($($rest)*);
903    };
904
905    ($(#[cfg($($cfg:tt)*)])* icydb_schema(authorization = public); $($rest:tt)*) => {
906        $(#[cfg($($cfg)*)])*
907        #[used]
908        static __ICYDB_ENDPOINT_DECLARATION_SCHEMA: () = ();
909        $(#[cfg($($cfg)*)])*
910        #[$crate::__reexports::ic_cdk::query(name = "icydb_schema")]
911        fn __icydb_export_icydb_schema(
912        ) -> Result<Vec<__icydb_facade::db::EntitySchemaDescription>, __icydb_facade::Error> {
913            $crate::__macro::with_query_metrics_context(|| {
914                $crate::db::with_request_execution(|| {
915                    crate::__icydb_generated::endpoint_handlers::schema()
916                })
917            })
918        }
919        $crate::__icydb_endpoints_internal!($($rest)*);
920    };
921
922    ($(#[cfg($($cfg:tt)*)])* icydb_schema(
923        authorization = guard($guard:path) $(,)?
924    ); $($rest:tt)*) => {
925        $(#[cfg($($cfg)*)])*
926        #[used]
927        static __ICYDB_ENDPOINT_DECLARATION_SCHEMA: () = ();
928        $(#[cfg($($cfg)*)])*
929        #[$crate::__reexports::ic_cdk::query(name = "icydb_schema")]
930        fn __icydb_export_icydb_schema(
931        ) -> Result<Vec<__icydb_facade::db::EntitySchemaDescription>, __icydb_facade::Error> {
932            let guard: $crate::ReadAuthorizationGuard = $guard;
933            $crate::__macro::authorize_schema_read(
934                $crate::__reexports::ic_cdk::api::msg_caller(),
935                guard,
936            )?;
937            $crate::__macro::with_query_metrics_context(|| {
938                $crate::db::with_request_execution(|| {
939                    crate::__icydb_generated::endpoint_handlers::schema()
940                })
941            })
942        }
943        $crate::__icydb_endpoints_internal!($($rest)*);
944    };
945
946    ($(#[cfg($($cfg:tt)*)])* icydb_schema(authorization = controller); $($rest:tt)*) => {
947        $(#[cfg($($cfg)*)])*
948        #[used]
949        static __ICYDB_ENDPOINT_DECLARATION_SCHEMA: () = ();
950        $(#[cfg($($cfg)*)])*
951        #[$crate::__reexports::ic_cdk::query(name = "icydb_schema")]
952        fn __icydb_export_icydb_schema(
953        ) -> Result<Vec<__icydb_facade::db::EntitySchemaDescription>, __icydb_facade::Error> {
954            crate::__icydb_generated::endpoint_authorization::require_schema_controller()?;
955            $crate::__macro::with_query_metrics_context(|| {
956                $crate::db::with_request_execution(|| {
957                    crate::__icydb_generated::endpoint_handlers::schema()
958                })
959            })
960        }
961        $crate::__icydb_endpoints_internal!($($rest)*);
962    };
963
964    ($(#[cfg($($cfg:tt)*)])* icydb_schema_migrate; $($rest:tt)*) => {
965        $(#[cfg($($cfg)*)])*
966        #[used]
967        static __ICYDB_ENDPOINT_DECLARATION_SCHEMA_MIGRATE: () = ();
968        $(#[cfg($($cfg)*)])*
969        $crate::__icydb_with_migration_endpoint! {
970            "icydb_schema_migrate";
971            #[$crate::__reexports::ic_cdk::update(name = "icydb_schema_migrate")]
972            fn __icydb_export_icydb_schema_migrate(
973                command: __icydb_facade::db::SchemaMigrationCommand,
974            ) -> Result<__icydb_facade::db::SchemaMigrationStatusPage, __icydb_facade::Error> {
975                crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
976                $crate::db::with_request_execution(|| {
977                    crate::__icydb_generated::endpoint_handlers::schema_migrate(command)
978                })
979            }
980        }
981        $crate::__icydb_endpoints_internal!($($rest)*);
982    };
983
984    ($(#[cfg($($cfg:tt)*)])* icydb_schema_migration; $($rest:tt)*) => {
985        $(#[cfg($($cfg)*)])*
986        #[used]
987        static __ICYDB_ENDPOINT_DECLARATION_SCHEMA_MIGRATION: () = ();
988        $(#[cfg($($cfg)*)])*
989        $crate::__icydb_with_migration_endpoint! {
990            "icydb_schema_migration";
991            #[$crate::__reexports::ic_cdk::query(name = "icydb_schema_migration")]
992            fn __icydb_export_icydb_schema_migration(
993                request: __icydb_facade::db::SchemaMigrationStatusRequest,
994            ) -> Result<__icydb_facade::db::SchemaMigrationStatusPage, __icydb_facade::Error> {
995                crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
996                $crate::__macro::with_query_metrics_context(|| {
997                    $crate::db::with_request_execution(|| {
998                        crate::__icydb_generated::endpoint_handlers::schema_migration(&request)
999                    })
1000                })
1001            }
1002        }
1003        $crate::__icydb_endpoints_internal!($($rest)*);
1004    };
1005
1006    ($(#[cfg($($cfg:tt)*)])* $endpoint:ident $($rest:tt)*) => {
1007        compile_error!(concat!("unknown or invalid IcyDB endpoint declaration `", stringify!($endpoint), "`"));
1008    };
1009
1010    (#[$attribute:meta] $($rest:tt)*) => {
1011        compile_error!("IcyDB endpoint declarations accept only `#[cfg(...)]` attributes");
1012    };
1013
1014    ($($invalid:tt)+) => {
1015        compile_error!("invalid IcyDB endpoint declaration syntax");
1016    };
1017}
1018
1019/// Access the active request's database session.
1020///
1021/// Use `db!()` in ordinary generated endpoints and nested helpers. Every call
1022/// shares the execution counters installed at request entry. Manual IC-CDK,
1023/// framework, and timer entries establish that boundary with
1024/// [`request_execution`]; lifecycle callbacks declared by the composed
1025/// [`start!`] form receive it automatically.
1026///
1027/// `db!(&request_root)` is the explicit low-level integration form for a
1028/// framework that already owns a request root. Obtain that root from
1029/// [`db::with_request_execution_root`](crate::db::with_request_execution_root);
1030/// passing it never creates fresh counters and fails if a different root is
1031/// already active.
1032#[macro_export]
1033#[expect(clippy::crate_in_macro_def)]
1034macro_rules! db {
1035    () => {
1036        crate::db()
1037    };
1038    ($request_root:expr) => {
1039        crate::db_with_request_root($request_root)
1040    };
1041}
1042
1043//
1044// Helpers
1045//
1046
1047#[cfg(all(test, not(target_arch = "wasm32")))]
1048mod tests {
1049    use crate::build;
1050
1051    struct ModelWrapper(u64);
1052
1053    impl icydb_model::Inner<u64> for ModelWrapper {
1054        fn inner(&self) -> &u64 {
1055            &self.0
1056        }
1057
1058        fn into_inner(self) -> u64 {
1059            self.0
1060        }
1061    }
1062
1063    #[test]
1064    fn build_facade_exports_typed_entrypoint() {
1065        fn assert_model_inner<T: crate::traits::Inner<u64>>() {}
1066
1067        assert_model_inner::<ModelWrapper>();
1068        let wrapper = ModelWrapper(7);
1069        assert_eq!(*crate::traits::Inner::inner(&wrapper), 7);
1070        assert_eq!(crate::traits::Inner::into_inner(wrapper), 7);
1071
1072        std::hint::black_box(
1073            build_facade_macros_resolve as fn() -> Result<(), Box<dyn std::error::Error>>,
1074        );
1075    }
1076
1077    fn build_facade_macros_resolve() -> Result<(), Box<dyn std::error::Error>> {
1078        build::build_canister!(())?;
1079
1080        Ok(())
1081    }
1082}