Skip to main content

wp_knowledge/
facade.rs

1use std::path::Path;
2use std::sync::Arc;
3use std::time::Duration;
4use std::{
5    collections::hash_map::DefaultHasher,
6    collections::{HashMap, HashSet},
7    hash::{Hash, Hasher},
8};
9
10use crate::error::KnowledgeResult;
11use async_trait::async_trait;
12use rusqlite::ToSql;
13use rusqlite::{Connection, OpenFlags};
14use wp_log::{info_ctrl, warn_kdb};
15use wp_model_core::model::DataField;
16
17use crate::cache::CacheAble;
18use crate::loader::{
19    DEFAULT_SQLDB_NAME, ProviderKind, SqlProviderKind, SqlProviderSpec, parse_knowdb_conf,
20};
21use crate::mem::RowData;
22use crate::mem::memdb::MemDB;
23use crate::mem::thread_clone::ThreadClonedMDB;
24use crate::mysql::{MySqlProvider, MySqlProviderConfig};
25use crate::param::named_params_to_fields;
26use crate::postgres::{PostgresProvider, PostgresProviderConfig};
27use crate::runtime::{
28    CachePolicy, CachedRedisValue, DatasourceId, Generation, MetadataCacheScope, ProviderExecutor,
29    QueryRequest, QueryResponse, RedisCacheKey, RedisCmdTag, RuntimeSnapshot, runtime,
30};
31use crate::telemetry::{
32    CacheLayer, CacheOutcome, CacheTelemetryEvent, KnowledgeTelemetry, install_telemetry,
33    telemetry, telemetry_enabled,
34};
35
36struct MemProvider {
37    db: MemDB,
38    metadata_scope: MetadataCacheScope,
39}
40
41#[async_trait]
42impl ProviderExecutor for ThreadClonedMDB {
43    fn query(&self, sql: &str) -> KnowledgeResult<Vec<RowData>> {
44        ThreadClonedMDB::query_with_scope(self, sql)
45    }
46
47    fn query_fields(&self, sql: &str, params: &[DataField]) -> KnowledgeResult<Vec<RowData>> {
48        ThreadClonedMDB::query_fields_with_scope(self, sql, params)
49    }
50
51    fn query_row(&self, sql: &str) -> KnowledgeResult<RowData> {
52        ThreadClonedMDB::query_row_with_scope(self, sql)
53    }
54
55    fn query_named_fields(&self, sql: &str, params: &[DataField]) -> KnowledgeResult<RowData> {
56        ThreadClonedMDB::query_named_fields_with_scope(self, sql, params)
57    }
58}
59
60#[async_trait]
61impl ProviderExecutor for MemProvider {
62    fn query(&self, sql: &str) -> KnowledgeResult<Vec<RowData>> {
63        self.db.query_with_scope(&self.metadata_scope, sql)
64    }
65
66    fn query_fields(&self, sql: &str, params: &[DataField]) -> KnowledgeResult<Vec<RowData>> {
67        self.db
68            .query_fields_with_scope(&self.metadata_scope, sql, params)
69    }
70
71    fn query_row(&self, sql: &str) -> KnowledgeResult<RowData> {
72        self.db.query_row_with_scope(&self.metadata_scope, sql)
73    }
74
75    fn query_named_fields(&self, sql: &str, params: &[DataField]) -> KnowledgeResult<RowData> {
76        self.db
77            .query_named_fields_with_scope(&self.metadata_scope, sql, params)
78    }
79}
80
81#[async_trait]
82impl ProviderExecutor for PostgresProvider {
83    fn query(&self, sql: &str) -> KnowledgeResult<Vec<RowData>> {
84        PostgresProvider::query(self, sql)
85    }
86
87    fn query_fields(&self, sql: &str, params: &[DataField]) -> KnowledgeResult<Vec<RowData>> {
88        PostgresProvider::query_fields(self, sql, params)
89    }
90
91    fn query_row(&self, sql: &str) -> KnowledgeResult<RowData> {
92        PostgresProvider::query_row(self, sql)
93    }
94
95    fn query_named_fields(&self, sql: &str, params: &[DataField]) -> KnowledgeResult<RowData> {
96        PostgresProvider::query_named_fields(self, sql, params)
97    }
98
99    async fn query_async(&self, sql: &str) -> KnowledgeResult<Vec<RowData>> {
100        PostgresProvider::query_async(self, sql).await
101    }
102
103    async fn query_fields_async(
104        &self,
105        sql: &str,
106        params: &[DataField],
107    ) -> KnowledgeResult<Vec<RowData>> {
108        PostgresProvider::query_fields_async(self, sql, params).await
109    }
110
111    async fn query_row_async(&self, sql: &str) -> KnowledgeResult<RowData> {
112        PostgresProvider::query_row_async(self, sql).await
113    }
114
115    async fn query_named_fields_async(
116        &self,
117        sql: &str,
118        params: &[DataField],
119    ) -> KnowledgeResult<RowData> {
120        PostgresProvider::query_named_fields_async(self, sql, params).await
121    }
122}
123
124#[async_trait]
125impl ProviderExecutor for MySqlProvider {
126    fn query(&self, sql: &str) -> KnowledgeResult<Vec<RowData>> {
127        MySqlProvider::query(self, sql)
128    }
129
130    fn query_fields(&self, sql: &str, params: &[DataField]) -> KnowledgeResult<Vec<RowData>> {
131        MySqlProvider::query_fields(self, sql, params)
132    }
133
134    fn query_row(&self, sql: &str) -> KnowledgeResult<RowData> {
135        MySqlProvider::query_row(self, sql)
136    }
137
138    fn query_named_fields(&self, sql: &str, params: &[DataField]) -> KnowledgeResult<RowData> {
139        MySqlProvider::query_named_fields(self, sql, params)
140    }
141
142    async fn query_async(&self, sql: &str) -> KnowledgeResult<Vec<RowData>> {
143        MySqlProvider::query_async(self, sql).await
144    }
145
146    async fn query_fields_async(
147        &self,
148        sql: &str,
149        params: &[DataField],
150    ) -> KnowledgeResult<Vec<RowData>> {
151        MySqlProvider::query_fields_async(self, sql, params).await
152    }
153
154    async fn query_row_async(&self, sql: &str) -> KnowledgeResult<RowData> {
155        MySqlProvider::query_row_async(self, sql).await
156    }
157
158    async fn query_named_fields_async(
159        &self,
160        sql: &str,
161        params: &[DataField],
162    ) -> KnowledgeResult<RowData> {
163        MySqlProvider::query_named_fields_async(self, sql, params).await
164    }
165}
166
167fn install_provider<F>(
168    kind: ProviderKind,
169    datasource_id: DatasourceId,
170    build: F,
171) -> KnowledgeResult<()>
172where
173    F: FnOnce(Generation) -> KnowledgeResult<Arc<dyn ProviderExecutor>>,
174{
175    runtime().install_provider(kind, datasource_id, build)?;
176    Ok(())
177}
178
179fn datasource_id_for(kind: ProviderKind, seed: &str) -> DatasourceId {
180    DatasourceId::from_seed(kind, seed)
181}
182
183pub fn init_thread_cloned_from_authority(authority_uri: &str) -> KnowledgeResult<()> {
184    let datasource_id = datasource_id_for(ProviderKind::SqliteAuthority, authority_uri);
185    install_provider(ProviderKind::SqliteAuthority, datasource_id, |generation| {
186        Ok(Arc::new(ThreadClonedMDB::from_authority_with_scope(
187            authority_uri,
188            datasource_id_for(ProviderKind::SqliteAuthority, authority_uri),
189            generation.0,
190        )))
191    })
192}
193
194pub fn init_mem_provider(memdb: MemDB) -> KnowledgeResult<()> {
195    install_provider(
196        ProviderKind::SqliteAuthority,
197        datasource_id_for(ProviderKind::SqliteAuthority, "memdb"),
198        |generation| {
199            Ok(Arc::new(MemProvider {
200                db: memdb,
201                metadata_scope: MetadataCacheScope {
202                    datasource_id: datasource_id_for(ProviderKind::SqliteAuthority, "memdb"),
203                    generation,
204                },
205            }))
206        },
207    )
208}
209
210pub fn init_postgres_provider(connection_uri: &str, pool_size: Option<u32>) -> KnowledgeResult<()> {
211    init_postgres_provider_with_config(
212        PostgresProviderConfig::new(connection_uri).with_pool_size(pool_size),
213    )
214}
215
216pub fn init_postgres_provider_with_config(config: PostgresProviderConfig) -> KnowledgeResult<()> {
217    let connection_uri = config.connection_uri().to_string();
218    install_postgres_provider(DEFAULT_SQLDB_NAME, config, &connection_uri, true)
219}
220
221/// 以命名 provider 安装 PostgreSQL 连接(不替换默认 provider)。
222pub fn init_postgres_provider_named(
223    name: &str,
224    config: PostgresProviderConfig,
225) -> KnowledgeResult<()> {
226    install_postgres_provider(name, config, name, false)
227}
228
229fn install_postgres_provider(
230    name: &str,
231    config: PostgresProviderConfig,
232    datasource_seed: &str,
233    is_default: bool,
234) -> KnowledgeResult<()> {
235    let datasource_id = datasource_id_for(ProviderKind::Postgres, datasource_seed);
236    runtime().install_provider_named(
237        name,
238        ProviderKind::Postgres,
239        datasource_id.clone(),
240        |generation| {
241            let provider = PostgresProvider::connect(
242                name,
243                &config,
244                MetadataCacheScope {
245                    datasource_id: datasource_id.clone(),
246                    generation,
247                },
248            )?;
249            Ok(Arc::new(provider))
250        },
251        is_default,
252    )?;
253    Ok(())
254}
255
256pub fn init_mysql_provider(connection_uri: &str, pool_size: Option<u32>) -> KnowledgeResult<()> {
257    init_mysql_provider_with_config(
258        MySqlProviderConfig::new(connection_uri).with_pool_size(pool_size),
259    )
260}
261
262pub fn init_mysql_provider_with_config(config: MySqlProviderConfig) -> KnowledgeResult<()> {
263    let connection_uri = config.connection_uri().to_string();
264    install_mysql_provider(DEFAULT_SQLDB_NAME, config, &connection_uri, true)
265}
266
267/// 以命名 provider 安装 MySQL 连接(不替换默认 provider)。
268pub fn init_mysql_provider_named(name: &str, config: MySqlProviderConfig) -> KnowledgeResult<()> {
269    install_mysql_provider(name, config, name, false)
270}
271
272fn install_mysql_provider(
273    name: &str,
274    config: MySqlProviderConfig,
275    datasource_seed: &str,
276    is_default: bool,
277) -> KnowledgeResult<()> {
278    let datasource_id = datasource_id_for(ProviderKind::Mysql, datasource_seed);
279    runtime().install_provider_named(
280        name,
281        ProviderKind::Mysql,
282        datasource_id.clone(),
283        |generation| {
284            let provider = MySqlProvider::connect(
285                &config,
286                MetadataCacheScope {
287                    datasource_id: datasource_id.clone(),
288                    generation,
289                },
290            )?;
291            Ok(Arc::new(provider))
292        },
293        is_default,
294    )?;
295    Ok(())
296}
297
298fn build_postgres_config(spec: &SqlProviderSpec) -> PostgresProviderConfig {
299    PostgresProviderConfig::new(spec.connection_uri.clone())
300        .with_pool_size(spec.pool_size)
301        .with_min_connections(spec.min_connections)
302        .with_acquire_timeout_ms(spec.acquire_timeout_ms)
303        .with_idle_timeout_ms(spec.idle_timeout_ms)
304        .with_max_lifetime_ms(spec.max_lifetime_ms)
305        .with_session(spec.postgres_session.as_ref())
306}
307
308fn build_mysql_config(spec: &SqlProviderSpec) -> MySqlProviderConfig {
309    MySqlProviderConfig::new(spec.connection_uri.clone())
310        .with_pool_size(spec.pool_size)
311        .with_min_connections(spec.min_connections)
312        .with_acquire_timeout_ms(spec.acquire_timeout_ms)
313        .with_idle_timeout_ms(spec.idle_timeout_ms)
314        .with_max_lifetime_ms(spec.max_lifetime_ms)
315}
316
317// ---------------------------------------------------------------------------
318// Redis provider API
319// ---------------------------------------------------------------------------
320
321/// Check whether an item exists in a Bloom filter.
322///
323/// Returns `true` if the item *may* exist, `false` if it definitely does not.
324/// Requires the RedisBloom module.
325pub fn redis_bf_exists(key: &str, item: &str) -> KnowledgeResult<bool> {
326    if let Some(generation) = current_generation() {
327        let ck = redis_cache_key(RedisCmdTag::BfExists, generation, key, &[item]);
328        if let Some(CachedRedisValue::Bool(v)) = runtime().redis_cache_get(&ck) {
329            return Ok(v);
330        }
331        let result = crate::redis::bf_exists("knowdb", key, item)?;
332        runtime().redis_cache_put(ck, CachedRedisValue::Bool(result));
333        return Ok(result);
334    }
335    crate::redis::bf_exists("knowdb", key, item)
336}
337
338/// Get the value of a hash field.
339///
340/// Returns `None` when the key or field does not exist.
341pub fn redis_hget(key: &str, field: &str) -> KnowledgeResult<Option<String>> {
342    if let Some(generation) = current_generation() {
343        let ck = redis_cache_key(RedisCmdTag::HGet, generation, key, &[field]);
344        if let Some(CachedRedisValue::OptString(ref v)) = runtime().redis_cache_get(&ck) {
345            return Ok(v.clone());
346        }
347        let result = crate::redis::hget("knowdb", key, field)?;
348        runtime().redis_cache_put(ck, CachedRedisValue::OptString(result.clone()));
349        return Ok(result);
350    }
351    crate::redis::hget("knowdb", key, field)
352}
353
354/// Get the value of a key.
355///
356/// Returns `None` when the key does not exist.
357pub fn redis_get(key: &str) -> KnowledgeResult<Option<String>> {
358    if let Some(generation) = current_generation() {
359        let ck = redis_cache_key(RedisCmdTag::Get, generation, key, &[]);
360        if let Some(CachedRedisValue::OptString(ref v)) = runtime().redis_cache_get(&ck) {
361            return Ok(v.clone());
362        }
363        let result = crate::redis::get("knowdb", key)?;
364        runtime().redis_cache_put(ck, CachedRedisValue::OptString(result.clone()));
365        return Ok(result);
366    }
367    crate::redis::get("knowdb", key)
368}
369
370/// Check whether a member exists in a set.
371pub fn redis_set_exists(key: &str, member: &str) -> KnowledgeResult<bool> {
372    if let Some(generation) = current_generation() {
373        let ck = redis_cache_key(RedisCmdTag::SetExists, generation, key, &[member]);
374        if let Some(CachedRedisValue::Bool(v)) = runtime().redis_cache_get(&ck) {
375            return Ok(v);
376        }
377        let result = crate::redis::set_exists("knowdb", key, member)?;
378        runtime().redis_cache_put(ck, CachedRedisValue::Bool(result));
379        return Ok(result);
380    }
381    crate::redis::set_exists("knowdb", key, member)
382}
383
384/// Add items to a Bloom filter.
385///
386/// Returns a `Vec<bool>` indicating whether each item was *new* to the filter.
387/// Requires the RedisBloom module.
388pub fn redis_bf_add(key: &str, items: &[&str]) -> KnowledgeResult<Vec<bool>> {
389    let owned: Vec<String> = items.iter().map(|s| s.to_string()).collect();
390    crate::redis::bf_madd("knowdb", key, &owned)
391}
392
393/// Create a new Bloom filter.
394///
395/// `error_rate`: desired false positive rate (e.g. `0.01` for 1%).
396/// `capacity`: expected number of items to add.
397/// Requires the RedisBloom module.
398pub fn redis_bf_create(key: &str, error_rate: f64, capacity: i64) -> KnowledgeResult<()> {
399    crate::redis::bf_reserve("knowdb", key, error_rate, capacity)
400}
401
402// ---------------------------------------------------------------------------
403// External call API — named queries from [fun.<name>]
404// ---------------------------------------------------------------------------
405
406/// Register [fun] definitions from knowdb.toml. Called once during init.
407pub(crate) fn register_fun_map(map: HashMap<String, crate::loader::FunSpec>) {
408    crate::fun::register_fun_map(map);
409}
410
411/// Execute a [fun.<name>] named query where returns = "bool".
412pub fn external_exists(service: &str, arg: &str) -> KnowledgeResult<bool> {
413    let spec = crate::fun::resolve_spec(service, true)?;
414    let redis_key = spec.key.as_deref().unwrap_or(arg);
415    if spec.enabled()
416        && let Some(generation) = current_generation()
417    {
418        let ck = redis_cache_key(RedisCmdTag::BfExists, generation, redis_key, &[arg]);
419        if let Some(CachedRedisValue::Bool(v)) = runtime().redis_cache_get(&ck) {
420            return Ok(v);
421        }
422        let result = crate::fun::external_exists(service, arg)?;
423        if let Some(ttl) = spec.ttl_ms {
424            runtime().redis_cache_put_with_ttl(ck, CachedRedisValue::Bool(result), ttl);
425        } else {
426            runtime().redis_cache_put(ck, CachedRedisValue::Bool(result));
427        }
428        return Ok(result);
429    }
430    crate::fun::external_exists(service, arg)
431}
432
433/// Execute a [fun.<name>] named query where returns = "value".
434pub fn external_value(service: &str, arg: &str) -> KnowledgeResult<Option<String>> {
435    let spec = crate::fun::resolve_spec(service, false)?;
436    let redis_key = spec.key.as_deref().unwrap_or(arg);
437    if spec.enabled()
438        && let Some(generation) = current_generation()
439    {
440        let ck = redis_cache_key(RedisCmdTag::Get, generation, redis_key, &[arg]);
441        if let Some(CachedRedisValue::OptString(ref v)) = runtime().redis_cache_get(&ck) {
442            return Ok(v.clone());
443        }
444        let result = crate::fun::external_value(service, arg)?;
445        if let Some(ttl) = spec.ttl_ms {
446            runtime().redis_cache_put_with_ttl(
447                ck,
448                CachedRedisValue::OptString(result.clone()),
449                ttl,
450            );
451        } else {
452            runtime().redis_cache_put(ck, CachedRedisValue::OptString(result.clone()));
453        }
454        return Ok(result);
455    }
456    crate::fun::external_value(service, arg)
457}
458
459#[allow(dead_code)]
460pub(crate) fn redis_ping() -> KnowledgeResult<bool> {
461    crate::redis::ping_blocking("knowdb")
462}
463
464#[allow(dead_code)]
465pub(crate) fn redis_close() -> KnowledgeResult<()> {
466    crate::redis::close(Some("knowdb"))
467}
468
469fn redis_cache_key(cmd: RedisCmdTag, generation: u64, key: &str, args: &[&str]) -> RedisCacheKey {
470    use std::collections::hash_map::DefaultHasher;
471    use std::hash::{Hash, Hasher};
472    let mut hasher = DefaultHasher::new();
473    key.hash(&mut hasher);
474    let key_hash = hasher.finish();
475    let mut hasher = DefaultHasher::new();
476    for arg in args {
477        arg.hash(&mut hasher);
478    }
479    let args_hash = hasher.finish();
480    RedisCacheKey {
481        generation,
482        cmd_tag: cmd,
483        key_hash,
484        args_hash,
485    }
486}
487
488// ---------------------------------------------------------------------------
489
490pub fn current_generation() -> Option<u64> {
491    runtime()
492        .current_generation()
493        .map(|generation| generation.0)
494}
495
496pub fn runtime_snapshot() -> RuntimeSnapshot {
497    runtime().snapshot()
498}
499
500pub fn install_runtime_telemetry(
501    telemetry_impl: Arc<dyn KnowledgeTelemetry>,
502) -> Arc<dyn KnowledgeTelemetry> {
503    install_telemetry(telemetry_impl)
504}
505
506pub use crate::sql_route::route_provider_sql;
507
508/// 指定名称的 provider 是否已安装。
509pub fn provider_exists(name: &str) -> bool {
510    runtime().provider_exists(name)
511}
512
513/// 当前已安装的命名 provider 名称列表。
514pub fn provider_names() -> Vec<String> {
515    runtime().provider_names()
516}
517
518/// 对指定命名 provider 执行查询(全部行)。
519pub fn query_for(name: &str, sql: &str) -> KnowledgeResult<Vec<RowData>> {
520    runtime()
521        .execute_for(
522            name,
523            &QueryRequest::many(sql, Vec::new(), CachePolicy::Bypass),
524        )
525        .map(QueryResponse::into_rows)
526}
527
528/// 对指定命名 provider 异步执行查询(全部行)。
529pub async fn query_async_for(name: &str, sql: &str) -> KnowledgeResult<Vec<RowData>> {
530    runtime()
531        .execute_for_async(
532            name,
533            &QueryRequest::many(sql, Vec::new(), CachePolicy::Bypass),
534        )
535        .await
536        .map(QueryResponse::into_rows)
537}
538
539/// 对指定命名 provider 执行首行查询。
540pub fn query_fields_for(name: &str, sql: &str, params: &[DataField]) -> KnowledgeResult<RowData> {
541    runtime().execute_first_row_fields_for(name, sql, params, CachePolicy::Bypass)
542}
543
544/// 对指定命名 provider 异步执行首行查询。
545pub async fn query_fields_for_async(
546    name: &str,
547    sql: &str,
548    params: &[DataField],
549) -> KnowledgeResult<RowData> {
550    runtime()
551        .execute_first_row_fields_for_async(name, sql, params, CachePolicy::Bypass)
552        .await
553}
554
555pub fn query(sql: &str) -> KnowledgeResult<Vec<RowData>> {
556    runtime()
557        .execute(&QueryRequest::many(sql, Vec::new(), CachePolicy::Bypass))
558        .map(QueryResponse::into_rows)
559}
560
561pub async fn query_async(sql: &str) -> KnowledgeResult<Vec<RowData>> {
562    runtime()
563        .execute_async(&QueryRequest::many(sql, Vec::new(), CachePolicy::Bypass))
564        .await
565        .map(QueryResponse::into_rows)
566}
567
568pub fn query_row(sql: &str) -> KnowledgeResult<RowData> {
569    runtime()
570        .execute(&QueryRequest::first_row(
571            sql,
572            Vec::new(),
573            CachePolicy::Bypass,
574        ))
575        .map(QueryResponse::into_row)
576}
577
578pub async fn query_row_async(sql: &str) -> KnowledgeResult<RowData> {
579    runtime()
580        .execute_async(&QueryRequest::first_row(
581            sql,
582            Vec::new(),
583            CachePolicy::Bypass,
584        ))
585        .await
586        .map(QueryResponse::into_row)
587}
588
589pub fn query_fields(sql: &str, params: &[DataField]) -> KnowledgeResult<RowData> {
590    runtime().execute_first_row_fields(sql, params, CachePolicy::Bypass)
591}
592
593pub async fn query_fields_async(sql: &str, params: &[DataField]) -> KnowledgeResult<RowData> {
594    runtime()
595        .execute_first_row_fields_async(sql, params, CachePolicy::Bypass)
596        .await
597}
598
599pub fn query_named_fields(sql: &str, params: &[DataField]) -> KnowledgeResult<RowData> {
600    query_fields(sql, params)
601}
602
603pub async fn query_named_fields_async(sql: &str, params: &[DataField]) -> KnowledgeResult<RowData> {
604    query_fields_async(sql, params).await
605}
606
607pub fn query_named<'a>(
608    sql: &str,
609    params: &'a [(&'a str, &'a dyn ToSql)],
610) -> KnowledgeResult<RowData> {
611    let fields = named_params_to_fields(params)?;
612    query_fields(sql, &fields)
613}
614
615pub fn cache_query_fields<const N: usize>(
616    sql: &str,
617    c_params: &[DataField; N],
618    query_params: &[DataField],
619    cache: &mut impl CacheAble<DataField, RowData, N>,
620) -> RowData {
621    cache_query_fields_with_scope(sql, stable_hash(sql), c_params, query_params, cache)
622}
623
624pub fn cache_query_fields_with_scope<const N: usize>(
625    sql: &str,
626    local_cache_scope: u64,
627    c_params: &[DataField; N],
628    query_params: &[DataField],
629    cache: &mut impl CacheAble<DataField, RowData, N>,
630) -> RowData {
631    let generation = current_generation();
632    let provider_kind = runtime().current_provider_kind();
633    if let Some(hit) = local_cache_hit(
634        local_cache_scope,
635        c_params,
636        cache,
637        generation,
638        provider_kind,
639    ) {
640        return hit;
641    }
642    match runtime().execute_first_row_fields(sql, query_params, CachePolicy::UseGlobal) {
643        Ok(row) => {
644            cache.save_scoped(local_cache_scope, c_params, row.clone());
645            row
646        }
647        Err(err) => {
648            warn_kdb!("[kdb] query error: {}", err);
649            Vec::new()
650        }
651    }
652}
653
654/// 本地缓存路由派发:`provider` 为 `Some(name)` 时走命名 provider,`None` 时走默认 provider。
655pub fn cache_query_fields_route<const N: usize>(
656    provider: Option<&str>,
657    sql: &str,
658    local_cache_scope: u64,
659    c_params: &[DataField; N],
660    query_params: &[DataField],
661    cache: &mut impl CacheAble<DataField, RowData, N>,
662) -> RowData {
663    let Some(name) = provider else {
664        return cache_query_fields_with_scope(
665            sql,
666            local_cache_scope,
667            c_params,
668            query_params,
669            cache,
670        );
671    };
672    let handle = match runtime().provider_by_name(name) {
673        Ok(handle) => handle,
674        Err(err) => {
675            warn_kdb!("[kdb] named provider '{name}' unavailable: {}", err);
676            return Vec::new();
677        }
678    };
679    let generation = Some(handle.generation.0);
680    let provider_kind = Some(handle.kind.clone());
681    // provider 粒度 scope:与默认 provider 区分,使本地缓存代际按 provider 独立跟踪,
682    // 混用命名库与默认库时不互相重置。
683    let scope = local_cache_scope ^ stable_hash(name);
684    if let Some(hit) = local_cache_hit(scope, c_params, cache, generation, provider_kind) {
685        return hit;
686    }
687    match runtime().execute_first_row_fields_for(name, sql, query_params, CachePolicy::UseGlobal) {
688        Ok(row) => {
689            cache.save_scoped(scope, c_params, row.clone());
690            row
691        }
692        Err(err) => {
693            warn_kdb!("[kdb] query error: {}", err);
694            Vec::new()
695        }
696    }
697}
698
699/// 本地缓存命中检查(含 generation 准备)与 miss 记录。命中时返回 `Some(hit)`。
700fn local_cache_hit<const N: usize>(
701    local_cache_scope: u64,
702    c_params: &[DataField; N],
703    cache: &mut impl CacheAble<DataField, RowData, N>,
704    generation: Option<u64>,
705    provider_kind: Option<ProviderKind>,
706) -> Option<RowData> {
707    if let Some(generation) = generation {
708        cache.prepare_generation(local_cache_scope, generation);
709    }
710    if let Some(hit) = cache.fetch_scoped(local_cache_scope, c_params) {
711        runtime().record_local_cache_hit();
712        if telemetry_enabled() {
713            telemetry().on_cache(&CacheTelemetryEvent {
714                layer: CacheLayer::Local,
715                outcome: CacheOutcome::Hit,
716                provider_kind,
717            });
718        }
719        return Some(hit.clone());
720    }
721    runtime().record_local_cache_miss();
722    if telemetry_enabled() {
723        telemetry().on_cache(&CacheTelemetryEvent {
724            layer: CacheLayer::Local,
725            outcome: CacheOutcome::Miss,
726            provider_kind,
727        });
728    }
729    None
730}
731
732pub async fn cache_query_fields_async<const N: usize>(
733    sql: &str,
734    c_params: &[DataField; N],
735    query_params: &[DataField],
736    cache: &mut impl CacheAble<DataField, RowData, N>,
737) -> RowData {
738    cache_query_fields_async_with_scope(
739        sql,
740        stable_hash(sql),
741        c_params,
742        || query_params.to_vec(),
743        cache,
744    )
745    .await
746}
747
748pub async fn cache_query_fields_async_with<const N: usize, F>(
749    sql: &str,
750    c_params: &[DataField; N],
751    build_query_params: F,
752    cache: &mut impl CacheAble<DataField, RowData, N>,
753) -> RowData
754where
755    F: FnOnce() -> Vec<DataField>,
756{
757    cache_query_fields_async_with_scope(sql, stable_hash(sql), c_params, build_query_params, cache)
758        .await
759}
760
761pub async fn cache_query_fields_async_with_scope<const N: usize, F>(
762    sql: &str,
763    local_cache_scope: u64,
764    c_params: &[DataField; N],
765    build_query_params: F,
766    cache: &mut impl CacheAble<DataField, RowData, N>,
767) -> RowData
768where
769    F: FnOnce() -> Vec<DataField>,
770{
771    let generation = current_generation();
772    let provider_kind = runtime().current_provider_kind();
773    if let Some(hit) = local_cache_hit(
774        local_cache_scope,
775        c_params,
776        cache,
777        generation,
778        provider_kind,
779    ) {
780        return hit;
781    }
782    let query_params = build_query_params();
783    match runtime()
784        .execute_first_row_fields_async(sql, &query_params, CachePolicy::UseGlobal)
785        .await
786    {
787        Ok(row) => {
788            cache.save_scoped(local_cache_scope, c_params, row.clone());
789            row
790        }
791        Err(err) => {
792            warn_kdb!("[kdb] query error: {}", err);
793            Vec::new()
794        }
795    }
796}
797
798/// 异步本地缓存路由派发:`provider` 为 `Some(name)` 时走命名 provider,`None` 时走默认 provider。
799pub async fn cache_query_fields_route_async<const N: usize, F>(
800    provider: Option<&str>,
801    sql: &str,
802    local_cache_scope: u64,
803    c_params: &[DataField; N],
804    build_query_params: F,
805    cache: &mut impl CacheAble<DataField, RowData, N>,
806) -> RowData
807where
808    F: FnOnce() -> Vec<DataField>,
809{
810    let Some(name) = provider else {
811        return cache_query_fields_async_with_scope(
812            sql,
813            local_cache_scope,
814            c_params,
815            build_query_params,
816            cache,
817        )
818        .await;
819    };
820    let handle = match runtime().provider_by_name(name) {
821        Ok(handle) => handle,
822        Err(err) => {
823            warn_kdb!("[kdb] named provider '{name}' unavailable: {}", err);
824            return Vec::new();
825        }
826    };
827    let generation = Some(handle.generation.0);
828    let provider_kind = Some(handle.kind.clone());
829    // provider 粒度 scope:与默认 provider 区分,本地缓存代际按 provider 独立跟踪。
830    let scope = local_cache_scope ^ stable_hash(name);
831    if let Some(hit) = local_cache_hit(scope, c_params, cache, generation, provider_kind) {
832        return hit;
833    }
834    let query_params = build_query_params();
835    match runtime()
836        .execute_first_row_fields_for_async(name, sql, &query_params, CachePolicy::UseGlobal)
837        .await
838    {
839        Ok(row) => {
840            cache.save_scoped(scope, c_params, row.clone());
841            row
842        }
843        Err(err) => {
844            warn_kdb!("[kdb] query error: {}", err);
845            Vec::new()
846        }
847    }
848}
849
850pub fn cache_query<const N: usize>(
851    sql: &str,
852    c_params: &[DataField; N],
853    named_params: &[(&str, &dyn ToSql)],
854    cache: &mut impl CacheAble<DataField, RowData, N>,
855) -> RowData {
856    let query_fields = match named_params_to_fields(named_params) {
857        Ok(fields) => fields,
858        Err(err) => {
859            warn_kdb!("[kdb] query param conversion error: {}", err);
860            return Vec::new();
861        }
862    };
863
864    cache_query_fields(sql, c_params, &query_fields, cache)
865}
866
867pub async fn cache_query_async<const N: usize>(
868    sql: &str,
869    c_params: &[DataField; N],
870    named_params: &[(&str, &dyn ToSql)],
871    cache: &mut impl CacheAble<DataField, RowData, N>,
872) -> RowData {
873    let query_fields = match named_params_to_fields(named_params) {
874        Ok(fields) => fields,
875        Err(err) => {
876            warn_kdb!("[kdb] query param conversion error: {}", err);
877            return Vec::new();
878        }
879    };
880
881    cache_query_fields_async(sql, c_params, &query_fields, cache).await
882}
883
884fn ensure_wal(authority_uri: &str) -> KnowledgeResult<()> {
885    if let Ok(conn) = Connection::open_with_flags(
886        authority_uri,
887        OpenFlags::SQLITE_OPEN_READ_WRITE
888            | OpenFlags::SQLITE_OPEN_CREATE
889            | OpenFlags::SQLITE_OPEN_URI,
890    ) {
891        let _ = conn.execute_batch(
892            "PRAGMA journal_mode=WAL;\nPRAGMA synchronous=NORMAL;\nPRAGMA temp_store=MEMORY;",
893        );
894    }
895    Ok(())
896}
897
898pub fn init_wal_pool_from_authority(authority_uri: &str, pool_size: u32) -> KnowledgeResult<()> {
899    ensure_wal(authority_uri)?;
900    let flags = OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_URI;
901    let mem = MemDB::new_file(authority_uri, pool_size, flags)?;
902    init_mem_provider(mem)
903}
904
905pub fn init_thread_cloned_from_knowdb(
906    root: &Path,
907    knowdb_conf: &Path,
908    authority_uri: &str,
909    dict: &orion_variate::EnvDict,
910) -> KnowledgeResult<()> {
911    let (conf, conf_abs, _) = parse_knowdb_conf(root, knowdb_conf, dict)?;
912    if let Some(provider_cfg) = conf.provider() {
913        // New-style [provider.sqldb] / [[provider.sqldb]]
914        if let Some(specs) = provider_cfg.sqldb
915            && !specs.is_empty()
916        {
917            let expected = SqlProviderSpec::validate_specs(&specs)?;
918            // 默认 provider:生效名为 `default`(历史单库)者优先,否则取第一个。
919            let default_name = if expected.contains(DEFAULT_SQLDB_NAME) {
920                DEFAULT_SQLDB_NAME.to_string()
921            } else {
922                specs[0].effective_name().to_string()
923            };
924            // 外部 SQL provider 只读(仅 SELECT),初始化中途失败时允许部分安装:
925            // 已连通的库照常可用、连不上的库不可查询,错误由调用方记录后继续。
926            // 因此不做"先全部建池再统一提交"的事务化处理。
927            for spec in &specs {
928                let name = spec.effective_name().to_string();
929                let is_default = name == default_name;
930                // 无显式 name 的历史单库保留 connection_uri 作为 datasource 种子
931                let seed = match &spec.name {
932                    Some(_) => name.clone(),
933                    None => spec.connection_uri.clone(),
934                };
935                match spec.kind {
936                    SqlProviderKind::Postgres => {
937                        info_ctrl!(
938                            "init postgres knowdb provider({name}:{}) ",
939                            conf_abs.display(),
940                        );
941                        install_postgres_provider(
942                            &name,
943                            build_postgres_config(spec),
944                            &seed,
945                            is_default,
946                        )?;
947                    }
948                    SqlProviderKind::Mysql => {
949                        info_ctrl!("init mysql knowdb provider({name}:{}) ", conf_abs.display(),);
950                        install_mysql_provider(&name, build_mysql_config(spec), &seed, is_default)?;
951                    }
952                }
953            }
954            runtime().configure_result_cache(
955                conf.cache.enabled,
956                conf.cache.capacity,
957                Duration::from_millis(conf.cache.ttl_ms.max(1)),
958            );
959            runtime().prune_named_providers_except(&expected);
960            return Ok(());
961        }
962        // New-style [provider.redis]
963        if let Some(redis_cfg) = provider_cfg.redis {
964            info_ctrl!("init redis knowdb provider({}) ", conf_abs.display(),);
965            crate::redis::init_with_opts(
966                "knowdb",
967                &redis_cfg.connection_uri,
968                redis_cfg.pool_size,
969                redis_cfg.command_timeout_ms,
970            )?;
971            runtime().configure_redis_cache(conf.cache.enabled, conf.cache.capacity);
972            register_fun_map(conf.fun.clone());
973            // redis-only 配置不注册任何 sqldb 命名 provider,清空过期命名注册
974            runtime().prune_named_providers_except(&HashSet::new());
975            return Ok(());
976        }
977    }
978
979    crate::loader::build_authority_from_knowdb(root, knowdb_conf, authority_uri, dict)?;
980    let ro_uri = if let Some(rest) = authority_uri.strip_prefix("file:") {
981        let path_part = rest.split('?').next().unwrap_or(rest);
982        format!("file:{}?mode=ro&uri=true", path_part)
983    } else {
984        authority_uri.to_string()
985    };
986
987    info_ctrl!("init authority knowdb success({}) ", knowdb_conf.display(),);
988    init_thread_cloned_from_authority(&ro_uri)?;
989    runtime().configure_result_cache(
990        conf.cache.enabled,
991        conf.cache.capacity,
992        Duration::from_millis(conf.cache.ttl_ms.max(1)),
993    );
994    // 本地 authority 模式:仅保留默认 provider,清理残留的命名 provider
995    runtime().prune_named_providers_except(&HashSet::from([DEFAULT_SQLDB_NAME.to_string()]));
996    Ok(())
997}
998
999fn stable_hash(value: &str) -> u64 {
1000    let mut hasher = DefaultHasher::new();
1001    value.hash(&mut hasher);
1002    hasher.finish()
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007    use super::*;
1008    use crate::cache::FieldQueryCache;
1009    use crate::error::KnowReason;
1010    use crate::mem::memdb::MemDB;
1011    use crate::mem::query_util::{COLNAME_CACHE, metadata_cache_key_for_scope};
1012    use crate::runtime::fields_to_params;
1013    use crate::telemetry::{
1014        CacheLayer, CacheTelemetryEvent, KnowledgeTelemetry, QueryTelemetryEvent,
1015        ReloadTelemetryEvent, reset_telemetry,
1016    };
1017    use orion_error::conversion::ToStructError;
1018    use orion_variate::EnvDict;
1019    use std::fs;
1020    use std::hint::black_box;
1021    use std::path::PathBuf;
1022    use std::sync::atomic::{AtomicU64, Ordering};
1023    use std::time::{Duration, Instant};
1024
1025    #[derive(Default)]
1026    struct TestTelemetry {
1027        reload_success: AtomicU64,
1028        reload_failure: AtomicU64,
1029        local_hits: AtomicU64,
1030        local_misses: AtomicU64,
1031        result_hits: AtomicU64,
1032        result_misses: AtomicU64,
1033        metadata_hits: AtomicU64,
1034        metadata_misses: AtomicU64,
1035        query_success: AtomicU64,
1036        query_failure: AtomicU64,
1037    }
1038
1039    impl KnowledgeTelemetry for TestTelemetry {
1040        fn on_cache(&self, event: &CacheTelemetryEvent) {
1041            let counter = match (event.layer, event.outcome) {
1042                (CacheLayer::Local, CacheOutcome::Hit) => &self.local_hits,
1043                (CacheLayer::Local, CacheOutcome::Miss) => &self.local_misses,
1044                (CacheLayer::Result, CacheOutcome::Hit) => &self.result_hits,
1045                (CacheLayer::Result, CacheOutcome::Miss) => &self.result_misses,
1046                (CacheLayer::Metadata, CacheOutcome::Hit) => &self.metadata_hits,
1047                (CacheLayer::Metadata, CacheOutcome::Miss) => &self.metadata_misses,
1048            };
1049            counter.fetch_add(1, Ordering::Relaxed);
1050        }
1051
1052        fn on_reload(&self, event: &ReloadTelemetryEvent) {
1053            let counter = match event.outcome {
1054                crate::telemetry::ReloadOutcome::Success => &self.reload_success,
1055                crate::telemetry::ReloadOutcome::Failure => &self.reload_failure,
1056            };
1057            counter.fetch_add(1, Ordering::Relaxed);
1058        }
1059
1060        fn on_query(&self, event: &QueryTelemetryEvent) {
1061            let counter = if event.success {
1062                &self.query_success
1063            } else {
1064                &self.query_failure
1065            };
1066            counter.fetch_add(1, Ordering::Relaxed);
1067        }
1068    }
1069
1070    fn perf_env_usize(key: &str, default: usize) -> usize {
1071        std::env::var(key)
1072            .ok()
1073            .and_then(|value| value.parse::<usize>().ok())
1074            .unwrap_or(default)
1075    }
1076
1077    fn seed_perf_provider(rows: usize) {
1078        let db = MemDB::instance();
1079        db.execute("CREATE TABLE perf_kv (id INTEGER PRIMARY KEY, value TEXT)")
1080            .expect("create perf_kv");
1081        db.execute("BEGIN IMMEDIATE").expect("begin perf_kv load");
1082        for id in 1..=rows {
1083            let sql = format!("INSERT INTO perf_kv (id, value) VALUES ({id}, 'value_{id}')");
1084            db.execute(sql.as_str()).expect("insert perf_kv row");
1085        }
1086        db.execute("COMMIT").expect("commit perf_kv load");
1087        init_mem_provider(db).expect("init perf provider");
1088    }
1089
1090    #[tokio::test(flavor = "current_thread")]
1091    async fn query_async_works_with_mem_provider() {
1092        let _guard = crate::runtime::runtime_test_guard().lock_async().await;
1093        let db = MemDB::instance();
1094        db.execute("CREATE TABLE async_kv (id INTEGER PRIMARY KEY, value TEXT)")
1095            .expect("create async_kv");
1096        db.execute("INSERT INTO async_kv (id, value) VALUES (1, 'hello')")
1097            .expect("insert async_kv row");
1098        init_mem_provider(db).expect("init mem provider");
1099
1100        let row = query_fields_async(
1101            "SELECT value FROM async_kv WHERE id=:id",
1102            &[DataField::from_digit(":id", 1)],
1103        )
1104        .await
1105        .expect("query async row");
1106        assert_eq!(row.len(), 1);
1107        assert_eq!(row[0].to_string(), "chars(hello)");
1108    }
1109
1110    #[derive(Clone)]
1111    struct PerfQuery {
1112        cache_key: [DataField; 1],
1113        query_params: [DataField; 1],
1114        bypass_req: QueryRequest,
1115        global_req: QueryRequest,
1116    }
1117
1118    fn build_perf_workload(ops: usize, hotset: usize) -> Vec<PerfQuery> {
1119        (0..ops)
1120            .map(|idx| {
1121                let id = ((idx * 17) % hotset + 1) as i64;
1122                let cache_key = [DataField::from_digit("id", id)];
1123                let query_params = [DataField::from_digit(":id", id)];
1124                let bypass_req = QueryRequest::first_row(
1125                    "SELECT value FROM perf_kv WHERE id=:id",
1126                    fields_to_params(&query_params),
1127                    CachePolicy::Bypass,
1128                );
1129                let global_req = QueryRequest::first_row(
1130                    "SELECT value FROM perf_kv WHERE id=:id",
1131                    fields_to_params(&query_params),
1132                    CachePolicy::UseGlobal,
1133                );
1134                PerfQuery {
1135                    cache_key,
1136                    query_params,
1137                    bypass_req,
1138                    global_req,
1139                }
1140            })
1141            .collect()
1142    }
1143
1144    #[derive(Debug, Clone, Copy)]
1145    struct PerfCounters {
1146        result_hits: u64,
1147        result_misses: u64,
1148        local_hits: u64,
1149        local_misses: u64,
1150        metadata_hits: u64,
1151        metadata_misses: u64,
1152    }
1153
1154    #[derive(Debug, Clone)]
1155    struct PerfResult {
1156        name: &'static str,
1157        elapsed: Duration,
1158        ops: usize,
1159        counters: PerfCounters,
1160    }
1161
1162    impl PerfResult {
1163        fn qps(&self) -> f64 {
1164            let secs = self.elapsed.as_secs_f64();
1165            if secs == 0.0 {
1166                self.ops as f64
1167            } else {
1168                self.ops as f64 / secs
1169            }
1170        }
1171    }
1172
1173    fn snapshot_delta(before: &RuntimeSnapshot, after: &RuntimeSnapshot) -> PerfCounters {
1174        PerfCounters {
1175            result_hits: after
1176                .result_cache_hits
1177                .saturating_sub(before.result_cache_hits),
1178            result_misses: after
1179                .result_cache_misses
1180                .saturating_sub(before.result_cache_misses),
1181            local_hits: after
1182                .local_cache_hits
1183                .saturating_sub(before.local_cache_hits),
1184            local_misses: after
1185                .local_cache_misses
1186                .saturating_sub(before.local_cache_misses),
1187            metadata_hits: after
1188                .metadata_cache_hits
1189                .saturating_sub(before.metadata_cache_hits),
1190            metadata_misses: after
1191                .metadata_cache_misses
1192                .saturating_sub(before.metadata_cache_misses),
1193        }
1194    }
1195
1196    fn run_bypass_perf(workload: &[PerfQuery], rows: usize) -> PerfResult {
1197        seed_perf_provider(rows);
1198        let before = runtime_snapshot();
1199        let started = Instant::now();
1200        for item in workload {
1201            let row = runtime()
1202                .execute(&item.bypass_req)
1203                .expect("execute bypass request")
1204                .into_row();
1205            black_box(row);
1206        }
1207        let elapsed = started.elapsed();
1208        let after = runtime_snapshot();
1209        PerfResult {
1210            name: "bypass",
1211            elapsed,
1212            ops: workload.len(),
1213            counters: snapshot_delta(&before, &after),
1214        }
1215    }
1216
1217    fn run_global_cache_perf(workload: &[PerfQuery], rows: usize) -> PerfResult {
1218        seed_perf_provider(rows);
1219        let before = runtime_snapshot();
1220        let started = Instant::now();
1221        for item in workload {
1222            let row = runtime()
1223                .execute(&item.global_req)
1224                .expect("execute global-cache request")
1225                .into_row();
1226            black_box(row);
1227        }
1228        let elapsed = started.elapsed();
1229        let after = runtime_snapshot();
1230        PerfResult {
1231            name: "global_cache",
1232            elapsed,
1233            ops: workload.len(),
1234            counters: snapshot_delta(&before, &after),
1235        }
1236    }
1237
1238    fn run_local_cache_perf(workload: &[PerfQuery], rows: usize) -> PerfResult {
1239        seed_perf_provider(rows);
1240        let mut cache = FieldQueryCache::with_capacity(workload.len().max(1));
1241        let before = runtime_snapshot();
1242        let started = Instant::now();
1243        for item in workload {
1244            let row = cache_query_fields(
1245                "SELECT value FROM perf_kv WHERE id=:id",
1246                &item.cache_key,
1247                &item.query_params,
1248                &mut cache,
1249            );
1250            black_box(row);
1251        }
1252        let elapsed = started.elapsed();
1253        let after = runtime_snapshot();
1254        PerfResult {
1255            name: "local_cache",
1256            elapsed,
1257            ops: workload.len(),
1258            counters: snapshot_delta(&before, &after),
1259        }
1260    }
1261
1262    fn print_perf_result(result: &PerfResult) {
1263        eprintln!(
1264            "[wp-knowledge][cache-perf] scenario={} elapsed_ms={} qps={:.0} result_hit={} result_miss={} local_hit={} local_miss={} metadata_hit={} metadata_miss={}",
1265            result.name,
1266            result.elapsed.as_millis(),
1267            result.qps(),
1268            result.counters.result_hits,
1269            result.counters.result_misses,
1270            result.counters.local_hits,
1271            result.counters.local_misses,
1272            result.counters.metadata_hits,
1273            result.counters.metadata_misses,
1274        );
1275    }
1276
1277    fn uniq_cache_cfg_tmp_dir() -> PathBuf {
1278        use rand::{Rng, rng};
1279        let rnd: u64 = rng().next_u64();
1280        std::env::temp_dir().join(format!("wpk_cache_cfg_{}", rnd))
1281    }
1282
1283    fn write_minimal_knowdb_with_cache(
1284        root: &Path,
1285        enabled: bool,
1286        capacity: usize,
1287        ttl_ms: u64,
1288    ) -> std::path::PathBuf {
1289        let models = root.join("models").join("knowledge");
1290        let example_dir = models.join("example");
1291        fs::create_dir_all(&example_dir).expect("create knowdb models/example");
1292        fs::write(
1293            models.join("knowdb.toml"),
1294            format!(
1295                r#"
1296version = 2
1297base_dir = "."
1298
1299[cache]
1300enabled = {enabled}
1301capacity = {capacity}
1302ttl_ms = {ttl_ms}
1303
1304[csv]
1305has_header = false
1306
1307[[tables]]
1308name = "example"
1309columns.by_index = [0,1]
1310"#
1311            ),
1312        )
1313        .expect("write knowdb.toml");
1314        fs::write(
1315            example_dir.join("create.sql"),
1316            r#"
1317CREATE TABLE IF NOT EXISTS {table} (
1318  id INTEGER PRIMARY KEY,
1319  value TEXT NOT NULL
1320);
1321"#,
1322        )
1323        .expect("write create.sql");
1324        fs::write(
1325            example_dir.join("insert.sql"),
1326            "INSERT INTO {table} (id, value) VALUES (?1, ?2);\n",
1327        )
1328        .expect("write insert.sql");
1329        fs::write(example_dir.join("data.csv"), "1,alpha\n").expect("write data.csv");
1330        models.join("knowdb.toml")
1331    }
1332
1333    fn write_provider_only_knowdb_with_cache(
1334        root: &Path,
1335        provider_kind: &str,
1336        connection_uri: &str,
1337        enabled: bool,
1338        capacity: usize,
1339        ttl_ms: u64,
1340    ) -> std::path::PathBuf {
1341        let models = root.join("models").join("knowledge");
1342        fs::create_dir_all(&models).expect("create knowdb models");
1343        fs::write(
1344            models.join("knowdb.toml"),
1345            format!(
1346                r#"
1347version = 2
1348base_dir = "."
1349
1350[cache]
1351enabled = {enabled}
1352capacity = {capacity}
1353ttl_ms = {ttl_ms}
1354
1355[provider.sqldb]
1356kind = "{provider_kind}"
1357connection_uri = "{connection_uri}"
1358"#
1359            ),
1360        )
1361        .expect("write provider knowdb.toml");
1362        models.join("knowdb.toml")
1363    }
1364
1365    fn restore_default_result_cache_config() {
1366        runtime().configure_result_cache(true, 1024, Duration::from_millis(30_000));
1367    }
1368
1369    #[test]
1370    fn provider_can_be_replaced() {
1371        let _guard = crate::runtime::runtime_test_guard()
1372            .lock()
1373            .expect("provider test guard");
1374        let db1 = MemDB::instance();
1375        db1.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1376            .expect("create table in db1");
1377        db1.execute("INSERT INTO t (id, value) VALUES (1, 'first')")
1378            .expect("seed db1");
1379        init_mem_provider(db1).expect("init provider db1");
1380        let row = query_row("SELECT value FROM t WHERE id = 1").expect("query db1");
1381        assert_eq!(row[0].to_string(), "chars(first)");
1382
1383        let db2 = MemDB::instance();
1384        db2.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1385            .expect("create table in db2");
1386        db2.execute("INSERT INTO t (id, value) VALUES (1, 'second')")
1387            .expect("seed db2");
1388        init_mem_provider(db2).expect("replace provider with db2");
1389        let row = query_row("SELECT value FROM t WHERE id = 1").expect("query db2");
1390        assert_eq!(row[0].to_string(), "chars(second)");
1391    }
1392
1393    #[test]
1394    fn sqlite_metadata_cache_uses_provider_scope_after_reload() {
1395        let _guard = crate::runtime::runtime_test_guard()
1396            .lock()
1397            .expect("provider test guard");
1398        COLNAME_CACHE.write().expect("metadata cache lock").clear();
1399
1400        let db = MemDB::instance();
1401        db.execute("CREATE TABLE cache_scope_t (id INTEGER PRIMARY KEY, value TEXT)")
1402            .expect("create table");
1403        db.execute("INSERT INTO cache_scope_t (id, value) VALUES (1, 'scope-old')")
1404            .expect("seed table");
1405
1406        let old_scope = MetadataCacheScope {
1407            datasource_id: DatasourceId("sqlite:old".to_string()),
1408            generation: Generation(1),
1409        };
1410        let new_scope = MetadataCacheScope {
1411            datasource_id: DatasourceId("sqlite:new".to_string()),
1412            generation: Generation(2),
1413        };
1414        let old_provider = MemProvider {
1415            db: db.clone(),
1416            metadata_scope: old_scope.clone(),
1417        };
1418
1419        install_provider(
1420            ProviderKind::SqliteAuthority,
1421            new_scope.datasource_id.clone(),
1422            |_generation| {
1423                Ok(Arc::new(MemProvider {
1424                    db: db.clone(),
1425                    metadata_scope: new_scope.clone(),
1426                }))
1427            },
1428        )
1429        .expect("install new provider");
1430
1431        let row = old_provider
1432            .query_row("SELECT value FROM cache_scope_t WHERE id = 1")
1433            .expect("old provider query");
1434        assert_eq!(row[0].to_string(), "chars(scope-old)");
1435
1436        let cache = COLNAME_CACHE.read().expect("metadata cache lock");
1437        assert!(cache.contains(&metadata_cache_key_for_scope(
1438            &old_scope,
1439            "SELECT value FROM cache_scope_t WHERE id = 1",
1440        )));
1441        assert!(!cache.contains(&metadata_cache_key_for_scope(
1442            &new_scope,
1443            "SELECT value FROM cache_scope_t WHERE id = 1",
1444        )));
1445    }
1446
1447    #[tokio::test(flavor = "current_thread")]
1448    async fn async_query_uses_runtime_bridge() {
1449        let _guard = crate::runtime::runtime_test_guard().lock_async().await;
1450        let db = MemDB::instance();
1451        db.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1452            .expect("create table");
1453        db.execute("INSERT INTO t (id, value) VALUES (1, 'async-first')")
1454            .expect("seed table");
1455        init_mem_provider(db).expect("init provider");
1456
1457        let row = query_row_async("SELECT value FROM t WHERE id = 1")
1458            .await
1459            .expect("async query row");
1460        assert_eq!(row[0].to_string(), "chars(async-first)");
1461    }
1462
1463    #[tokio::test(flavor = "current_thread")]
1464    async fn async_cache_query_fields_hits_local_cache() {
1465        let _guard = crate::runtime::runtime_test_guard().lock_async().await;
1466        let db = MemDB::instance();
1467        db.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1468            .expect("create table");
1469        db.execute("INSERT INTO t (id, value) VALUES (1, 'async-cache')")
1470            .expect("seed table");
1471        init_mem_provider(db).expect("init provider");
1472
1473        let key = [DataField::from_digit("id", 1)];
1474        let params = [DataField::from_digit(":id", 1)];
1475        let mut cache = FieldQueryCache::default();
1476
1477        let first = cache_query_fields_async(
1478            "SELECT value FROM t WHERE id=:id",
1479            &key,
1480            &params,
1481            &mut cache,
1482        )
1483        .await;
1484        let second = cache_query_fields_async(
1485            "SELECT value FROM t WHERE id=:id",
1486            &key,
1487            &params,
1488            &mut cache,
1489        )
1490        .await;
1491
1492        assert_eq!(first[0].to_string(), "chars(async-cache)");
1493        assert_eq!(second[0].to_string(), "chars(async-cache)");
1494    }
1495
1496    #[test]
1497    fn local_cache_is_cleared_when_generation_changes() {
1498        let _guard = crate::runtime::runtime_test_guard()
1499            .lock()
1500            .expect("provider test guard");
1501        let db1 = MemDB::instance();
1502        db1.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1503            .expect("create table in db1");
1504        db1.execute("INSERT INTO t (id, value) VALUES (1, 'first')")
1505            .expect("seed db1");
1506        init_mem_provider(db1).expect("init provider db1");
1507
1508        let key = [DataField::from_digit("id", 1)];
1509        let params = [DataField::from_digit(":id", 1)];
1510        let mut cache = FieldQueryCache::default();
1511        let row = cache_query_fields(
1512            "SELECT value FROM t WHERE id=:id",
1513            &key,
1514            &params,
1515            &mut cache,
1516        );
1517        assert_eq!(row[0].to_string(), "chars(first)");
1518
1519        let db2 = MemDB::instance();
1520        db2.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1521            .expect("create table in db2");
1522        db2.execute("INSERT INTO t (id, value) VALUES (1, 'second')")
1523            .expect("seed db2");
1524        init_mem_provider(db2).expect("replace provider with db2");
1525
1526        let row = cache_query_fields(
1527            "SELECT value FROM t WHERE id=:id",
1528            &key,
1529            &params,
1530            &mut cache,
1531        );
1532        assert_eq!(row[0].to_string(), "chars(second)");
1533    }
1534
1535    #[test]
1536    fn local_cache_is_scoped_by_sql_text() {
1537        let _guard = crate::runtime::runtime_test_guard()
1538            .lock()
1539            .expect("provider test guard");
1540        let db = MemDB::instance();
1541        db.execute("CREATE TABLE t1 (id INTEGER PRIMARY KEY, value TEXT)")
1542            .expect("create t1");
1543        db.execute("CREATE TABLE t2 (id INTEGER PRIMARY KEY, value TEXT)")
1544            .expect("create t2");
1545        db.execute("INSERT INTO t1 (id, value) VALUES (1, 'first')")
1546            .expect("seed t1");
1547        db.execute("INSERT INTO t2 (id, value) VALUES (1, 'second')")
1548            .expect("seed t2");
1549        init_mem_provider(db).expect("init provider");
1550
1551        let key = [DataField::from_digit("id", 1)];
1552        let params = [DataField::from_digit(":id", 1)];
1553        let mut cache = FieldQueryCache::default();
1554
1555        let row = cache_query_fields(
1556            "SELECT value FROM t1 WHERE id=:id",
1557            &key,
1558            &params,
1559            &mut cache,
1560        );
1561        assert_eq!(row[0].to_string(), "chars(first)");
1562
1563        let row = cache_query_fields(
1564            "SELECT value FROM t2 WHERE id=:id",
1565            &key,
1566            &params,
1567            &mut cache,
1568        );
1569        assert_eq!(row[0].to_string(), "chars(second)");
1570    }
1571
1572    #[test]
1573    fn runtime_snapshot_tracks_generation_and_cache_size() {
1574        let _guard = crate::runtime::runtime_test_guard()
1575            .lock()
1576            .expect("provider test guard");
1577        let db = MemDB::instance();
1578        db.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1579            .expect("create table");
1580        db.execute("INSERT INTO t (id, value) VALUES (1, 'first')")
1581            .expect("seed table");
1582        init_mem_provider(db).expect("init provider");
1583
1584        let mut cache = FieldQueryCache::default();
1585        let key = [DataField::from_digit("id", 1)];
1586        let params = [DataField::from_digit(":id", 1)];
1587        let row = cache_query_fields(
1588            "SELECT value FROM t WHERE id=:id",
1589            &key,
1590            &params,
1591            &mut cache,
1592        );
1593        assert_eq!(row[0].to_string(), "chars(first)");
1594
1595        let snapshot = runtime_snapshot();
1596        assert!(matches!(
1597            snapshot.provider_kind,
1598            Some(ProviderKind::SqliteAuthority)
1599        ));
1600        assert!(snapshot.generation.is_some());
1601        assert!(snapshot.result_cache_len >= 1);
1602        assert!(snapshot.result_cache_capacity >= snapshot.result_cache_len);
1603        assert!(snapshot.metadata_cache_capacity >= snapshot.metadata_cache_len);
1604        assert!(snapshot.reload_successes >= 1);
1605    }
1606
1607    #[test]
1608    fn metadata_cache_is_scoped_by_generation() {
1609        let _guard = crate::runtime::runtime_test_guard()
1610            .lock()
1611            .expect("provider test guard");
1612        let sql = "SELECT value FROM t WHERE id = 1";
1613        let before = runtime_snapshot().metadata_cache_len;
1614
1615        let db1 = MemDB::instance();
1616        db1.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1617            .expect("create table in db1");
1618        db1.execute("INSERT INTO t (id, value) VALUES (1, 'first')")
1619            .expect("seed db1");
1620        init_mem_provider(db1).expect("init provider db1");
1621        let row = query_row(sql).expect("query db1");
1622        assert_eq!(row[0].to_string(), "chars(first)");
1623        let after_first = runtime_snapshot().metadata_cache_len;
1624        assert!(
1625            after_first > before,
1626            "metadata cache did not record first generation entry: before={before} after_first={after_first}"
1627        );
1628
1629        let db2 = MemDB::instance();
1630        db2.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1631            .expect("create table in db2");
1632        db2.execute("INSERT INTO t (id, value) VALUES (1, 'second')")
1633            .expect("seed db2");
1634        init_mem_provider(db2).expect("replace provider with db2");
1635        let row = query_row(sql).expect("query db2");
1636        assert_eq!(row[0].to_string(), "chars(second)");
1637        let after_second = runtime_snapshot().metadata_cache_len;
1638        assert!(
1639            after_second > after_first,
1640            "metadata cache did not keep a distinct generation entry: after_first={after_first} after_second={after_second}"
1641        );
1642    }
1643
1644    #[test]
1645    fn failed_provider_reload_keeps_previous_provider() {
1646        let _guard = crate::runtime::runtime_test_guard()
1647            .lock()
1648            .expect("provider test guard");
1649        let db1 = MemDB::instance();
1650        db1.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1651            .expect("create table in db1");
1652        db1.execute("INSERT INTO t (id, value) VALUES (1, 'first')")
1653            .expect("seed db1");
1654        init_mem_provider(db1).expect("init provider db1");
1655        let before_generation = current_generation();
1656
1657        let reload_err = install_provider(
1658            ProviderKind::SqliteAuthority,
1659            datasource_id_for(ProviderKind::SqliteAuthority, "reload-failure"),
1660            |_generation| {
1661                Err(KnowReason::from_logic()
1662                    .to_err()
1663                    .with_detail("expected reload failure"))
1664            },
1665        );
1666        assert!(reload_err.is_err());
1667
1668        let row = query_row("SELECT value FROM t WHERE id = 1").expect("query previous provider");
1669        assert_eq!(row[0].to_string(), "chars(first)");
1670        assert_eq!(current_generation(), before_generation);
1671    }
1672
1673    #[test]
1674    fn runtime_snapshot_records_cache_counters() {
1675        let _guard = crate::runtime::runtime_test_guard()
1676            .lock()
1677            .expect("provider test guard");
1678        let db = MemDB::instance();
1679        db.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1680            .expect("create table");
1681        db.execute("INSERT INTO t (id, value) VALUES (1, 'first')")
1682            .expect("seed table");
1683        init_mem_provider(db).expect("init provider");
1684
1685        let before = runtime_snapshot();
1686        let mut cache = FieldQueryCache::default();
1687        let key = [DataField::from_digit("id", 1)];
1688        let params = [DataField::from_digit(":id", 1)];
1689        let row = cache_query_fields(
1690            "SELECT value FROM t WHERE id=:id",
1691            &key,
1692            &params,
1693            &mut cache,
1694        );
1695        assert_eq!(row[0].to_string(), "chars(first)");
1696        let row = cache_query_fields(
1697            "SELECT value FROM t WHERE id=:id",
1698            &key,
1699            &params,
1700            &mut cache,
1701        );
1702        assert_eq!(row[0].to_string(), "chars(first)");
1703
1704        let after = runtime_snapshot();
1705        assert!(after.local_cache_hits > before.local_cache_hits);
1706        assert!(after.local_cache_misses > before.local_cache_misses);
1707        assert!(after.result_cache_misses > before.result_cache_misses);
1708        assert!(after.metadata_cache_misses > before.metadata_cache_misses);
1709    }
1710
1711    #[test]
1712    fn telemetry_receives_reload_cache_and_query_events() {
1713        let _guard = crate::runtime::runtime_test_guard()
1714            .lock()
1715            .expect("provider test guard");
1716        let telemetry_impl = Arc::new(TestTelemetry::default());
1717        let previous = install_runtime_telemetry(telemetry_impl.clone());
1718
1719        let db = MemDB::instance();
1720        db.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1721            .expect("create table");
1722        db.execute("INSERT INTO t (id, value) VALUES (1, 'first')")
1723            .expect("seed table");
1724        init_mem_provider(db).expect("init provider");
1725
1726        let mut cache = FieldQueryCache::default();
1727        let key = [DataField::from_digit("id", 1)];
1728        let params = [DataField::from_digit(":id", 1)];
1729        let row = cache_query_fields(
1730            "SELECT value FROM t WHERE id=:id",
1731            &key,
1732            &params,
1733            &mut cache,
1734        );
1735        assert_eq!(row[0].to_string(), "chars(first)");
1736        let row = cache_query_fields(
1737            "SELECT value FROM t WHERE id=:id",
1738            &key,
1739            &params,
1740            &mut cache,
1741        );
1742        assert_eq!(row[0].to_string(), "chars(first)");
1743
1744        let reload_err = install_provider(
1745            ProviderKind::SqliteAuthority,
1746            datasource_id_for(ProviderKind::SqliteAuthority, "telemetry-failure"),
1747            |_generation| {
1748                Err(KnowReason::from_logic()
1749                    .to_err()
1750                    .with_detail("expected telemetry reload failure"))
1751            },
1752        );
1753        assert!(reload_err.is_err());
1754
1755        install_runtime_telemetry(previous);
1756        reset_telemetry();
1757
1758        assert!(telemetry_impl.reload_success.load(Ordering::Relaxed) >= 1);
1759        assert!(telemetry_impl.reload_failure.load(Ordering::Relaxed) >= 1);
1760        assert!(telemetry_impl.local_hits.load(Ordering::Relaxed) >= 1);
1761        assert!(telemetry_impl.local_misses.load(Ordering::Relaxed) >= 1);
1762        assert!(telemetry_impl.result_misses.load(Ordering::Relaxed) >= 1);
1763        assert!(telemetry_impl.metadata_misses.load(Ordering::Relaxed) >= 1);
1764        assert!(telemetry_impl.query_success.load(Ordering::Relaxed) >= 1);
1765    }
1766
1767    #[test]
1768    #[ignore = "manual perf comparison; run with cargo test cache_perf_reports_cache_vs_no_cache -- --ignored --nocapture"]
1769    fn cache_perf_reports_cache_vs_no_cache() {
1770        let _guard = crate::runtime::runtime_test_guard()
1771            .lock()
1772            .expect("provider test guard");
1773        let rows = perf_env_usize("WP_KDB_PERF_ROWS", 10_000).max(1);
1774        let ops = perf_env_usize("WP_KDB_PERF_OPS", 120_000).max(1);
1775        let hotset = perf_env_usize("WP_KDB_PERF_HOTSET", 128).clamp(1, rows);
1776        let workload = build_perf_workload(ops, hotset);
1777
1778        eprintln!(
1779            "[wp-knowledge][cache-perf] rows={} ops={} hotset={} sql=SELECT value FROM perf_kv WHERE id=:id",
1780            rows, ops, hotset
1781        );
1782
1783        let bypass = run_bypass_perf(&workload, rows);
1784        let global = run_global_cache_perf(&workload, rows);
1785        let local = run_local_cache_perf(&workload, rows);
1786
1787        print_perf_result(&bypass);
1788        print_perf_result(&global);
1789        print_perf_result(&local);
1790
1791        eprintln!(
1792            "[wp-knowledge][cache-perf] speedup global_vs_bypass={:.2}x local_vs_bypass={:.2}x",
1793            bypass.elapsed.as_secs_f64() / global.elapsed.as_secs_f64(),
1794            bypass.elapsed.as_secs_f64() / local.elapsed.as_secs_f64(),
1795        );
1796
1797        assert_eq!(bypass.counters.result_hits, 0);
1798        assert_eq!(bypass.counters.result_misses, 0);
1799        assert_eq!(bypass.counters.local_hits, 0);
1800        assert_eq!(bypass.counters.local_misses, 0);
1801        assert!(global.counters.result_hits > 0);
1802        assert!(global.counters.result_misses > 0);
1803        assert_eq!(global.counters.local_hits, 0);
1804        assert_eq!(global.counters.local_misses, 0);
1805        assert!(local.counters.local_hits > 0);
1806        assert!(local.counters.local_misses > 0);
1807        assert!(local.counters.result_misses > 0);
1808    }
1809
1810    #[test]
1811    fn init_thread_cloned_from_knowdb_applies_result_cache_config() {
1812        let _guard = crate::runtime::runtime_test_guard()
1813            .lock()
1814            .expect("provider test guard");
1815        let root = uniq_cache_cfg_tmp_dir();
1816        let conf_path = write_minimal_knowdb_with_cache(&root, true, 7, 5);
1817        let auth_file = root.join(".run").join("authority.sqlite");
1818        fs::create_dir_all(auth_file.parent().expect("authority parent"))
1819            .expect("create authority parent");
1820        let authority_uri = format!("file:{}?mode=rwc&uri=true", auth_file.display());
1821
1822        init_thread_cloned_from_knowdb(&root, &conf_path, &authority_uri, &EnvDict::default())
1823            .expect("init knowdb with cache config");
1824
1825        let snapshot = runtime_snapshot();
1826        assert!(snapshot.result_cache_enabled);
1827        assert_eq!(snapshot.result_cache_capacity, 7);
1828        assert_eq!(snapshot.result_cache_ttl_ms, 5);
1829
1830        let req = QueryRequest::first_row(
1831            "SELECT value FROM example WHERE id=:id",
1832            fields_to_params(&[DataField::from_digit(":id", 1)]),
1833            CachePolicy::UseGlobal,
1834        );
1835        let before = runtime_snapshot();
1836        let row = runtime()
1837            .execute(&req)
1838            .expect("first result-cache query")
1839            .into_row();
1840        assert_eq!(row[0].to_string(), "chars(alpha)");
1841        let row = runtime()
1842            .execute(&req)
1843            .expect("second result-cache query")
1844            .into_row();
1845        assert_eq!(row[0].to_string(), "chars(alpha)");
1846        std::thread::sleep(Duration::from_millis(12));
1847        let row = runtime()
1848            .execute(&req)
1849            .expect("expired result-cache query")
1850            .into_row();
1851        assert_eq!(row[0].to_string(), "chars(alpha)");
1852        let after = runtime_snapshot();
1853
1854        assert!(after.result_cache_hits > before.result_cache_hits);
1855        assert!(after.result_cache_misses >= before.result_cache_misses + 2);
1856
1857        restore_default_result_cache_config();
1858        let _ = fs::remove_dir_all(&root);
1859    }
1860
1861    #[test]
1862    fn disabled_result_cache_from_knowdb_config_forces_bypass() {
1863        let _guard = crate::runtime::runtime_test_guard()
1864            .lock()
1865            .expect("provider test guard");
1866        let root = uniq_cache_cfg_tmp_dir();
1867        let conf_path = write_minimal_knowdb_with_cache(&root, false, 3, 30_000);
1868        let auth_file = root.join(".run").join("authority.sqlite");
1869        fs::create_dir_all(auth_file.parent().expect("authority parent"))
1870            .expect("create authority parent");
1871        let authority_uri = format!("file:{}?mode=rwc&uri=true", auth_file.display());
1872
1873        init_thread_cloned_from_knowdb(&root, &conf_path, &authority_uri, &EnvDict::default())
1874            .expect("init knowdb with cache disabled");
1875
1876        let snapshot = runtime_snapshot();
1877        assert!(!snapshot.result_cache_enabled);
1878        assert_eq!(snapshot.result_cache_capacity, 3);
1879        assert_eq!(snapshot.result_cache_ttl_ms, 30_000);
1880
1881        let req = QueryRequest::first_row(
1882            "SELECT value FROM example WHERE id=:id",
1883            fields_to_params(&[DataField::from_digit(":id", 1)]),
1884            CachePolicy::UseGlobal,
1885        );
1886        let before = runtime_snapshot();
1887        let _ = runtime()
1888            .execute(&req)
1889            .expect("first bypassed result-cache query");
1890        let _ = runtime()
1891            .execute(&req)
1892            .expect("second bypassed result-cache query");
1893        let after = runtime_snapshot();
1894
1895        assert_eq!(after.result_cache_hits, before.result_cache_hits);
1896        assert_eq!(after.result_cache_misses, before.result_cache_misses);
1897
1898        restore_default_result_cache_config();
1899        let _ = fs::remove_dir_all(&root);
1900    }
1901
1902    #[test]
1903    fn failed_knowdb_provider_init_does_not_apply_cache_config() {
1904        let _guard = crate::runtime::runtime_test_guard()
1905            .lock()
1906            .expect("provider test guard");
1907        restore_default_result_cache_config();
1908
1909        let db = MemDB::instance();
1910        db.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
1911            .expect("create table");
1912        db.execute("INSERT INTO t (id, value) VALUES (1, 'first')")
1913            .expect("seed table");
1914        init_mem_provider(db).expect("init provider");
1915
1916        let before = runtime_snapshot();
1917        let root = uniq_cache_cfg_tmp_dir();
1918        let conf_path = write_provider_only_knowdb_with_cache(
1919            &root,
1920            "mysql",
1921            "not-a-valid-mysql-url",
1922            false,
1923            3,
1924            5,
1925        );
1926        let authority_uri = format!(
1927            "file:{}?mode=rwc&uri=true",
1928            root.join("unused.sqlite").display()
1929        );
1930
1931        let err =
1932            init_thread_cloned_from_knowdb(&root, &conf_path, &authority_uri, &EnvDict::default());
1933        assert!(err.is_err());
1934
1935        let after = runtime_snapshot();
1936        assert_eq!(after.result_cache_enabled, before.result_cache_enabled);
1937        assert_eq!(after.result_cache_capacity, before.result_cache_capacity);
1938        assert_eq!(after.result_cache_ttl_ms, before.result_cache_ttl_ms);
1939
1940        let row = query_row("SELECT value FROM t WHERE id = 1").expect("query previous provider");
1941        assert_eq!(row[0].to_string(), "chars(first)");
1942
1943        let _ = fs::remove_dir_all(&root);
1944    }
1945
1946    // 安装一个命名内存 provider(不替换默认 provider)。
1947    fn install_named_mem_provider(name: &str, db: MemDB) {
1948        let datasource_id = DatasourceId::from_seed(ProviderKind::SqliteAuthority, name);
1949        let ds_for_scope = datasource_id.clone();
1950        runtime()
1951            .install_provider_named(
1952                name,
1953                ProviderKind::SqliteAuthority,
1954                datasource_id,
1955                |generation| {
1956                    Ok(Arc::new(MemProvider {
1957                        db,
1958                        metadata_scope: MetadataCacheScope {
1959                            datasource_id: ds_for_scope.clone(),
1960                            generation,
1961                        },
1962                    }))
1963                },
1964                false,
1965            )
1966            .expect("install named mem provider");
1967    }
1968
1969    #[test]
1970    fn cache_query_fields_route_separates_named_and_default_providers() {
1971        let _guard = crate::runtime::runtime_test_guard()
1972            .lock()
1973            .expect("provider test guard");
1974
1975        // 默认 provider:route_t.value = 'default'
1976        let db_default = MemDB::instance();
1977        db_default
1978            .execute("CREATE TABLE route_t (id INTEGER PRIMARY KEY, value TEXT)")
1979            .expect("create default table");
1980        db_default
1981            .execute("INSERT INTO route_t (id, value) VALUES (1, 'default')")
1982            .expect("seed default");
1983        init_mem_provider(db_default).expect("init default provider");
1984
1985        // 命名 provider "geo":route_t.value = 'geo'
1986        let db_geo = MemDB::instance();
1987        db_geo
1988            .execute("CREATE TABLE route_t (id INTEGER PRIMARY KEY, value TEXT)")
1989            .expect("create geo table");
1990        db_geo
1991            .execute("INSERT INTO route_t (id, value) VALUES (1, 'geo')")
1992            .expect("seed geo");
1993        install_named_mem_provider("geo", db_geo);
1994
1995        let sql = "SELECT value FROM route_t WHERE id=:id";
1996        let c_params = [DataField::from_digit("id", 1)];
1997        let query_params = [DataField::from_digit(":id", 1)];
1998        let mut cache = FieldQueryCache::default();
1999
2000        let named =
2001            cache_query_fields_route(Some("geo"), sql, 0, &c_params, &query_params, &mut cache);
2002        assert_eq!(named[0].to_string(), "chars(geo)");
2003
2004        // 同一 cache 实例切到默认 provider,本地缓存必须按 provider 隔离
2005        let default = cache_query_fields_route(None, sql, 0, &c_params, &query_params, &mut cache);
2006        assert_eq!(default[0].to_string(), "chars(default)");
2007
2008        // 再查命名 provider,仍返回 geo
2009        let named_again =
2010            cache_query_fields_route(Some("geo"), sql, 0, &c_params, &query_params, &mut cache);
2011        assert_eq!(named_again[0].to_string(), "chars(geo)");
2012    }
2013
2014    #[test]
2015    fn mixed_provider_queries_keep_local_cache_per_provider() {
2016        let _guard = crate::runtime::runtime_test_guard()
2017            .lock()
2018            .expect("provider test guard");
2019
2020        let db_default = MemDB::instance();
2021        db_default
2022            .execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
2023            .expect("create default table");
2024        db_default
2025            .execute("INSERT INTO t (id, value) VALUES (1, 'default')")
2026            .expect("seed default");
2027        init_mem_provider(db_default).expect("init default provider");
2028
2029        let db_geo = MemDB::instance();
2030        db_geo
2031            .execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
2032            .expect("create geo table");
2033        db_geo
2034            .execute("INSERT INTO t (id, value) VALUES (1, 'geo')")
2035            .expect("seed geo");
2036        install_named_mem_provider("geo", db_geo);
2037
2038        let sql = "SELECT value FROM t WHERE id=:id";
2039        let c_params = [DataField::from_digit("id", 1)];
2040        let query_params = [DataField::from_digit(":id", 1)];
2041        let mut cache = FieldQueryCache::default();
2042
2043        let before = runtime_snapshot();
2044        // 1) 命名 provider:miss → 执行
2045        let row =
2046            cache_query_fields_route(Some("geo"), sql, 0, &c_params, &query_params, &mut cache);
2047        assert_eq!(row[0].to_string(), "chars(geo)");
2048        // 2) 默认 provider:scope 不同,不应清空 geo 的本地缓存
2049        let row = cache_query_fields_route(None, sql, 0, &c_params, &query_params, &mut cache);
2050        assert_eq!(row[0].to_string(), "chars(default)");
2051        // 3) 再查命名 provider:应命中本地缓存(不重新执行)
2052        let row =
2053            cache_query_fields_route(Some("geo"), sql, 0, &c_params, &query_params, &mut cache);
2054        assert_eq!(row[0].to_string(), "chars(geo)");
2055        let after = runtime_snapshot();
2056        assert!(
2057            after.local_cache_hits > before.local_cache_hits,
2058            "命名 provider 二次查询应命中本地缓存(按 provider 隔离代际)"
2059        );
2060    }
2061
2062    #[test]
2063    fn local_authority_init_prunes_stale_named_providers() {
2064        let _guard = crate::runtime::runtime_test_guard()
2065            .lock()
2066            .expect("provider test guard");
2067
2068        // 先安装命名 provider,模拟上一份配置的残留
2069        let db = MemDB::instance();
2070        db.execute("CREATE TABLE t (id INTEGER PRIMARY KEY, value TEXT)")
2071            .expect("create table");
2072        db.execute("INSERT INTO t (id, value) VALUES (1, 'x')")
2073            .expect("seed table");
2074        install_named_mem_provider("geo", db);
2075        assert!(provider_exists("geo"));
2076
2077        // 再用本地 authority 配置初始化 → 应清理 geo,仅保留 default
2078        let root = uniq_cache_cfg_tmp_dir();
2079        let conf_path = write_minimal_knowdb_with_cache(&root, true, 16, 30_000);
2080        let auth_file = root.join(".run").join("authority.sqlite");
2081        fs::create_dir_all(auth_file.parent().expect("authority parent"))
2082            .expect("create authority parent");
2083        let authority_uri = format!("file:{}?mode=rwc&uri=true", auth_file.display());
2084
2085        init_thread_cloned_from_knowdb(&root, &conf_path, &authority_uri, &EnvDict::default())
2086            .expect("init local authority");
2087
2088        assert!(!provider_exists("geo"));
2089        assert!(provider_exists(DEFAULT_SQLDB_NAME));
2090
2091        restore_default_result_cache_config();
2092        let _ = fs::remove_dir_all(&root);
2093    }
2094
2095    #[test]
2096    fn cache_query_fields_route_unknown_provider_returns_empty() {
2097        let _guard = crate::runtime::runtime_test_guard()
2098            .lock()
2099            .expect("provider test guard");
2100        let c_params = [DataField::from_digit("id", 1)];
2101        let query_params = [DataField::from_digit(":id", 1)];
2102        let mut cache = FieldQueryCache::default();
2103        let out = cache_query_fields_route(
2104            Some("nope"),
2105            "SELECT value FROM t WHERE id=:id",
2106            0,
2107            &c_params,
2108            &query_params,
2109            &mut cache,
2110        );
2111        assert!(out.is_empty());
2112    }
2113
2114    #[test]
2115    fn query_for_named_provider_returns_rows() {
2116        let _guard = crate::runtime::runtime_test_guard()
2117            .lock()
2118            .expect("provider test guard");
2119        let db = MemDB::instance();
2120        db.execute("CREATE TABLE named_t (id INTEGER PRIMARY KEY, value TEXT)")
2121            .expect("create named table");
2122        db.execute("INSERT INTO named_t (id, value) VALUES (1, 'named')")
2123            .expect("seed named");
2124        install_named_mem_provider("geo", db);
2125
2126        let rows = query_for("geo", "SELECT value FROM named_t WHERE id = 1").expect("query geo");
2127        assert_eq!(rows.len(), 1);
2128        assert_eq!(rows[0][0].to_string(), "chars(named)");
2129        assert!(provider_exists("geo"));
2130        assert!(!provider_exists("nope"));
2131        assert!(provider_names().iter().any(|name| name == "geo"));
2132    }
2133
2134    #[tokio::test(flavor = "current_thread")]
2135    async fn query_async_for_named_provider_works() {
2136        let _guard = crate::runtime::runtime_test_guard().lock_async().await;
2137        let db = MemDB::instance();
2138        db.execute("CREATE TABLE named_t (id INTEGER PRIMARY KEY, value TEXT)")
2139            .expect("create named table");
2140        db.execute("INSERT INTO named_t (id, value) VALUES (1, 'async-named')")
2141            .expect("seed named");
2142        install_named_mem_provider("geo", db);
2143
2144        let rows = query_async_for("geo", "SELECT value FROM named_t WHERE id = 1")
2145            .await
2146            .expect("query geo");
2147        assert_eq!(rows[0][0].to_string(), "chars(async-named)");
2148    }
2149}