1- //! Model config values read as typed config nodes, each key converted on first read and kept .
1+ //! Built model config trees retained natively, with Python-authored values converted on first read.
22
3+ use std:: collections:: HashMap ;
34use std:: sync:: { Arc , Mutex } ;
45
56use pyo3:: exceptions:: PyRuntimeError ;
@@ -12,11 +13,11 @@ use sqlbuild_model_config::config_values::main::checked_flag::checked_flag;
1213use sqlbuild_model_config:: config_values:: main:: checked_integer:: checked_integer;
1314use sqlbuild_model_config:: config_values:: main:: checked_text:: checked_text;
1415use sqlbuild_model_config:: config_values:: main:: checked_texts:: checked_texts;
15- use sqlbuild_model_config:: config_values:: models:: { ConfigNode , CursorBound } ;
16+ use sqlbuild_model_config:: config_values:: models:: { ConfigNode , CursorBound , HostChildren } ;
1617
1718use crate :: bindings:: _helpers:: compile_driver:: stage_work:: { TYPED_CONFIG_READS , count_stage} ;
1819use crate :: bindings:: _helpers:: model_config:: config_nodes:: {
19- HostHandle , PyConfigNode , config_node, config_object,
20+ HostHandle , PyConfigEntries , PyConfigNode , config_node, config_object, is_exact_container ,
2021} ;
2122use crate :: bindings:: _helpers:: model_config:: config_values:: config_value_type_error;
2223use crate :: bindings:: _helpers:: records:: fields:: {
@@ -31,19 +32,69 @@ const ISOFORMAT_METHOD: &str = "isoformat";
3132const TYPED_VALUES_SLOT : & str = "__typed_values__" ;
3233const POISONED : & str = "typed config values cache is poisoned" ;
3334
34- /// The typed nodes of a config record's values read so far, by key.
35+ /// A built config's string-keyed tree; values shared across reused configs stay native.
36+ pub ( crate ) type BuiltConfigValues = Arc < HashMap < String , Arc < PyConfigNode > > > ;
37+
38+ /// The built tree and any Python-authored values opened by the record's readers.
3539#[ pyclass( module = "sqlbuild._native" , frozen) ]
3640pub ( crate ) struct TypedConfigValues {
41+ pub ( crate ) built : Option < BuiltConfigValues > ,
3742 read : Mutex < Vec < ( String , Arc < PyConfigNode > ) > > ,
3843}
3944
45+ /// Keep the builder's tree without interpreting custom Python mapping keys natively.
46+ pub ( crate ) fn built_values ( entries : PyConfigEntries ) -> Option < BuiltConfigValues > {
47+ let mut values = HashMap :: with_capacity ( entries. len ( ) ) ;
48+ for ( key, value) in entries {
49+ let ConfigNode :: Str ( key) = key else {
50+ return None ;
51+ } ;
52+ values. insert ( key, Arc :: new ( value) ) ;
53+ }
54+ Some ( Arc :: new ( values) )
55+ }
56+
57+ /// Attach the built tree to its public record, rather than reconstructing it from that view.
58+ pub ( crate ) fn retain_values (
59+ config : & Bound < ' _ , PyAny > ,
60+ built : Option < & BuiltConfigValues > ,
61+ ) -> PyResult < ( ) > {
62+ if let Some ( built) = built
63+ && let Ok ( record) = config. cast :: < NativeRecord > ( )
64+ {
65+ let typed = Bound :: new (
66+ config. py ( ) ,
67+ TypedConfigValues {
68+ built : Some ( Arc :: clone ( built) ) ,
69+ read : Mutex :: new ( Vec :: new ( ) ) ,
70+ } ,
71+ ) ?;
72+ store_cached ( record, TYPED_VALUES_SLOT , & typed. into_any ( ) . unbind ( ) ) ?;
73+ }
74+ Ok ( ( ) )
75+ }
76+
4077/// A config record's values and the typed nodes read of them, each key converted on first read.
4178pub ( crate ) type ConfigValues < ' py > = ( Bound < ' py , PyAny > , Bound < ' py , TypedConfigValues > ) ;
4279
4380/// `values.get(key)` as a typed node, `None` when absent; each key is converted once.
4481fn node ( values : & ConfigValues < ' _ > , key : & str ) -> PyResult < Arc < PyConfigNode > > {
4582 let ( mapping, typed) = values;
4683 let py: Python < ' _ > = mapping. py ( ) ;
84+ let built = typed. get ( ) . built . as_ref ( ) . map ( |values| values. get ( key) ) ;
85+ let deferred = match built {
86+ Some ( Some ( node) ) => match node. as_ref ( ) {
87+ ConfigNode :: Host ( host)
88+ if matches ! ( host. children, HostChildren :: None )
89+ && is_exact_container ( host. handle . 0 . bind ( py) ) =>
90+ {
91+ Some ( host. handle . 0 . bind ( py) )
92+ }
93+ _ => return Ok ( Arc :: clone ( node) ) ,
94+ } ,
95+ Some ( None ) => return Ok ( Arc :: new ( ConfigNode :: Null ) ) ,
96+ None => None ,
97+ } ;
4798 let read: & Mutex < Vec < ( String , Arc < PyConfigNode > ) > > = & typed. get ( ) . read ;
4899 let poisoned = || PyRuntimeError :: new_err ( POISONED ) ;
49100 let found: Option < Arc < PyConfigNode > > = read
@@ -55,7 +106,10 @@ fn node(values: &ConfigValues<'_>, key: &str) -> PyResult<Arc<PyConfigNode>> {
55106 if let Some ( node) = found {
56107 return Ok ( node) ;
57108 }
58- let node: Arc < PyConfigNode > = Arc :: new ( config_node ( & mapping_get ( mapping, key) ?) ?) ;
109+ let node: Arc < PyConfigNode > = Arc :: new ( match deferred {
110+ Some ( value) => config_node ( value) ?,
111+ None => config_node ( & mapping_get ( mapping, key) ?) ?,
112+ } ) ;
59113 count_stage ( py, TYPED_CONFIG_READS , 1 ) ?;
60114 read. lock ( )
61115 . map_err ( |_| poisoned ( ) ) ?
@@ -71,6 +125,7 @@ pub(crate) fn typed_values<'py>(config: &Bound<'py, PyAny>) -> PyResult<ConfigVa
71125 Bound :: new (
72126 py,
73127 TypedConfigValues {
128+ built : None ,
74129 read : Mutex :: new ( Vec :: new ( ) ) ,
75130 } ,
76131 )
0 commit comments