From 15c4ce15140ec0f45f38dec7f0ec4e92a90b4421 Mon Sep 17 00:00:00 2001 From: harehare Date: Sun, 25 Oct 2026 12:30:24 +0900 Subject: [PATCH 1/2] fix(sql): remove duplicated ORDER BY test definition The test was defined twice, which stopped the test target from compiling. --- src/sql/tests.rs | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/src/sql/tests.rs b/src/sql/tests.rs index f4bac1f..620f53a 100644 --- a/src/sql/tests.rs +++ b/src/sql/tests.rs @@ -3510,42 +3510,6 @@ fn test_sql_order_by_mixed_integer_and_numeric_string_keys() { assert_eq!(names, vec!["second", "third", "first"]); } -#[test] -fn test_sql_order_by_mixed_null_and_numbers_is_total() { - let store = DocumentStore::new(); - let engine = SqlEngine::new(&store).unwrap(); - engine - .execute("CREATE TABLE v (name TEXT, embedding TEXT)") - .unwrap(); - let values: Vec = (0..200) - .map(|i| { - if i % 3 == 0 { - format!("('n{i}', 'bad')") - } else { - format!("('n{i}', '[1,{i}]')") - } - }) - .collect(); - engine - .execute(&format!("INSERT INTO v VALUES {}", values.join(","))) - .unwrap(); - let out = engine - .execute("SELECT name, cosine_similarity(embedding, '[1,0]') AS s FROM v ORDER BY s DESC") - .unwrap(); - assert_eq!(out.rows.len(), 200); - let nulls = out.rows.iter().filter(|r| r[1] == "NULL").count(); - assert_eq!(nulls, 67); - // Every number must come before every NULL. - let first_null = out.rows.iter().position(|r| r[1] == "NULL").unwrap(); - assert!(out.rows[first_null..].iter().all(|r| r[1] == "NULL")); - // And the numbers must be in descending order. - let nums: Vec = out.rows[..first_null] - .iter() - .map(|r| r[1].parse().unwrap()) - .collect(); - assert!(nums.windows(2).all(|w| w[0] >= w[1])); -} - fn people_engine_store() -> DocumentStore { let store = DocumentStore::new(); let engine = SqlEngine::new(&store).unwrap(); From c75a68b2ad4ccb7bdcbe783fe4edf87db75e480e Mon Sep 17 00:00:00 2001 From: harehare Date: Sun, 25 Oct 2026 12:32:56 +0900 Subject: [PATCH 2/2] refactor!: remove the Query builder and the lint command Both overlap with what SQL (WHERE, under()) and mq already cover. BREAKING CHANGE: DocumentStore::query(), Query, QueryResult, LintViolation and the `mq-db lint` subcommand are removed. --- README.md | 2 +- docs/books/src/reference/index-layers.md | 2 - docs/books/src/start/cli.md | 16 - docs/books/src/start/library.md | 23 +- src/bin/mq-db.rs | 48 --- src/lib.rs | 34 +- src/query.rs | 468 ----------------------- src/store.rs | 10 +- 8 files changed, 10 insertions(+), 593 deletions(-) delete mode 100644 src/query.rs diff --git a/README.md b/README.md index 023ca5b..d7aa60e 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ print!("{}", out.to_table()); ## Documentation -- [Install](https://db.mqlang.org/book/start/install) and [CLI](https://db.mqlang.org/book/start/cli) (`index`, `find`, `lint`, `stats`, `vacuum`, …) +- [Install](https://db.mqlang.org/book/start/install) and [CLI](https://db.mqlang.org/book/start/cli) (`index`, `find`, `stats`, `vacuum`, …) - [Library API](https://db.mqlang.org/book/start/library), [HTTP server](https://db.mqlang.org/book/start/http-server) - [SQL schema](https://db.mqlang.org/book/reference/sql-schema), [functions](https://db.mqlang.org/book/reference/sql-functions) and [examples](https://db.mqlang.org/book/reference/sql-examples) - [Block model](https://db.mqlang.org/book/reference/block-model), [index layers](https://db.mqlang.org/book/reference/index-layers) and [storage format](https://db.mqlang.org/book/reference/storage-format) diff --git a/docs/books/src/reference/index-layers.md b/docs/books/src/reference/index-layers.md index 1f1be43..ed57cae 100644 --- a/docs/books/src/reference/index-layers.md +++ b/docs/books/src/reference/index-layers.md @@ -31,8 +31,6 @@ Built once per document and stored in the `.mq-db` file. Checked before any bloc | `depth = N` (`N > 0`) | `N` exceeds `max_heading_depth` | | `block_type = 'heading' AND content = 'X'` | `heading_contents` has no case-insensitive match for `X` | -**Via the Rust API**: `store.query().documents(|doc| ...)` lets you filter on *any* zone-map field yourself (`heading_slugs`, `frontmatter_keys`, `title`, `tags`, …), not just the patterns `SqlEngine` recognizes automatically. - ## Layer 2: Interval Index (section hierarchy) Heading hierarchy is encoded as `(pre, post)` pairs via Pre-Post Order (Nested Set) traversal: diff --git a/docs/books/src/start/cli.md b/docs/books/src/start/cli.md index cfa25fc..c18edd7 100644 --- a/docs/books/src/start/cli.md +++ b/docs/books/src/start/cli.md @@ -96,22 +96,6 @@ mq-db repl --db store.mq-db --mode sql See [REPL](repl.md) for the full command list. -## `lint` - -Run structural lint checks (currently: a heading at the given depth immediately followed by a list). - -```bash -mq-db lint --db store.mq-db --depth 2 -``` - -```text -✗ 1 violation (H2 immediately followed by list) - - file heading - ──────────────────────────────────────── ────────────────────────────── - docs/DESIGN.md "Quick Start" -``` - ## `stats` Show store-wide statistics: document/block counts, block-type distribution, code-language distribution. diff --git a/docs/books/src/start/library.md b/docs/books/src/start/library.md index 6a5afbc..349a611 100644 --- a/docs/books/src/start/library.md +++ b/docs/books/src/start/library.md @@ -8,20 +8,13 @@ mq-db = "0.1" ``` ```rust -use mq_db::{DocumentStore, SqlEngine, MqEngine, block::BlockType}; +use mq_db::{DocumentStore, SqlEngine, MqEngine}; // Build in memory let mut store = DocumentStore::new(); store.add_file("docs/DESIGN.md")?; store.add_str("# Hello\n\n## Architecture\n\nDetails\n")?; -// Chainable query API: zone-map skip + interval scope + block predicates -let chunks = store.query() - .documents(|doc| doc.zone_maps.heading_contents.contains("Architecture")) - .under_heading("Architecture", Some(2)) - .filter(|b| matches!(b.block_type, BlockType::Paragraph | BlockType::Code)) - .blocks(); - // SQL engine (custom sqlparser-based evaluator, no SQLite dependency) let engine = SqlEngine::new(&store)?; let out = engine.execute( @@ -32,9 +25,6 @@ print!("{}", out.to_table()); // mq engine let results = MqEngine::eval_store(".h1", &store)?; -// Structural lint -let violations = store.query().lint_heading_followed_by(2, &[BlockType::List]); - // UPDATE/DELETE with write-back: rewrites the affected block's source file // (heading/paragraph content only), then re-parses it in place store.execute_sql_mut( @@ -66,14 +56,3 @@ let store = DocumentStore::load_catalog_only("store.mq-db")?; | `DocumentStore::load_catalog_only()` | Catalog only | `list` / `stats`-style metadata commands | When using `open()`, call `load_all_blocks()` and `load_all_indexes()` before running any `SqlEngine` query. - -## Query builder - -`store.query()` returns a chainable builder that applies the same three index layers used by the SQL engine, in order: - -1. `.documents(|doc| ...)`: zone-map predicate, skips whole documents -2. `.under_heading(title, depth)` / interval-scope helpers: narrows to a `(pre, post)` range -3. `.filter(|block| ...)`: per-block predicate over the remaining candidates -4. `.blocks()`: materializes the final `Vec<&Block>` - -See [Index Layers](../reference/index-layers.md) for how each layer works. diff --git a/src/bin/mq-db.rs b/src/bin/mq-db.rs index a6a3c21..55982cf 100644 --- a/src/bin/mq-db.rs +++ b/src/bin/mq-db.rs @@ -187,17 +187,6 @@ enum Commands { attach: Vec, }, - /// Run structural lint checks - Lint { - /// Path to .mq-db store file - #[arg(short, long, default_value = "store.mq-db")] - db: PathBuf, - - /// Heading depth to check (H1=1 .. H6=6) - #[arg(long, default_value_t = 2)] - depth: u8, - }, - /// Show store statistics Stats { /// Path to .mq-db store file @@ -894,43 +883,6 @@ async fn main() -> anyhow::Result<()> { run_repl(store, mode, write_back)?; } - // lint - Commands::Lint { db, depth } => { - let store = load_store(&db)?; - let q = store.query(); - let violations = q.lint_heading_followed_by(depth, &[BlockType::List]); - if violations.is_empty() { - println!( - "✓ No violations (H{} must not be immediately followed by a list)", - depth - ); - } else { - let n = violations.len(); - println!( - "✗ {} violation{} (H{} immediately followed by list)\n", - n, - if n == 1 { "" } else { "s" }, - depth - ); - println!(" {:<40} heading", "file"); - println!(" {} {}", "─".repeat(40), "─".repeat(30)); - for v in &violations { - let path = v - .document - .path - .as_ref() - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_else(|| format!("", v.document.id)); - let path_display = if path.len() > 40 { - format!("…{}", &path[path.len() - 39..]) - } else { - path - }; - println!(" {:<40} \"{}\"", path_display, v.heading.content); - } - } - } - // stats Commands::Stats { db } => { let store = load_store(&db)?; diff --git a/src/lib.rs b/src/lib.rs index c486492..6587b81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,38 +16,20 @@ //! slugs, code languages, front-matter keys/tags) that let the query //! engine skip irrelevant documents before scanning their blocks. //! -//! * **Chainable query API** – document-level and block-level predicates, -//! `UNDER heading` section scoping, built-in linter helpers. -//! //! ## Quick Start //! //! ```rust -//! use mq_db::{DocumentStore, block::BlockType}; -//! -//! let mut store = DocumentStore::new(); -//! store.add_str("# Hello\n\n## Architecture\n\nDetails\n\n```rust\ncode\n```\n").unwrap(); -//! -//! // Extract all content under the "Architecture" H2 -//! let chunks = store.query() -//! .under_heading("Architecture", Some(2)) -//! .filter(|b| matches!(b.block_type, BlockType::Paragraph | BlockType::Code)) -//! .blocks(); -//! -//! assert_eq!(chunks.len(), 2); -//! ``` -//! -//! ## Structural Linting -//! -//! ```rust -//! use mq_db::{DocumentStore, block::BlockType}; +//! use mq_db::{DocumentStore, SqlEngine}; //! //! let mut store = DocumentStore::new(); -//! store.add_str("## Section\n\n- item without intro paragraph\n").unwrap(); +//! store.add_str("# Hello\n\n## Architecture\n\nDetails\n").unwrap(); //! -//! let q = store.query(); -//! let violations = q.lint_heading_followed_by(2, &[BlockType::List]); +//! let engine = SqlEngine::new(&store).unwrap(); +//! let out = engine +//! .execute("SELECT content FROM blocks WHERE block_type = 'heading' ORDER BY pre") +//! .unwrap(); //! -//! assert_eq!(violations.len(), 1); +//! assert_eq!(out.rows.len(), 2); //! ``` pub mod block; @@ -57,7 +39,6 @@ pub mod error; pub mod index; pub mod indexes; pub mod mq_engine; -pub mod query; pub mod search; pub mod sql; pub mod storage; @@ -67,7 +48,6 @@ pub mod tui; pub use document::Document; pub use error::MqdbError; pub use mq_engine::MqEngine; -pub use query::{LintViolation, Query, QueryResult}; pub use sql::{QueryOutput, SqlEngine}; pub use storage::Storage; pub use storage::catalog::CatalogEntry; diff --git a/src/query.rs b/src/query.rs deleted file mode 100644 index 8f66a9d..0000000 --- a/src/query.rs +++ /dev/null @@ -1,468 +0,0 @@ -use crate::{ - block::{Block, BlockType}, - document::Document, - store::DocumentStore, -}; - -enum SectionAnchor { - /// Directly supply a known (pre, post) interval. - Interval { pre: u32, post: u32 }, - /// Find the first heading matching the given content and optional depth. - Heading { content: String, depth: Option }, -} - -/// A query result pairing a matched [`Block`] with its parent [`Document`]. -pub struct QueryResult<'a> { - pub block: &'a Block, - pub document: &'a Document, -} - -/// Chainable, lazy query builder over a [`DocumentStore`]. -/// -/// Filters are applied in evaluation order (cheapest first): -/// 1. Document-level zone-map skip (applied per document before scanning blocks) -/// 2. Section `UNDER` constraint (interval check) -/// 3. Block-level predicates -/// -/// # Example – RAG chunk extraction -/// -/// ```rust -/// use mq_db::{DocumentStore, block::BlockType}; -/// -/// let mut store = DocumentStore::new(); -/// store.add_str("# Doc\n\n## Architecture\n\nExplanation\n\n```rust\ncode\n```\n").unwrap(); -/// -/// let results = store.query() -/// .under_heading("Architecture", Some(2)) -/// .filter(|b| matches!(b.block_type, BlockType::Paragraph | BlockType::Code)) -/// .blocks(); -/// -/// assert_eq!(results.len(), 2); -/// ``` -type DocPredicate<'store> = Box bool + 'store>; -type BlockPredicate<'store> = Box bool + 'store>; - -pub struct Query<'store> { - store: &'store DocumentStore, - doc_predicate: Option>, - block_predicates: Vec>, - anchor: Option, - limit: Option, -} - -impl<'store> Query<'store> { - pub(crate) fn new(store: &'store DocumentStore) -> Self { - Self { - store, - doc_predicate: None, - block_predicates: Vec::new(), - anchor: None, - limit: None, - } - } - - /// Skip documents for which `predicate` returns `false`. - /// - /// Use this to leverage zone-map statistics before scanning blocks: - /// ```rust - /// # use mq_db::DocumentStore; - /// # let store = DocumentStore::new(); - /// store.query() - /// .documents(|doc| doc.zone_maps.code_languages.contains("python")); - /// ``` - pub fn documents(mut self, f: F) -> Self - where - F: Fn(&Document) -> bool + 'store, - { - self.doc_predicate = Some(Box::new(f)); - self - } - - /// Restrict results to blocks that fall within the heading section - /// identified by `content` and optional `depth`. - /// - /// Equivalent to the SQL `WHERE b UNDER (SELECT id FROM blocks WHERE ...)`. - /// - /// For best performance, chain a `.documents(|d| d.zone_maps.heading_contents.contains("..."))` - /// filter before this to skip irrelevant documents via zone maps. - pub fn under_heading(mut self, content: impl Into, depth: Option) -> Self { - self.anchor = Some(SectionAnchor::Heading { - content: content.into(), - depth, - }); - self - } - - /// Restrict results to blocks that fall within the interval `(pre, post)`. - /// - /// Use this when you already know the ancestor block's interval values. - pub fn under_interval(mut self, pre: u32, post: u32) -> Self { - self.anchor = Some(SectionAnchor::Interval { pre, post }); - self - } - - /// Keep only blocks for which `predicate` returns `true`. - pub fn filter(mut self, f: F) -> Self - where - F: Fn(&Block) -> bool + 'store, - { - self.block_predicates.push(Box::new(f)); - self - } - - /// Keep only blocks with the given [`BlockType`]. - pub fn block_type(self, ty: BlockType) -> Self { - self.filter(move |b| b.block_type == ty) - } - - /// Keep only heading blocks at the given depth. - pub fn heading_depth(self, depth: u8) -> Self { - self.filter(move |b| b.block_type == BlockType::Heading && b.heading_depth() == Some(depth)) - } - - /// Keep only code blocks with the given language tag. - pub fn code_lang(self, lang: impl Into) -> Self { - let lang = lang.into(); - self.filter(move |b| b.code_lang() == Some(lang.as_str())) - } - - /// Keep only blocks whose content contains `substring` (case-sensitive). - pub fn content_contains(self, substring: impl Into) -> Self { - let s = substring.into(); - self.filter(move |b| b.content.contains(s.as_str())) - } - - /// Keep only blocks whose content matches `pattern` (case-insensitive). - pub fn content_contains_ci(self, pattern: impl Into) -> Self { - let pat = pattern.into().to_lowercase(); - self.filter(move |b| b.content.to_lowercase().contains(pat.as_str())) - } - - /// Stop collecting after `n` results. - pub fn limit(mut self, n: usize) -> Self { - self.limit = Some(n); - self - } - - /// Execute the query, returning matched (block, document) pairs in - /// document order. - pub fn collect(&self) -> Vec> { - let mut results: Vec> = Vec::new(); - - 'doc: for doc in self.store.documents() { - // Zone-map skip - if let Some(dp) = &self.doc_predicate - && !dp(doc) - { - continue 'doc; - } - - // Resolve the section anchor for this document - let interval: Option<(u32, u32)> = match &self.anchor { - None => None, - Some(SectionAnchor::Interval { pre, post }) => Some((*pre, *post)), - Some(SectionAnchor::Heading { content, depth }) => doc - .blocks - .iter() - .find(|b| { - b.block_type == BlockType::Heading - && b.content == *content - && depth.is_none_or(|d| b.heading_depth() == Some(d)) - }) - .map(|h| (h.pre, h.post)), - }; - - for block in &doc.blocks { - // UNDER interval check - if let Some((anc_pre, anc_post)) = interval - && !block.is_under_interval(anc_pre, anc_post) - { - continue; - } - - // Block predicates - if self.block_predicates.iter().all(|f| f(block)) { - results.push(QueryResult { - block, - document: doc, - }); - - if let Some(limit) = self.limit - && results.len() >= limit - { - return results; - } - } - } - } - - results - } - - /// Like [`collect`], but returns cloned blocks (discarding document context). - /// - /// Returns owned `Vec` so it can be used on temporaries: - /// ```rust - /// # use mq_db::{DocumentStore, block::BlockType}; - /// # let mut store = DocumentStore::new(); - /// # store.add_str("# Hello\n").unwrap(); - /// let blocks = store.query().heading_depth(1).blocks(); - /// assert_eq!(blocks.len(), 1); - /// ``` - pub fn blocks(&self) -> Vec { - self.collect() - .into_iter() - .map(|r| r.block.clone()) - .collect() - } - - /// Returns the number of matching blocks without materialising them. - pub fn count(&self) -> usize { - self.collect().len() - } - - /// Find all (heading, next_sibling) pairs where the heading matches the - /// given depth and the immediately following sibling has one of the - /// `forbidden_types`. - /// - /// This is the foundation for structural lint rules such as: - /// > "An H2 heading must not be immediately followed by a list." - /// - /// # Example - /// - /// ```rust - /// use mq_db::{DocumentStore, block::BlockType}; - /// - /// let mut store = DocumentStore::new(); - /// store.add_str("## Section\n\n- item\n").unwrap(); - /// - /// let q = store.query(); - /// let violations = q.lint_heading_followed_by(2, &[BlockType::List]); - /// - /// assert_eq!(violations.len(), 1); - /// ``` - pub fn lint_heading_followed_by( - &self, - heading_depth: u8, - forbidden_types: &[BlockType], - ) -> Vec> { - let mut violations = Vec::new(); - - for doc in self.store.documents() { - if let Some(dp) = &self.doc_predicate - && !dp(doc) - { - continue; - } - - for block in &doc.blocks { - if block.block_type != BlockType::Heading { - continue; - } - if block.heading_depth() != Some(heading_depth) { - continue; - } - - if let Some(next) = doc.first_child(block) - && forbidden_types.contains(&next.block_type) - { - violations.push(LintViolation { - heading: block, - offending: next, - document: doc, - }); - } - } - } - - violations - } -} - -/// A structural lint violation: a heading followed by a forbidden block type. -pub struct LintViolation<'a> { - pub heading: &'a Block, - pub offending: &'a Block, - pub document: &'a Document, -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::DocumentStore; - use rstest::rstest; - - fn store_with(sources: &[&str]) -> DocumentStore { - let mut s = DocumentStore::new(); - for src in sources { - s.add_str(src).unwrap(); - } - s - } - - #[test] - fn test_block_type_filter() { - let store = store_with(&["# H1\n\n## H2\n\nParagraph\n\n```rust\ncode\n```\n"]); - let q = store.query().block_type(BlockType::Heading); - let headings = q.blocks(); - assert_eq!(headings.len(), 2); - } - - #[test] - fn test_heading_depth_filter() { - let store = store_with(&["# H1\n\n## H2\n\n### H3\n"]); - let q = store.query().heading_depth(2); - let h2s = q.blocks(); - assert_eq!(h2s.len(), 1); - assert_eq!(h2s[0].content, "H2"); - } - - #[test] - fn test_under_heading_filter() { - let store = store_with(&[ - "# Doc\n\n## Architecture\n\nExplanation\n\n```rust\ncode\n```\n\n## Other\n\nOther para\n", - ]); - - let q = store - .query() - .under_heading("Architecture", Some(2)) - .filter(|b| matches!(b.block_type, BlockType::Paragraph | BlockType::Code)); - let results = q.blocks(); - - // Should contain "Explanation" and the code block, but NOT "Other para" - assert_eq!(results.len(), 2); - assert!(results.iter().any(|b| b.content.contains("Explanation"))); - assert!(results.iter().any(|b| b.block_type == BlockType::Code)); - assert!(!results.iter().any(|b| b.content.contains("Other para"))); - } - - #[test] - fn test_limit() { - let store = store_with(&["# A\n\n# B\n\n# C\n\n# D\n"]); - let q = store.query().heading_depth(1).limit(2); - let results = q.blocks(); - assert_eq!(results.len(), 2); - } - - #[test] - fn test_content_contains() { - let store = store_with(&["# Hello World\n\n## Goodbye\n"]); - let q = store.query().content_contains("World"); - let results = q.blocks(); - assert_eq!(results.len(), 1); - assert_eq!(results[0].content, "Hello World"); - } - - #[test] - fn test_code_lang_filter() { - let store = store_with(&["```rust\nfn x(){}\n```\n\n```python\nx=1\n```\n"]); - let q = store.query().code_lang("rust"); - let results = q.blocks(); - assert_eq!(results.len(), 1); - assert_eq!(results[0].code_lang(), Some("rust")); - } - - #[test] - fn test_zone_map_document_skip() { - let store = store_with(&["```python\nx=1\n```\n", "```rust\nfn x(){}\n```\n"]); - - let q = store - .query() - .documents(|doc| doc.zone_maps.code_languages.contains("rust")) - .block_type(BlockType::Code); - let results = q.blocks(); - - assert_eq!(results.len(), 1); - assert_eq!(results[0].code_lang(), Some("rust")); - } - - #[test] - fn test_lint_heading_followed_by_list() { - let store = store_with(&[ - "## Good\n\nParagraph intro\n\n- item\n", - "## Bad\n\n- item without intro\n", - ]); - - let q = store.query(); - let violations = q.lint_heading_followed_by(2, &[BlockType::List]); - // Only the second doc has a violation - assert_eq!(violations.len(), 1); - assert_eq!(violations[0].heading.content, "Bad"); - assert_eq!(violations[0].offending.block_type, BlockType::List); - } - - #[test] - fn test_multi_document_query() { - let store = store_with(&[ - "# Doc1\n\n```rust\nfn a(){}\n```\n", - "# Doc2\n\n```python\nx=1\n```\n", - "# Doc3\n\n```rust\nfn b(){}\n```\n", - ]); - - let q = store.query().code_lang("rust"); - let results = q.blocks(); - assert_eq!(results.len(), 2); - } - - #[rstest] - #[case(BlockType::Heading, 3)] - #[case(BlockType::Paragraph, 1)] - #[case(BlockType::Code, 1)] - #[case(BlockType::List, 1)] - fn test_block_type_filter_count_param(#[case] block_type: BlockType, #[case] expected: usize) { - let store = - store_with(&["# H1\n\n## H2\n\n### H3\n\nParagraph\n\n```rust\ncode\n```\n\n- item\n"]); - assert_eq!( - store.query().block_type(block_type).blocks().len(), - expected - ); - } - - #[rstest] - #[case(1, 1, "H1")] - #[case(2, 1, "H2")] - #[case(3, 1, "H3")] - fn test_heading_depth_count_param( - #[case] depth: u8, - #[case] expected: usize, - #[case] content: &str, - ) { - let store = store_with(&["# H1\n\n## H2\n\n### H3\n"]); - let results = store.query().heading_depth(depth).blocks(); - assert_eq!(results.len(), expected); - assert_eq!(results[0].content, content); - } - - #[rstest] - #[case("Hello", 1)] - #[case("World", 1)] - #[case("Goodbye", 1)] - #[case("nonexistent_xyz", 0)] - fn test_content_contains_count_param(#[case] needle: &str, #[case] expected: usize) { - let store = store_with(&["# Hello World\n\n## Goodbye\n"]); - assert_eq!( - store.query().content_contains(needle).blocks().len(), - expected - ); - } - - #[rstest] - #[case("rust", 1)] - #[case("python", 1)] - #[case("go", 0)] - fn test_code_lang_count_param(#[case] lang: &str, #[case] expected: usize) { - let store = store_with(&["```rust\nfn x(){}\n```\n\n```python\nx=1\n```\n"]); - assert_eq!(store.query().code_lang(lang).blocks().len(), expected); - } - - #[rstest] - #[case(1, 1)] - #[case(2, 2)] - #[case(3, 3)] - #[case(100, 4)] - fn test_limit_count_param(#[case] limit: usize, #[case] expected: usize) { - let store = store_with(&["# A\n\n# B\n\n# C\n\n# D\n"]); - let results = store.query().heading_depth(1).limit(limit).blocks(); - assert_eq!(results.len(), expected); - } -} diff --git a/src/store.rs b/src/store.rs index 11eb019..c4c47c4 100644 --- a/src/store.rs +++ b/src/store.rs @@ -93,7 +93,6 @@ use crate::{ error::MqdbError, index, indexes::{DocumentIndex, TokenizerKind}, - query::Query, storage::{ Storage, catalog::{CatalogEntry, CustomTableEntry, ViewEntry}, @@ -199,9 +198,7 @@ impl VacuumReport { /// let mut store = DocumentStore::new(); /// store.add_str("# Hello\n\nWorld").unwrap(); /// -/// let results = store.query().heading_depth(1).blocks(); -/// assert_eq!(results.len(), 1); -/// assert_eq!(results[0].content, "Hello"); +/// assert_eq!(store.len(), 1); /// ``` pub struct DocumentStore { documents: Vec, @@ -740,11 +737,6 @@ impl DocumentStore { self.documents.is_empty() } - /// Creates a new query builder backed by this store. - pub fn query(&self) -> Query<'_> { - Query::new(self) - } - /// Aggregate block-type / code-language statistics across every /// document currently loaded in memory. pub fn stats(&self) -> StoreStats {