1use crate::DBQuery;
2use crate::cache::CacheAble;
3use crate::error::{KnowReason, KnowledgeResult};
4use crate::mem::RowData;
5use crate::mem::stub::StubMDB;
6use csv::Reader;
7use enum_dispatch::enum_dispatch;
8use lazy_static::lazy_static;
9use orion_error::conversion::ErrorWith;
10use orion_error::conversion::{SourceRawErr, ToStructError};
11use r2d2_sqlite::SqliteConnectionManager;
12use rusqlite::OpenFlags;
13use rusqlite::Params;
14use rusqlite::ToSql;
15use rusqlite::types::ToSqlOutput;
16use rusqlite::types::Value;
17use std::path::PathBuf;
18use wp_log::debug_kdb;
19use wp_log::info_kdb;
20use wp_log::warn_kdb;
21use wp_model_core::model;
22use wp_model_core::model::DataField;
23
24use super::SqlNamedParam;
25use crate::loader::ProviderKind;
26use crate::runtime::MetadataCacheScope;
27
28lazy_static! {
29 pub static ref MEM_SQLITE_INS: r2d2::Pool<SqliteConnectionManager> =
34 r2d2::Pool::builder()
35 .max_size(1)
36 .build(SqliteConnectionManager::memory())
37 .expect("init SQLite memory pool (size=1) failed");
38}
39
40#[derive(Debug, Clone)]
41pub struct MemDB {
42 conn: r2d2::Pool<SqliteConnectionManager>,
43}
44
45#[derive(Debug, Clone)]
46#[enum_dispatch(DBQuery)]
47pub enum MDBEnum {
48 Stub(StubMDB),
49 Use(MemDB),
50}
51impl Default for MDBEnum {
52 fn default() -> Self {
53 MDBEnum::Stub(StubMDB {})
54 }
55}
56impl MDBEnum {
57 pub fn global() -> Self {
58 MDBEnum::Use(MemDB::global())
59 }
60 pub fn load_test() -> KnowledgeResult<()> {
61 MemDB::load_test()?;
62 Ok(())
63 }
64}
65
66pub fn cache_query<const N: usize, P: Params>(
67 db: &MDBEnum,
68 sql: &str,
69 c_params: &[DataField; N],
70 q_params: P,
71 cache: &mut impl CacheAble<DataField, RowData, N>,
72) -> RowData {
73 crate::cache_util::cache_query_impl(c_params, cache, || db.query_row_params(sql, q_params))
74}
75impl ToSql for SqlNamedParam {
76 fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
77 match self.0.get_value() {
78 model::Value::Bool(v) => Ok(ToSqlOutput::Owned(Value::Integer(if *v { 1 } else { 0 }))),
79 model::Value::Null => Ok(ToSqlOutput::Owned(Value::Null)),
80 model::Value::Chars(v) => Ok(ToSqlOutput::Owned(Value::Text(v.to_string()))),
81 model::Value::Symbol(v) => Ok(ToSqlOutput::Owned(Value::Text(v.to_string()))),
82 model::Value::Time(v) => Ok(ToSqlOutput::Owned(Value::Text(v.to_string()))),
83 model::Value::Digit(v) => Ok(ToSqlOutput::Owned(Value::Integer(*v))),
84 model::Value::BigUint(v) => Ok(ToSqlOutput::Owned(Value::Text(v.to_string()))),
86 model::Value::Hex(v) => Ok(ToSqlOutput::Owned(Value::Text(v.to_string()))),
87 model::Value::Float(v) => Ok(ToSqlOutput::Owned(Value::Real(*v))),
88 model::Value::IpNet(v) => Ok(ToSqlOutput::Owned(Value::Text(v.to_string()))),
89 model::Value::IpAddr(v) => Ok(ToSqlOutput::Owned(Value::Text(v.to_string()))),
90 model::Value::Ignore(_) => Ok(ToSqlOutput::Owned(Value::Null)),
91 model::Value::Obj(v) => Ok(ToSqlOutput::Owned(Value::Text(format!("{:?}", v)))),
92 model::Value::Array(v) => Ok(ToSqlOutput::Owned(Value::Text(format!("{:?}", v)))),
93 model::Value::Domain(v) => Ok(ToSqlOutput::Owned(Value::Text(v.0.to_string()))),
94 model::Value::Url(v) => Ok(ToSqlOutput::Owned(Value::Text(v.0.to_string()))),
95 model::Value::Email(v) => Ok(ToSqlOutput::Owned(Value::Text(v.0.to_string()))),
96 model::Value::IdCard(v) => Ok(ToSqlOutput::Owned(Value::Text(v.0.to_string()))),
97 model::Value::MobilePhone(v) => Ok(ToSqlOutput::Owned(Value::Text(v.0.to_string()))),
98 }
99 }
100}
101
102impl DBQuery for MemDB {
103 fn query(&self, sql: &str) -> KnowledgeResult<Vec<RowData>> {
104 let conn = self
105 .conn
106 .get()
107 .source_raw_err(KnowReason::from_res(), "source error")
108 .doing("get memdb connect")?;
109 let _ = crate::sqlite_ext::register_builtin(&conn);
110 super::query_util::query_cached(&conn, sql, [])
111 }
112
113 fn query_row(&self, sql: &str) -> KnowledgeResult<RowData> {
114 let conn = self
115 .conn
116 .get()
117 .source_raw_err(KnowReason::from_res(), "source error")
118 .doing("get memdb connect")?;
119 let _ = crate::sqlite_ext::register_builtin(&conn);
121 super::query_util::query_first_row_cached(&conn, sql, [])
122 }
123
124 fn query_row_params<P: Params>(&self, sql: &str, params: P) -> KnowledgeResult<RowData> {
125 debug_kdb!("[memdb] query_row_params: {}", sql);
126 let conn = self
127 .conn
128 .get()
129 .source_raw_err(KnowReason::from_res(), "source error")?;
130 let _ = crate::sqlite_ext::register_builtin(&conn);
132 super::query_util::query_first_row_cached(&conn, sql, params)
133 }
134
135 fn query_row_tdos<P: Params>(
136 &self,
137 _sql: &str,
138 _params: &[DataField; 2],
139 ) -> KnowledgeResult<RowData> {
140 todo!();
144 }
145}
146impl MemDB {
147 pub fn query_with_scope(
148 &self,
149 scope: &MetadataCacheScope,
150 sql: &str,
151 ) -> KnowledgeResult<Vec<RowData>> {
152 let conn = self
153 .conn
154 .get()
155 .source_raw_err(KnowReason::from_res(), "source error")
156 .doing("get memdb connect")?;
157 let _ = crate::sqlite_ext::register_builtin(&conn);
158 super::query_util::query_cached_with_scope(
159 &conn,
160 scope,
161 Some(ProviderKind::SqliteAuthority),
162 sql,
163 [],
164 )
165 }
166
167 pub fn query_row_with_scope(
168 &self,
169 scope: &MetadataCacheScope,
170 sql: &str,
171 ) -> KnowledgeResult<RowData> {
172 let conn = self
173 .conn
174 .get()
175 .source_raw_err(KnowReason::from_res(), "source error")
176 .doing("get memdb connect")?;
177 let _ = crate::sqlite_ext::register_builtin(&conn);
178 super::query_util::query_first_row_cached_with_scope(
179 &conn,
180 scope,
181 Some(ProviderKind::SqliteAuthority),
182 sql,
183 [],
184 )
185 }
186
187 pub fn query_fields_with_scope(
188 &self,
189 scope: &MetadataCacheScope,
190 sql: &str,
191 params: &[DataField],
192 ) -> KnowledgeResult<Vec<RowData>> {
193 let conn = self
194 .conn
195 .get()
196 .source_raw_err(KnowReason::from_res(), "source error")
197 .doing("get memdb connect")?;
198 let _ = crate::sqlite_ext::register_builtin(&conn);
199 let named_params = params
200 .iter()
201 .cloned()
202 .map(SqlNamedParam)
203 .collect::<Vec<_>>();
204 let refs: Vec<(&str, &dyn ToSql)> = named_params
205 .iter()
206 .map(|param| (param.0.get_name(), param as &dyn ToSql))
207 .collect();
208 super::query_util::query_cached_with_scope(
209 &conn,
210 scope,
211 Some(ProviderKind::SqliteAuthority),
212 sql,
213 refs.as_slice(),
214 )
215 }
216
217 pub fn query_named_fields_with_scope(
218 &self,
219 scope: &MetadataCacheScope,
220 sql: &str,
221 params: &[DataField],
222 ) -> KnowledgeResult<RowData> {
223 self.query_fields_with_scope(scope, sql, params)
224 .map(|rows| rows.into_iter().next().unwrap_or_default())
225 }
226
227 pub fn query_fields(&self, sql: &str, params: &[DataField]) -> KnowledgeResult<Vec<RowData>> {
228 let conn = self
229 .conn
230 .get()
231 .source_raw_err(KnowReason::from_res(), "source error")
232 .doing("get memdb connect")?;
233 let _ = crate::sqlite_ext::register_builtin(&conn);
234 let named_params = params
235 .iter()
236 .cloned()
237 .map(SqlNamedParam)
238 .collect::<Vec<_>>();
239 let refs: Vec<(&str, &dyn ToSql)> = named_params
240 .iter()
241 .map(|param| (param.0.get_name(), param as &dyn ToSql))
242 .collect();
243 super::query_util::query_cached(&conn, sql, refs.as_slice())
244 }
245
246 pub fn query_named_fields(&self, sql: &str, params: &[DataField]) -> KnowledgeResult<RowData> {
247 self.query_fields(sql, params)
248 .map(|rows| rows.into_iter().next().unwrap_or_default())
249 }
250
251 pub fn instance() -> Self {
252 let manager = SqliteConnectionManager::memory();
254 let pool = r2d2::Pool::builder()
255 .max_size(1)
256 .build(manager)
257 .expect("init SQLite memory pool (size=1) failed");
258 Self { conn: pool }
259 }
260 pub fn shared_pool(max_size: u32) -> KnowledgeResult<Self> {
263 let uri = "file:wp_knowledge_shm?mode=memory&cache=shared";
266 let manager = SqliteConnectionManager::file(uri).with_flags(
267 OpenFlags::SQLITE_OPEN_READ_WRITE
268 | OpenFlags::SQLITE_OPEN_CREATE
269 | OpenFlags::SQLITE_OPEN_URI,
270 );
271 let pool = r2d2::Pool::builder()
272 .max_size(max_size)
273 .build(manager)
274 .source_raw_err(KnowReason::from_res(), "source error")?;
275 Ok(Self { conn: pool })
276 }
277
278 pub fn new_file(
280 path: &str,
281 max_size: u32,
282 flags: rusqlite::OpenFlags,
283 ) -> KnowledgeResult<Self> {
284 let manager = r2d2_sqlite::SqliteConnectionManager::file(path).with_flags(flags);
285 let pool = r2d2::Pool::builder()
286 .max_size(max_size)
287 .build(manager)
288 .source_raw_err(KnowReason::from_res(), "source error")?;
289 Ok(Self { conn: pool })
290 }
291 pub fn with_conn<T, F: FnOnce(&rusqlite::Connection) -> anyhow::Result<T>>(
296 &self,
297 f: F,
298 ) -> anyhow::Result<T> {
299 let pooled = self.conn.get()?;
300 let conn_ref: &rusqlite::Connection = &pooled;
301 f(conn_ref)
302 }
303
304 pub fn table_create(&self, sql: &str) -> KnowledgeResult<()> {
305 let conn = self
306 .conn
307 .get()
308 .source_raw_err(KnowReason::from_res(), "source error")?;
309 conn.execute(sql, ())
310 .source_raw_err(KnowReason::from_rule(), "source error")?;
311 debug_kdb!("crate table: {} ", sql);
312 Ok(())
313 }
314 pub fn execute(&self, sql: &str) -> KnowledgeResult<()> {
315 let conn = self
316 .conn
317 .get()
318 .source_raw_err(KnowReason::from_res(), "source error")?;
319 conn.execute(sql, ())
320 .source_raw_err(KnowReason::from_rule(), "source error")?;
321 debug_kdb!("execute: {} ", sql);
322 Ok(())
323 }
324
325 pub fn table_clean(&self, sql: &str) -> KnowledgeResult<()> {
326 let conn = self
327 .conn
328 .get()
329 .source_raw_err(KnowReason::from_res(), "source error")?;
330 conn.execute(sql, ())
331 .source_raw_err(KnowReason::from_rule(), "source error")?;
332 debug_kdb!("clean table: {} ", sql);
333 Ok(())
334 }
335
336 pub fn table_load(
337 &self,
338 sql: &str,
339 csv_path: PathBuf,
340 cols: Vec<usize>,
341 max: usize,
342 ) -> KnowledgeResult<usize> {
343 info_kdb!("load table data in {}", csv_path.display());
344 if !csv_path.exists() {
345 warn_kdb!("{} not find, load knowdb failed", csv_path.display());
346 return Ok(0);
347 }
348 let mut rdr =
349 Reader::from_path(&csv_path).source_raw_err(KnowReason::from_res(), "source error")?;
350 let conn = self
351 .conn
352 .get()
353 .source_raw_err(KnowReason::from_res(), "source error")?;
354 let mut load_cnt: usize = 0;
355 let mut stmt = conn
357 .prepare(sql)
358 .source_raw_err(KnowReason::from_rule(), "source error")?;
359 for (idx, result) in rdr.records().enumerate() {
360 if load_cnt >= max {
361 break;
362 }
363 let record = result.map_err(|e| {
364 KnowReason::from_rule().to_err().with_detail(format!(
365 "read csv record failed at line {}: {}",
366 idx + 1,
367 e
368 ))
369 })?;
370
371 if let Some(max_col) = cols.iter().max()
373 && *max_col >= record.len()
374 {
375 return Err(KnowReason::from_rule().to_err().with_detail(format!(
376 "csv has insufficient columns at line {}: need index {}, got {} columns",
377 idx + 1,
378 *max_col,
379 record.len()
380 )));
381 }
382
383 let mut vec: Vec<&str> = Vec::with_capacity(cols.len());
385 for &ci in &cols {
386 let v = record.get(ci).ok_or_else(|| {
387 KnowReason::from_rule().to_err().with_detail(format!(
388 "line {} col {} missing",
389 idx + 1,
390 ci
391 ))
392 })?;
393 vec.push(v);
394 }
395 let params = rusqlite::params_from_iter(vec);
396 stmt.execute(params)
397 .source_raw_err(KnowReason::from_rule(), "source error")?;
398 load_cnt += 1;
399 }
400 info_kdb!("from {} load data cnt: {}", csv_path.display(), load_cnt);
401 Ok(load_cnt)
402 }
403
404 pub fn check_data(&self, table: &str, scope: (usize, usize)) -> KnowledgeResult<usize> {
405 let conn = self
406 .conn
407 .get()
408 .source_raw_err(KnowReason::from_res(), "source error")?;
409 let count_sql = format!("select count(*) from {}", table);
410 let count: usize = conn
411 .query_row(count_sql.as_str(), (), |row| row.get(0))
412 .source_raw_err(KnowReason::from_rule(), "source error")?;
413 if count >= scope.0 {
414 Ok(count)
415 } else {
416 Err(KnowReason::from_conf()
417 .to_err()
418 .with_detail("table data less")
419 .with_context(("table", table))
420 .with_context(("count", count.to_string())))
421
422 }
430 }
431
432 pub fn global() -> Self {
433 Self {
434 conn: MEM_SQLITE_INS.clone(),
435 }
436 }
437 pub fn load_test() -> KnowledgeResult<Self> {
438 let db = Self::global();
439 debug_kdb!("[memdb] load_test invoked");
440 db.table_create(EXAMPLE_CREATE_SQL)?;
441 let csv = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/mem/dict/example.csv");
443 let _ = db.table_clean(EXAMPLE_CLEAN_SQL);
444 db.table_load(EXAMPLE_INSERT_SQL, csv, vec![0, 1], 100)?;
445 if let Ok(cnt) = db.check_data("example", (1, usize::MAX)) {
447 debug_kdb!("[memdb] example rows loaded = {}", cnt);
448 }
449 Ok(db)
450 }
451}
452pub const EXAMPLE_CREATE_SQL: &str = r#"CREATE TABLE IF NOT EXISTS example (
453 id INTEGER PRIMARY KEY,
454 name TEXT NOT NULL,
455 pinying TEXT NOT NULL
456 )"#;
457pub const EXAMPLE_CLEAN_SQL: &str = "DELETE FROM example";
458pub const EXAMPLE_INSERT_SQL: &str = r#"INSERT INTO example(name,pinying) VALUES (?1, ?2 ) "#;
459
460#[cfg(test)]
461mod tests {
462
463 use std::{fs::File, io::Read};
464
465 use super::*;
466 use crate::error::{KnowReason, KnowledgeResult};
468 use crate::mem::ToSqlParams;
469
470 use orion_conf::EnvTomlLoad;
471 use orion_error::conversion::SourceErr;
472 use orion_variate::EnvDict;
473 use serde::Serialize;
474 use std::fs;
475 use wp_data_fmt::{Csv, RecordFormatter};
476
477 #[test]
478 fn test_load() -> KnowledgeResult<()> {
479 let db = MemDB::instance();
480 db.table_create(EXAMPLE_CREATE_SQL)?;
481 let loaded = db.table_load(
482 EXAMPLE_INSERT_SQL,
483 PathBuf::from("src/mem/dict/example.csv"),
484 vec![0, 1],
485 100,
486 )?;
487 assert_eq!(loaded, 10);
488 let fmt = Csv::default();
489 let tdos = db.query_row("select * from example;")?;
490 for obj in tdos {
491 println!("{}", fmt.fmt_field(&obj.into()));
492 }
493 Ok(())
494 }
495
496 #[test]
497 fn test_csv_off_by_one() -> KnowledgeResult<()> {
498 let db = MemDB::instance();
499 db.table_create(EXAMPLE_CREATE_SQL)?;
500 let loaded = db.table_load(
502 EXAMPLE_INSERT_SQL,
503 PathBuf::from("src/mem/dict/example.csv"),
504 vec![0, 1],
505 1,
506 )?;
507 assert_eq!(loaded, 1);
508 Ok(())
509 }
510
511 #[test]
512 fn test_row_null_mapping() -> KnowledgeResult<()> {
513 let db = MemDB::instance();
514 db.execute("CREATE TABLE tnull (v TEXT)")?;
515 db.execute("INSERT INTO tnull (v) VALUES (NULL)")?;
516 let row = db.query_row("SELECT v FROM tnull")?;
517 assert_eq!(row.len(), 1);
518 assert_eq!(row[0].get_name(), "v");
519 assert!(matches!(row[0].get_value(), model::Value::Null));
521 Ok(())
522 }
523
524 #[test]
525 fn test_row_blob_mapping() -> KnowledgeResult<()> {
526 let db = MemDB::instance();
527 db.execute("CREATE TABLE tblob (b BLOB)")?;
528 db.execute("INSERT INTO tblob (b) VALUES (X'414243')")?;
530 let row = db.query_row("SELECT b FROM tblob")?;
531 assert_eq!(row.len(), 1);
532 assert_eq!(row[0].get_name(), "b");
533 assert_eq!(row[0].to_string(), "chars(ABC)");
535 Ok(())
536 }
537
538 #[test]
539 fn test_csv_missing_column_error() -> KnowledgeResult<()> {
540 use std::fs;
541 use std::io::Write;
542 let db = MemDB::instance();
543 db.table_create(EXAMPLE_CREATE_SQL)?;
544 let mut path = std::env::temp_dir();
546 path.push("wp_knowledge_csv_missing_col.csv");
547 {
548 let mut f =
549 fs::File::create(&path).source_raw_err(KnowReason::from_res(), "source error")?;
550 writeln!(f, "name").source_raw_err(KnowReason::from_res(), "source error")?;
551 writeln!(f, "only_one_col").source_raw_err(KnowReason::from_res(), "source error")?;
552 }
553 let res = db.table_load(
554 EXAMPLE_INSERT_SQL,
555 path.clone(),
556 vec![0, 1], 10,
558 );
559 assert!(res.is_err());
560 let e = format!("{}", res.err().unwrap());
561 assert!(e.contains("line"));
562 assert!(e.contains("insufficient columns"));
563 let _ = fs::remove_file(&path);
565 Ok(())
566 }
567
568 #[test]
569 fn test_global_persistence_across_handles() -> KnowledgeResult<()> {
570 {
572 let db1 = MemDB::global();
573 db1.execute("CREATE TABLE IF NOT EXISTS gtest (v TEXT)")?;
574 db1.execute("INSERT INTO gtest (v) VALUES ('ok')")?;
575 }
576 {
578 let db2 = MemDB::global();
579 let rows = db2.query_row("SELECT v FROM gtest")?;
580 assert_eq!(rows.len(), 1);
581 assert_eq!(rows[0].to_string(), "chars(ok)");
582 }
583 Ok(())
584 }
585
586 #[test]
587 fn test_init_by_conf() -> KnowledgeResult<()> {
588 let db = MemDB::global();
589 db.table_create(EXAMPLE_CREATE_SQL)?;
590 let _ = db.table_clean(EXAMPLE_CLEAN_SQL);
591 db.table_load(
592 EXAMPLE_INSERT_SQL,
593 PathBuf::from("src/mem/dict/example.csv"),
594 vec![0, 1],
595 100,
596 )?;
597 Ok(())
598 }
599
600 #[test]
603 fn test_alter_level() -> KnowledgeResult<()> {
604 let db = MemDB::global();
605 let _ = db.execute("DROP TABLE IF EXISTS alert_cat_level");
607 db.table_create(
608 r#"CREATE TABLE IF NOT EXISTS alert_cat_level (
609 id INTEGER PRIMARY KEY,
610 log_type TEXT NOT NULL,
611 level1_code TEXT NOT NULL,
612 level1_name TEXT NOT NULL,
613 level2_code TEXT NOT NULL,
614 level2_name TEXT NOT NULL,
615 original_code TEXT NOT NULL,
616 original_name TEXT NOT NULL
617 )"#,
618 )?;
619 let _ = db.table_clean("DELETE FROM alert_cat_level");
620 db.table_load(
621 r#"INSERT INTO alert_cat_level (log_type, level1_code, level1_name, level2_code, level2_name, original_code, original_name) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"#,
622 PathBuf::from("src/mem/dict/event_cat_level.csv"),
623 vec![0, 1, 2, 3, 4, 5, 6],
624 2000,
625 )?;
626
627 let sql = "select level1_code from alert_cat_level where log_type = :log_type and original_code = :code ";
628 let result = db.query_row_params(
629 sql,
631 &[(":log_type", "app_log"), (":code", "00000002")],
632 )?;
633 assert_eq!(result, vec![DataField::from_chars("level1_code", "105")]);
634
635 let px = [
636 SqlNamedParam(DataField::from_chars(":code", "00000002")),
637 SqlNamedParam(DataField::from_chars(":log_type", "app_log")),
638 ];
639
640 let p = px.to_params();
641 let result = db.query_row_params(sql, &p)?;
642 assert_eq!(result, vec![DataField::from_chars("level1_code", "105")]);
643
644 Ok(())
645 }
646
647 #[test]
648 fn test_tosql_bind_various_types() -> KnowledgeResult<()> {
649 use chrono::NaiveDate;
650 use std::net::{IpAddr, Ipv4Addr};
651 use wp_model_core::model::types::value::ObjectValue;
652 use wp_model_core::model::{DateTimeValue, HexT};
653
654 let db = MemDB::instance();
655 db.execute("CREATE TABLE p (v)")?;
656
657 {
659 let sql = "INSERT INTO p (v) VALUES (:v)";
660 let p = [SqlNamedParam(DataField::from_bool(":v", true))];
661 db.query_row_params(sql, &p.to_params())?;
662 let row = db.query_row("SELECT v FROM p ORDER BY rowid DESC LIMIT 1")?;
663 assert!(matches!(row[0].get_value(), model::Value::Digit(1)));
664 }
665 {
667 let sql = "INSERT INTO p (v) VALUES (:v)";
668 let p = [SqlNamedParam(DataField::new(
669 model::DataType::default(),
670 ":v",
671 model::Value::Null,
672 ))];
673 db.query_row_params(sql, &p.to_params())?;
674 let row = db.query_row("SELECT v FROM p ORDER BY rowid DESC LIMIT 1")?;
675 assert!(matches!(row[0].get_value(), model::Value::Null));
676 }
677 {
679 let dt: DateTimeValue = NaiveDate::from_ymd_opt(2023, 1, 1)
680 .unwrap()
681 .and_hms_opt(0, 0, 0)
682 .unwrap();
683 let sql = "INSERT INTO p (v) VALUES (:v)";
684 let p = [SqlNamedParam(DataField::from_time(":v", dt))];
685 db.query_row_params(sql, &p.to_params())?;
686 let row = db.query_row("SELECT v FROM p ORDER BY rowid DESC LIMIT 1")?;
687 assert!(matches!(row[0].get_value(), model::Value::Chars(_)));
688 }
689 {
691 let sql = "INSERT INTO p (v) VALUES (:v)";
692 let p = [SqlNamedParam(DataField::from_hex(":v", HexT(0xABCD)))];
693 db.query_row_params(sql, &p.to_params())?;
694 let row = db.query_row("SELECT v FROM p ORDER BY rowid DESC LIMIT 1")?;
695 assert!(matches!(row[0].get_value(), model::Value::Chars(_)));
696 }
697 {
699 let sql = "INSERT INTO p (v) VALUES (:v)";
700 let p = [SqlNamedParam(DataField::from_ip(
701 ":v",
702 IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)),
703 ))];
704 db.query_row_params(sql, &p.to_params())?;
705 let row = db.query_row("SELECT v FROM p ORDER BY rowid DESC LIMIT 1")?;
706 assert_eq!(row[0].to_string(), "chars(1.2.3.4)");
707 }
708 {
710 let mut obj = ObjectValue::new();
711 obj.insert("k".to_string(), DataField::from_chars("", "v"));
712 let sql = "INSERT INTO p (v) VALUES (:v)";
713 let p = [SqlNamedParam(DataField::from_obj(":v", obj))];
714 db.query_row_params(sql, &p.to_params())?;
715 let row = db.query_row("SELECT v FROM p ORDER BY rowid DESC LIMIT 1")?;
716 assert!(matches!(row[0].get_value(), model::Value::Chars(_)));
717 }
718 {
720 let arr = vec![DataField::from_chars("", "a"), DataField::from_digit("", 1)];
721 let sql = "INSERT INTO p (v) VALUES (:v)";
722 let p = [SqlNamedParam(DataField::from_arr(":v", arr))];
723 db.query_row_params(sql, &p.to_params())?;
724 let row = db.query_row("SELECT v FROM p ORDER BY rowid DESC LIMIT 1")?;
725 assert!(matches!(row[0].get_value(), model::Value::Chars(_)));
726 }
727 {
729 use num_bigint::BigUint;
730 use std::str::FromStr;
731 let sql = "INSERT INTO p (v) VALUES (:v)";
732 let p = [SqlNamedParam(DataField::new(
733 model::DataType::BigInt,
734 ":v",
735 model::Value::BigUint(
736 BigUint::from_str("382824323044708348099391746388336347272").unwrap(),
737 ),
738 ))];
739 db.query_row_params(sql, &p.to_params())?;
740 let row = db.query_row("SELECT v FROM p ORDER BY rowid DESC LIMIT 1")?;
741 assert!(matches!(row[0].get_value(), model::Value::Chars(_)));
742 assert_eq!(
743 row[0].get_chars(),
744 Some("382824323044708348099391746388336347272")
745 );
746 }
747 Ok(())
748 }
749
750 #[test]
751 fn test_column_alias_names() -> KnowledgeResult<()> {
752 let db = MemDB::instance();
753 db.execute("CREATE TABLE ctest (a INTEGER, b TEXT)")?;
755 db.execute("INSERT INTO ctest (a,b) VALUES (42,'x')")?;
756 let row = db.query_row("SELECT a AS 'the number', b AS 'the text' FROM ctest LIMIT 1")?;
757 assert_eq!(row.len(), 2);
758 assert_eq!(row[0].get_name(), "the number");
759 assert_eq!(row[1].get_name(), "the text");
760 Ok(())
761 }
762
763 #[test]
764 fn test_concurrent_inserts() -> KnowledgeResult<()> {
765 use std::thread;
766 let db = MemDB::global();
767 db.execute("CREATE TABLE IF NOT EXISTS concur (v INTEGER)")?;
768 let threads: Vec<_> = (0..4)
769 .map(|_| {
770 thread::spawn(|| {
771 let dbt = MemDB::global();
772 for _ in 0..10 {
773 let _ = dbt.execute("INSERT INTO concur (v) VALUES (1)");
774 }
775 })
776 })
777 .collect();
778 for t in threads {
779 t.join().unwrap();
780 }
781 let row = db.query_row("SELECT SUM(v) AS total FROM concur")?;
782 assert_eq!(row[0].to_string(), "digit(40)");
784 Ok(())
785 }
786
787 #[test]
788 fn test_query_returns_all_rows() -> KnowledgeResult<()> {
789 let db = MemDB::instance();
790 db.execute("CREATE TABLE multi (id INTEGER, name TEXT)")?;
791 let rows = db.query("SELECT * FROM multi")?;
792 assert!(rows.is_empty(), "empty table should return empty vec");
793 db.execute("INSERT INTO multi (id, name) VALUES (1, 'alice')")?;
794 db.execute("INSERT INTO multi (id, name) VALUES (2, 'bob')")?;
795 db.execute("INSERT INTO multi (id, name) VALUES (3, 'charlie')")?;
796
797 let rows = db.query("SELECT id, name FROM multi ORDER BY id")?;
798 assert_eq!(rows.len(), 3, "should return all 3 rows");
799
800 Ok(())
801 }
802
803 #[allow(dead_code)]
804 fn load_toml_conf<T: serde::de::DeserializeOwned>(path: &str) -> KnowledgeResult<T> {
805 let mut f = File::open(path)
806 .source_raw_err(KnowReason::from_res(), "source error")
807 .doing(format!("conf file not found: {}", path))?;
808 let mut buffer = Vec::with_capacity(10240);
809 f.read_to_end(&mut buffer)
810 .source_raw_err(KnowReason::from_res(), "source error")?;
811 let conf_data =
812 String::from_utf8(buffer).source_raw_err(KnowReason::from_rule(), "source error")?;
813 let dict = EnvDict::new();
814 let conf: T = T::env_parse_toml(conf_data.as_str(), &dict)
815 .source_err(KnowReason::from_conf(), "parse toml config")?;
816 Ok(conf)
817 }
818
819 #[allow(dead_code)]
820 fn export_toml_local<T: Serialize>(val: &T, path: &str) -> KnowledgeResult<()> {
821 let data =
822 toml::to_string_pretty(val).source_raw_err(KnowReason::from_rule(), "source error")?;
823 if let Some(parent) = std::path::Path::new(path).parent() {
824 fs::create_dir_all(parent).source_raw_err(KnowReason::from_res(), "source error")?;
825 }
826 fs::write(path, data).source_raw_err(KnowReason::from_res(), "source error")?;
827 Ok(())
828 }
829}