Skip to main content

json_eval_rs/jsoneval/
core.rs

1use super::JSONEval;
2use crate::jsoneval::eval_data::EvalData;
3use crate::jsoneval::json_parser;
4use crate::jsoneval::parsed_schema::ParsedSchema;
5use crate::jsoneval::parsed_schema_cache::PARSED_SCHEMA_CACHE;
6use crate::parse_schema;
7use crate::rlogic::{RLogic, RLogicConfig};
8
9use crate::time_block;
10
11use indexmap::IndexMap;
12use serde::de::Error as _;
13use serde_json::Value;
14use std::collections::HashMap;
15use std::sync::{Arc, Mutex, RwLock};
16
17impl Clone for JSONEval {
18    fn clone(&self) -> Self {
19        Self {
20            schema: Arc::clone(&self.schema),
21            engine: Arc::clone(&self.engine),
22            evaluations: self.evaluations.clone(),
23            tables: self.tables.clone(),
24            table_metadata: self.table_metadata.clone(),
25            dependencies: self.dependencies.clone(),
26            sorted_evaluations: self.sorted_evaluations.clone(),
27            dependents_evaluations: self.dependents_evaluations.clone(),
28            rules_evaluations: self.rules_evaluations.clone(),
29            fields_with_rules: self.fields_with_rules.clone(),
30            others_evaluations: self.others_evaluations.clone(),
31            value_evaluations: self.value_evaluations.clone(),
32            layout_paths: self.layout_paths.clone(),
33            layout_field_refs: self.layout_field_refs.clone(),
34            options_templates: self.options_templates.clone(),
35            subforms: self.subforms.clone(),
36            reffed_by: self.reffed_by.clone(),
37            dep_formula_triggers: self.dep_formula_triggers.clone(),
38            context: self.context.clone(),
39            data: self.data.clone(),
40            evaluated_schema: self.evaluated_schema.clone(),
41            eval_data: self.eval_data.clone(),
42            eval_cache: self.eval_cache.clone(),
43            eval_lock: Mutex::new(()), // Create fresh mutex for the clone
44            cached_msgpack_schema: self.cached_msgpack_schema.clone(),
45            resolved_layout_cache: None,
46            conditional_hidden_fields: self.conditional_hidden_fields.clone(),
47            conditional_readonly_fields: self.conditional_readonly_fields.clone(),
48            static_arrays: self.static_arrays.clone(),
49            regex_cache: RwLock::new(HashMap::new()),
50            layout_hidden_refs: indexmap::IndexSet::new(),
51            layout_visible_refs: indexmap::IndexSet::new(),
52            layout_condition_hidden_refs: indexmap::IndexSet::new(),
53        }
54    }
55}
56
57impl JSONEval {
58    pub fn new(
59        schema: &str,
60        context: Option<&str>,
61        data: Option<&str>,
62    ) -> Result<Self, serde_json::Error> {
63        time_block!("JSONEval::new() [total]", {
64            // Use serde_json for schema (needs arbitrary_precision) and SIMD for data (needs speed)
65            let mut schema_val: Value =
66                time_block!("  parse schema JSON", { serde_json::from_str(schema)? });
67            let context: Value = time_block!("  parse context JSON", {
68                json_parser::parse_json_str(context.unwrap_or("{}"))
69                    .map_err(serde_json::Error::custom)?
70            });
71            let data: Value = time_block!("  parse data JSON", {
72                json_parser::parse_json_str(data.unwrap_or("{}"))
73                    .map_err(serde_json::Error::custom)?
74            });
75
76            // Extract large static arrays
77            let static_arrays = if let Some(params) = schema_val
78                .get_mut("$params")
79                .and_then(|v| v.as_object_mut())
80            {
81                crate::jsoneval::static_arrays::extract_from_params(params)
82            } else {
83                IndexMap::new()
84            };
85            let static_arrays = Arc::new(static_arrays);
86            let evaluated_schema = schema_val.clone();
87
88            // Use default config: tracking enabled
89            let engine_config = RLogicConfig::default();
90            let mut engine = RLogic::with_config(engine_config);
91            engine.set_static_arrays(Arc::clone(&static_arrays));
92
93            let mut instance = time_block!("  create instance struct", {
94                Self {
95                    schema: Arc::new(schema_val),
96                    evaluations: Arc::new(IndexMap::new()),
97                    tables: Arc::new(IndexMap::new()),
98                    table_metadata: Arc::new(IndexMap::new()),
99                    dependencies: Arc::new(IndexMap::new()),
100                    sorted_evaluations: Arc::new(Vec::new()),
101                    dependents_evaluations: Arc::new(IndexMap::new()),
102                    rules_evaluations: Arc::new(Vec::new()),
103                    fields_with_rules: Arc::new(Vec::new()),
104                    others_evaluations: Arc::new(Vec::new()),
105                    value_evaluations: Arc::new(Vec::new()),
106                    layout_paths: Arc::new(Vec::new()),
107                    layout_field_refs: Arc::new(indexmap::IndexSet::new()),
108                    options_templates: Arc::new(Vec::new()),
109                    subforms: IndexMap::new(),
110                    engine: Arc::new(engine),
111                    reffed_by: Arc::new(IndexMap::new()),
112                    dep_formula_triggers: Arc::new(IndexMap::new()),
113                    context: context.clone(),
114                    data: data.clone(),
115                    evaluated_schema: evaluated_schema.clone(),
116                    eval_data: EvalData::with_schema_data_context(
117                        &evaluated_schema,
118                        &data,
119                        &context,
120                    ),
121                    eval_cache: crate::jsoneval::eval_cache::EvalCache::new(),
122                    eval_lock: Mutex::new(()),
123                    cached_msgpack_schema: None,
124                    resolved_layout_cache: None,
125                    conditional_hidden_fields: Arc::new(Vec::new()),
126                    conditional_readonly_fields: Arc::new(Vec::new()),
127                    static_arrays,
128                    regex_cache: RwLock::new(HashMap::new()),
129                    layout_hidden_refs: indexmap::IndexSet::new(),
130                    layout_visible_refs: indexmap::IndexSet::new(),
131                    layout_condition_hidden_refs: indexmap::IndexSet::new(),
132                }
133            });
134            time_block!("  parse_schema", {
135                parse_schema::legacy::parse_schema(&mut instance)
136                    .map_err(serde_json::Error::custom)?
137            });
138            Ok(instance)
139        })
140    }
141
142    /// Create a new JSONEval instance for a subform, avoiding string serialization
143    pub(crate) fn new_subform(
144        schema_val: Value,
145        context: Value,
146        static_arrays: Arc<IndexMap<String, Arc<Value>>>,
147    ) -> Result<Self, serde_json::Error> {
148        time_block!("JSONEval::new_subform() [total]", {
149            // Data is empty for a subform initially
150            let data = Value::Object(serde_json::Map::new());
151            let evaluated_schema = schema_val.clone();
152
153            // Use default config: tracking enabled
154            let engine_config = RLogicConfig::default();
155            let mut engine = RLogic::with_config(engine_config);
156            engine.set_static_arrays(Arc::clone(&static_arrays));
157
158            let mut instance = time_block!("  create instance struct", {
159                Self {
160                    schema: Arc::new(schema_val),
161                    evaluations: Arc::new(IndexMap::new()),
162                    tables: Arc::new(IndexMap::new()),
163                    table_metadata: Arc::new(IndexMap::new()),
164                    dependencies: Arc::new(IndexMap::new()),
165                    sorted_evaluations: Arc::new(Vec::new()),
166                    dependents_evaluations: Arc::new(IndexMap::new()),
167                    rules_evaluations: Arc::new(Vec::new()),
168                    fields_with_rules: Arc::new(Vec::new()),
169                    others_evaluations: Arc::new(Vec::new()),
170                    value_evaluations: Arc::new(Vec::new()),
171                    layout_paths: Arc::new(Vec::new()),
172                    layout_field_refs: Arc::new(indexmap::IndexSet::new()),
173                    options_templates: Arc::new(Vec::new()),
174                    subforms: IndexMap::new(),
175                    engine: Arc::new(engine),
176                    reffed_by: Arc::new(IndexMap::new()),
177                    dep_formula_triggers: Arc::new(IndexMap::new()),
178                    context: context.clone(),
179                    data: data.clone(),
180                    evaluated_schema: evaluated_schema.clone(),
181                    eval_data: EvalData::with_schema_data_context(
182                        &evaluated_schema,
183                        &data,
184                        &context,
185                    ),
186                    eval_cache: crate::jsoneval::eval_cache::EvalCache::new(),
187                    eval_lock: Mutex::new(()),
188                    cached_msgpack_schema: None,
189                    resolved_layout_cache: None,
190                    conditional_hidden_fields: Arc::new(Vec::new()),
191                    conditional_readonly_fields: Arc::new(Vec::new()),
192                    static_arrays,
193                    regex_cache: RwLock::new(HashMap::new()),
194                    layout_hidden_refs: indexmap::IndexSet::new(),
195                    layout_visible_refs: indexmap::IndexSet::new(),
196                    layout_condition_hidden_refs: indexmap::IndexSet::new(),
197                }
198            });
199            time_block!("  parse_schema", {
200                parse_schema::legacy::parse_schema(&mut instance)
201                    .map_err(serde_json::Error::custom)?
202            });
203            Ok(instance)
204        })
205    }
206
207    /// Create a new JSONEval instance from MessagePack-encoded schema
208    ///
209    /// # Arguments
210    ///
211    /// * `schema_msgpack` - MessagePack-encoded schema bytes
212    /// * `context` - Optional JSON context string
213    /// * `data` - Optional JSON data string
214    ///
215    /// # Returns
216    ///
217    /// A Result containing the JSONEval instance or an error
218    pub fn new_from_msgpack(
219        schema_msgpack: &[u8],
220        context: Option<&str>,
221        data: Option<&str>,
222    ) -> Result<Self, String> {
223        // Store original MessagePack bytes for zero-copy retrieval
224        let cached_msgpack = schema_msgpack.to_vec();
225
226        // Deserialize MessagePack schema to Value
227        let mut schema_val: Value = rmp_serde::from_slice(schema_msgpack)
228            .map_err(|e| format!("Failed to deserialize MessagePack schema: {}", e))?;
229
230        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))
231            .map_err(|e| format!("Failed to parse context: {}", e))?;
232        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))
233            .map_err(|e| format!("Failed to parse data: {}", e))?;
234
235        // Extract large static arrays
236        let static_arrays = if let Some(params) = schema_val
237            .get_mut("$params")
238            .and_then(|v| v.as_object_mut())
239        {
240            crate::jsoneval::static_arrays::extract_from_params(params)
241        } else {
242            IndexMap::new()
243        };
244        let static_arrays = Arc::new(static_arrays);
245        let evaluated_schema = schema_val.clone();
246
247        let engine_config = RLogicConfig::default();
248        let mut engine = RLogic::with_config(engine_config);
249        engine.set_static_arrays(Arc::clone(&static_arrays));
250
251        let mut instance = Self {
252            schema: Arc::new(schema_val),
253            evaluations: Arc::new(IndexMap::new()),
254            tables: Arc::new(IndexMap::new()),
255            table_metadata: Arc::new(IndexMap::new()),
256            dependencies: Arc::new(IndexMap::new()),
257            sorted_evaluations: Arc::new(Vec::new()),
258            dependents_evaluations: Arc::new(IndexMap::new()),
259            rules_evaluations: Arc::new(Vec::new()),
260            fields_with_rules: Arc::new(Vec::new()),
261            others_evaluations: Arc::new(Vec::new()),
262            value_evaluations: Arc::new(Vec::new()),
263            layout_paths: Arc::new(Vec::new()),
264            layout_field_refs: Arc::new(indexmap::IndexSet::new()),
265            options_templates: Arc::new(Vec::new()),
266            subforms: IndexMap::new(),
267            engine: Arc::new(engine),
268            reffed_by: Arc::new(IndexMap::new()),
269            dep_formula_triggers: Arc::new(IndexMap::new()),
270            context: context.clone(),
271            data: data.clone(),
272            evaluated_schema: evaluated_schema.clone(),
273            eval_data: EvalData::with_schema_data_context(&evaluated_schema, &data, &context),
274            eval_cache: crate::jsoneval::eval_cache::EvalCache::new(),
275            eval_lock: Mutex::new(()),
276            cached_msgpack_schema: Some(cached_msgpack),
277            resolved_layout_cache: None,
278            conditional_hidden_fields: Arc::new(Vec::new()),
279            conditional_readonly_fields: Arc::new(Vec::new()),
280            static_arrays,
281            regex_cache: RwLock::new(HashMap::new()),
282            layout_hidden_refs: indexmap::IndexSet::new(),
283            layout_visible_refs: indexmap::IndexSet::new(),
284            layout_condition_hidden_refs: indexmap::IndexSet::new(),
285        };
286        parse_schema::legacy::parse_schema(&mut instance)?;
287        Ok(instance)
288    }
289
290    /// Create a new JSONEval instance from a pre-parsed ParsedSchema
291    ///
292    /// This enables schema caching: parse once, reuse across multiple evaluations with different data/context.
293    ///
294    /// # Arguments
295    ///
296    /// * `parsed` - Arc-wrapped pre-parsed schema (can be cloned and cached)
297    /// * `context` - Optional JSON context string
298    /// * `data` - Optional JSON data string
299    ///
300    /// # Returns
301    ///
302    /// A Result containing the JSONEval instance or an error
303    ///
304    /// # Example
305    ///
306    /// ```ignore
307    /// use std::sync::Arc;
308    ///
309    /// // Parse schema once and wrap in Arc for caching
310    /// let parsed = Arc::new(ParsedSchema::parse(schema_str)?);
311    /// cache.insert(schema_key, parsed.clone());
312    ///
313    /// // Reuse across multiple evaluations (Arc::clone is cheap)
314    /// let eval1 = JSONEval::with_parsed_schema(parsed.clone(), Some(context1), Some(data1))?;
315    /// let eval2 = JSONEval::with_parsed_schema(parsed.clone(), Some(context2), Some(data2))?;
316    /// ```
317    pub fn with_parsed_schema(
318        parsed: Arc<ParsedSchema>,
319        context: Option<&str>,
320        data: Option<&str>,
321    ) -> Result<Self, String> {
322        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))
323            .map_err(|e| format!("Failed to parse context: {}", e))?;
324        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))
325            .map_err(|e| format!("Failed to parse data: {}", e))?;
326
327        let evaluated_schema = parsed.schema.clone();
328
329        // Share the engine Arc (cheap pointer clone, not data clone)
330        // Multiple JSONEval instances created from the same ParsedSchema will share the compiled RLogic
331        let engine = parsed.engine.clone();
332
333        // Convert Arc<ParsedSchema> subforms to Box<JSONEval> subforms
334        // This is a one-time conversion when creating JSONEval from ParsedSchema
335        let mut subforms = IndexMap::new();
336        for (path, subform_parsed) in &parsed.subforms {
337            // Create JSONEval from the cached ParsedSchema
338            let subform_eval =
339                JSONEval::with_parsed_schema(subform_parsed.clone(), Some("{}"), None)?;
340            subforms.insert(path.clone(), Box::new(subform_eval));
341        }
342
343        let instance = Self {
344            schema: Arc::clone(&parsed.schema),
345            evaluations: Arc::clone(&parsed.evaluations),
346            tables: Arc::clone(&parsed.tables),
347            table_metadata: Arc::clone(&parsed.table_metadata),
348            dependencies: Arc::clone(&parsed.dependencies),
349            sorted_evaluations: Arc::clone(&parsed.sorted_evaluations),
350            dependents_evaluations: Arc::clone(&parsed.dependents_evaluations),
351            rules_evaluations: Arc::clone(&parsed.rules_evaluations),
352            fields_with_rules: Arc::clone(&parsed.fields_with_rules),
353            others_evaluations: Arc::clone(&parsed.others_evaluations),
354            value_evaluations: Arc::clone(&parsed.value_evaluations),
355            layout_paths: Arc::clone(&parsed.layout_paths),
356            layout_field_refs: Arc::clone(&parsed.layout_field_refs),
357            options_templates: Arc::clone(&parsed.options_templates),
358            subforms,
359            engine,
360            reffed_by: Arc::clone(&parsed.reffed_by),
361            dep_formula_triggers: Arc::clone(&parsed.dep_formula_triggers),
362            context: context.clone(),
363            data: data.clone(),
364            evaluated_schema: (*evaluated_schema).clone(),
365            eval_data: EvalData::with_schema_data_context(&evaluated_schema, &data, &context),
366            eval_cache: crate::jsoneval::eval_cache::EvalCache::new(),
367            eval_lock: Mutex::new(()),
368            cached_msgpack_schema: None,
369            resolved_layout_cache: None,
370            conditional_hidden_fields: Arc::clone(&parsed.conditional_hidden_fields),
371            conditional_readonly_fields: Arc::clone(&parsed.conditional_readonly_fields),
372            static_arrays: Arc::clone(&parsed.static_arrays),
373            regex_cache: RwLock::new(HashMap::new()),
374            layout_hidden_refs: indexmap::IndexSet::new(),
375            layout_visible_refs: indexmap::IndexSet::new(),
376            layout_condition_hidden_refs: indexmap::IndexSet::new(),
377        };
378        Ok(instance)
379    }
380
381    pub fn reload_schema(
382        &mut self,
383        schema: &str,
384        context: Option<&str>,
385        data: Option<&str>,
386    ) -> Result<(), String> {
387        // Use serde_json for schema (precision) and SIMD for data (speed)
388        let mut schema_val: Value =
389            serde_json::from_str(schema).map_err(|e| format!("failed to parse schema: {e}"))?;
390        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
391        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
392        self.context = context.clone();
393        self.data = data.clone();
394
395        let static_arrays = if let Some(params) = schema_val
396            .get_mut("$params")
397            .and_then(|v| v.as_object_mut())
398        {
399            crate::jsoneval::static_arrays::extract_from_params(params)
400        } else {
401            IndexMap::new()
402        };
403        let static_arrays = Arc::new(static_arrays);
404        self.static_arrays = Arc::clone(&static_arrays);
405        self.schema = Arc::new(schema_val);
406        self.evaluated_schema = (*self.schema).clone();
407
408        let mut engine = RLogic::new();
409        engine.set_static_arrays(static_arrays);
410        self.engine = Arc::new(engine);
411
412        self.evaluations = Arc::new(IndexMap::new());
413        self.tables = Arc::new(IndexMap::new());
414        self.table_metadata = Arc::new(IndexMap::new());
415        self.dependencies = Arc::new(IndexMap::new());
416        self.sorted_evaluations = Arc::new(Vec::new());
417        self.dependents_evaluations = Arc::new(IndexMap::new());
418        self.rules_evaluations = Arc::new(Vec::new());
419        self.fields_with_rules = Arc::new(Vec::new());
420        self.others_evaluations = Arc::new(Vec::new());
421        self.value_evaluations = Arc::new(Vec::new());
422        self.layout_paths = Arc::new(Vec::new());
423        self.options_templates = Arc::new(Vec::new());
424        self.reffed_by = Arc::new(IndexMap::new());
425        self.dep_formula_triggers = Arc::new(IndexMap::new());
426        self.conditional_hidden_fields = Arc::new(Vec::new());
427        self.conditional_readonly_fields = Arc::new(Vec::new());
428        self.subforms.clear();
429        parse_schema::legacy::parse_schema(self)?;
430
431        // Re-initialize eval_data with new schema, data, and context
432        self.eval_data =
433            EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
434        self.eval_cache.clear();
435        if let Ok(mut cache) = self.regex_cache.write() {
436            cache.clear();
437        }
438
439        // Clear MessagePack cache since schema has been mutated
440        self.cached_msgpack_schema = None;
441        self.resolved_layout_cache = None;
442        self.layout_hidden_refs.clear();
443        self.layout_visible_refs.clear();
444        self.layout_condition_hidden_refs.clear();
445
446        Ok(())
447    }
448
449    /// Set the timezone offset for datetime operations (TODAY, NOW)
450    ///
451    /// This method updates the RLogic engine configuration with a new timezone offset.
452    /// The offset will be applied to all subsequent datetime evaluations.
453    ///
454    /// # Arguments
455    ///
456    /// * `offset_minutes` - Timezone offset in minutes from UTC (e.g., 420 for UTC+7, -300 for UTC-5)
457    ///   Pass `None` to reset to UTC (no offset)
458    ///
459    /// # Example
460    ///
461    /// ```ignore
462    /// let mut eval = JSONEval::new(schema, None, None)?;
463    ///
464    /// // Set to UTC+7 (Jakarta, Bangkok)
465    /// eval.set_timezone_offset(Some(420));
466    ///
467    /// // Reset to UTC
468    /// eval.set_timezone_offset(None);
469    /// ```
470    pub fn set_timezone_offset(&mut self, offset_minutes: Option<i32>) {
471        // Create new config with the timezone offset
472        let mut config = RLogicConfig::default();
473        if let Some(offset) = offset_minutes {
474            config = config.with_timezone_offset(offset);
475        }
476
477        // Recreate the engine with the new configuration
478        // This is necessary because RLogic is wrapped in Arc and config is part of the evaluator
479        let mut engine = RLogic::with_config(config);
480        engine.set_static_arrays(Arc::clone(&self.static_arrays));
481        self.engine = Arc::new(engine);
482
483        let _ = parse_schema::legacy::parse_schema(self);
484    }
485
486    /// Reload schema from MessagePack-encoded bytes
487    ///
488    /// # Arguments
489    ///
490    /// * `schema_msgpack` - MessagePack-encoded schema bytes
491    /// * `context` - Optional context data JSON string
492    /// * `data` - Optional initial data JSON string
493    ///
494    /// # Returns
495    ///
496    /// A `Result` indicating success or an error message
497    pub fn reload_schema_msgpack(
498        &mut self,
499        schema_msgpack: &[u8],
500        context: Option<&str>,
501        data: Option<&str>,
502    ) -> Result<(), String> {
503        // Deserialize MessagePack to Value
504        let mut schema_val: Value = rmp_serde::from_slice(schema_msgpack)
505            .map_err(|e| format!("failed to deserialize MessagePack schema: {e}"))?;
506
507        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
508        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
509
510        self.context = context.clone();
511        self.data = data.clone();
512
513        let static_arrays = if let Some(params) = schema_val
514            .get_mut("$params")
515            .and_then(|v| v.as_object_mut())
516        {
517            crate::jsoneval::static_arrays::extract_from_params(params)
518        } else {
519            IndexMap::new()
520        };
521        let static_arrays = Arc::new(static_arrays);
522        self.static_arrays = Arc::clone(&static_arrays);
523        self.schema = Arc::new(schema_val);
524        self.evaluated_schema = (*self.schema).clone();
525
526        let mut engine = RLogic::new();
527        engine.set_static_arrays(static_arrays);
528        self.engine = Arc::new(engine);
529        self.evaluations = Arc::new(IndexMap::new());
530        self.tables = Arc::new(IndexMap::new());
531        self.table_metadata = Arc::new(IndexMap::new());
532        self.dependencies = Arc::new(IndexMap::new());
533        self.sorted_evaluations = Arc::new(Vec::new());
534        self.dependents_evaluations = Arc::new(IndexMap::new());
535        self.rules_evaluations = Arc::new(Vec::new());
536        self.fields_with_rules = Arc::new(Vec::new());
537        self.others_evaluations = Arc::new(Vec::new());
538        self.value_evaluations = Arc::new(Vec::new());
539        self.layout_paths = Arc::new(Vec::new());
540        self.options_templates = Arc::new(Vec::new());
541        self.reffed_by = Arc::new(IndexMap::new());
542        self.dep_formula_triggers = Arc::new(IndexMap::new());
543        self.conditional_hidden_fields = Arc::new(Vec::new());
544        self.conditional_readonly_fields = Arc::new(Vec::new());
545        self.subforms.clear();
546        parse_schema::legacy::parse_schema(self)?;
547
548        // Re-initialize eval_data
549        self.eval_data =
550            EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
551        self.eval_cache.clear();
552        if let Ok(mut cache) = self.regex_cache.write() {
553            cache.clear();
554        }
555
556        // Cache the MessagePack for future retrievals
557        self.cached_msgpack_schema = Some(schema_msgpack.to_vec());
558        self.resolved_layout_cache = None;
559        self.layout_hidden_refs.clear();
560        self.layout_visible_refs.clear();
561        self.layout_condition_hidden_refs.clear();
562
563        Ok(())
564    }
565
566    /// Reload schema from a cached ParsedSchema
567    ///
568    /// This is the most efficient way to reload as it reuses pre-parsed schema compilation.
569    ///
570    /// # Arguments
571    ///
572    /// * `parsed` - Arc reference to a cached ParsedSchema
573    /// * `context` - Optional context data JSON string
574    /// * `data` - Optional initial data JSON string
575    ///
576    /// # Returns
577    ///
578    /// A `Result` indicating success or an error message
579    pub fn reload_schema_parsed(
580        &mut self,
581        parsed: Arc<ParsedSchema>,
582        context: Option<&str>,
583        data: Option<&str>,
584    ) -> Result<(), String> {
585        let context: Value = json_parser::parse_json_str(context.unwrap_or("{}"))?;
586        let data: Value = json_parser::parse_json_str(data.unwrap_or("{}"))?;
587
588        // Share all the pre-compiled data from ParsedSchema
589        self.schema = Arc::clone(&parsed.schema);
590        self.evaluations = parsed.evaluations.clone();
591        self.tables = parsed.tables.clone();
592        self.table_metadata = parsed.table_metadata.clone();
593        self.dependencies = parsed.dependencies.clone();
594        self.sorted_evaluations = parsed.sorted_evaluations.clone();
595        self.dependents_evaluations = parsed.dependents_evaluations.clone();
596        self.rules_evaluations = parsed.rules_evaluations.clone();
597        self.fields_with_rules = parsed.fields_with_rules.clone();
598        self.others_evaluations = parsed.others_evaluations.clone();
599        self.value_evaluations = parsed.value_evaluations.clone();
600        self.layout_paths = parsed.layout_paths.clone();
601        self.options_templates = parsed.options_templates.clone();
602        self.static_arrays = parsed.static_arrays.clone();
603        self.dep_formula_triggers = parsed.dep_formula_triggers.clone();
604
605        // Share the engine Arc (cheap pointer clone, not data clone)
606        self.engine = parsed.engine.clone();
607
608        // Convert Arc<ParsedSchema> subforms to Box<JSONEval> subforms
609        let mut subforms = IndexMap::new();
610        for (path, subform_parsed) in &parsed.subforms {
611            let subform_eval =
612                JSONEval::with_parsed_schema(subform_parsed.clone(), Some("{}"), None)?;
613            subforms.insert(path.clone(), Box::new(subform_eval));
614        }
615        self.subforms = subforms;
616
617        self.context = context.clone();
618        self.data = data.clone();
619        self.evaluated_schema = (*self.schema).clone();
620
621        // Re-initialize eval_data
622        self.eval_data =
623            EvalData::with_schema_data_context(&self.evaluated_schema, &data, &context);
624        self.eval_cache.clear();
625        self.engine.clear_indices();
626        if let Ok(mut cache) = self.regex_cache.write() {
627            cache.clear();
628        }
629
630        // Clear MessagePack cache since we're loading from ParsedSchema
631        self.cached_msgpack_schema = None;
632        self.resolved_layout_cache = None;
633        self.layout_hidden_refs.clear();
634        self.layout_visible_refs.clear();
635        self.layout_condition_hidden_refs.clear();
636
637        Ok(())
638    }
639
640    /// Reload schema from ParsedSchemaCache using a cache key
641    ///
642    /// This is the recommended way for cross-platform cached schema reloading.
643    ///
644    /// # Arguments
645    ///
646    /// * `cache_key` - Key to lookup in the global ParsedSchemaCache
647    /// * `context` - Optional context data JSON string
648    /// * `data` - Optional initial data JSON string
649    ///
650    /// # Returns
651    ///
652    /// A `Result` indicating success or an error message
653    pub fn reload_schema_from_cache(
654        &mut self,
655        cache_key: &str,
656        context: Option<&str>,
657        data: Option<&str>,
658    ) -> Result<(), String> {
659        // Get the cached ParsedSchema from global cache
660        let parsed = PARSED_SCHEMA_CACHE
661            .get(cache_key)
662            .ok_or_else(|| format!("Schema '{}' not found in cache", cache_key))?;
663
664        // Use reload_schema_parsed with the cached schema
665        self.reload_schema_parsed(parsed, context, data)
666    }
667}