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
221pub 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
267pub 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
317pub 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
338pub 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
354pub 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
370pub 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
384pub 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
393pub fn redis_bf_create(key: &str, error_rate: f64, capacity: i64) -> KnowledgeResult<()> {
399 crate::redis::bf_reserve("knowdb", key, error_rate, capacity)
400}
401
402pub(crate) fn register_fun_map(map: HashMap<String, crate::loader::FunSpec>) {
408 crate::fun::register_fun_map(map);
409}
410
411pub 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
433pub 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
488pub 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
508pub fn provider_exists(name: &str) -> bool {
510 runtime().provider_exists(name)
511}
512
513pub fn provider_names() -> Vec<String> {
515 runtime().provider_names()
516}
517
518pub 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
528pub 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
539pub 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
544pub 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
654pub 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 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
699fn 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
798pub 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 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 if let Some(specs) = provider_cfg.sqldb
915 && !specs.is_empty()
916 {
917 let expected = SqlProviderSpec::validate_specs(&specs)?;
918 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 for spec in &specs {
928 let name = spec.effective_name().to_string();
929 let is_default = name == default_name;
930 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 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 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 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 ¶ms,
1481 &mut cache,
1482 )
1483 .await;
1484 let second = cache_query_fields_async(
1485 "SELECT value FROM t WHERE id=:id",
1486 &key,
1487 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 ¶ms,
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 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 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 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 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 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 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 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 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 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 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}