Skip to content

feat(orm): string matching in Query#605

Open
ElijahAhianyo wants to merge 25 commits into
masterfrom
elijah/like-query
Open

feat(orm): string matching in Query#605
ElijahAhianyo wants to merge 25 commits into
masterfrom
elijah/like-query

Conversation

@ElijahAhianyo

@ElijahAhianyo ElijahAhianyo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds basic string pattern matching to queries. This is equivalent to LIKE, ILIKE(postgres specific) and GLOB(sqlite specific) variants on database engines
The expressions supported are:

  • contains
  • starts_with
  • ends_with
  • raw_like

and with an i-prefixed case-insensitive counterpart of each:

  • icontains
  • istarts_with
  • iends_with
  • iraw_like

Usage:

use cot::db::{Database, model, query};

#[model]
#[derive(Debug, Clone)]
struct Customer {
    #[model(primary_key)]
    id: i32,
    full_name: String,
}

let _ = query!(Customer, $full_name.contains("Doe"));
let _ = query!(Customer, $full_name.icontains("doe"));
let _ = query!(Customer, $full_name.starts_with("Jon"));
let _ = query!(Customer, $full_name.istarts_with("jon"));
let _ = query!(Customer, $full_name.ends_with("Doe"));
let _ = query!(Customer, $full_name.iends_with("doe"));

raw_like/iraw_like are escape hatches that accept a raw glob pattern (* for zero-or-more characters, ? for exactly one, \ to escape a wildcard) rather than a plain literal, for shapes the other four methods can't express, such as a fixed-width or middle-of-string match:

use cot::db::{model, query};

let _ = query!(Customer, $full_name.raw_like("J?n Do*"));
let _ = query!(Customer, $full_name.iraw_like("j*n ??e"));

These methods can be combined with Boolean and comparison operators like any other expression:

use cot::db::{model, query};

let _ = query!(Customer, $full_name.contains("Doe") && $is_active == true);

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Refactor / cleanup
  • Performance improvement
  • Other (describe above)

@github-actions github-actions Bot added C-lib Crate: cot (main library crate) C-macros Crate: cot-macros labels Jul 2, 2026
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Projectcot
Branchelijah/like-query
Testbedgithub-ubuntu-latest
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
microseconds (µs)
(Result Δ%)
Upper Boundary
microseconds (µs)
(Limit %)
empty_router/empty_router📈 view plot
🚷 view threshold
9,714.70 µs
(+33.97%)Baseline: 7,251.66 µs
12,548.18 µs
(77.42%)
json_api/json_api📈 view plot
🚷 view threshold
759.06 µs
(-29.74%)Baseline: 1,080.30 µs
1,366.69 µs
(55.54%)
nested_routers/nested_routers📈 view plot
🚷 view threshold
721.94 µs
(-28.26%)Baseline: 1,006.29 µs
1,243.37 µs
(58.06%)
single_root_route/single_root_route📈 view plot
🚷 view threshold
676.34 µs
(-30.25%)Baseline: 969.60 µs
1,206.40 µs
(56.06%)
single_root_route_burst/single_root_route_burst📈 view plot
🚷 view threshold
11,825.00 µs
(-31.93%)Baseline: 17,371.55 µs
21,686.46 µs
(54.53%)
🐰 View full continuous benchmarking report in Bencher

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.89516% with 65 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cot/src/db/query/expr.rs 73.98% 37 Missing and 8 partials ⚠️
cot/src/db.rs 68.42% 0 Missing and 6 partials ⚠️
cot-macros/src/query.rs 96.85% 4 Missing and 1 partial ⚠️
cot/src/db/query.rs 69.23% 2 Missing and 2 partials ⚠️
cot/src/db/query/expr/like.rs 94.44% 2 Missing and 2 partials ⚠️
cot/src/db/impl_sqlite.rs 97.14% 0 Missing and 1 partial ⚠️
Flag Coverage Δ
rust 90.31% <86.89%> (+0.05%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
cot-macros/src/model.rs 96.36% <ø> (ø)
cot/src/db/impl_mysql.rs 100.00% <100.00%> (ø)
cot/src/db/impl_postgres.rs 100.00% <100.00%> (ø)
cot/src/db/impl_sqlite.rs 96.29% <97.14%> (+1.55%) ⬆️
cot/src/db/query.rs 85.00% <69.23%> (+1.41%) ⬆️
cot/src/db/query/expr/like.rs 94.44% <94.44%> (ø)
cot-macros/src/query.rs 92.51% <96.85%> (+19.01%) ⬆️
cot/src/db.rs 87.50% <68.42%> (+0.66%) ⬆️
cot/src/db/query/expr.rs 73.98% <73.98%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@seqre seqre changed the title Query add .contains, .starts_with, .ends_with` proof of concept Query add .contains, .starts_with, .ends_with proof of concept Jul 2, 2026
- improve code quality
- Add docs
- refactor/move expr into a module since db/query file has grown bigger now
@github-actions github-actions Bot added the A-docs Area: Documentation label Jul 3, 2026
@ElijahAhianyo ElijahAhianyo changed the title Query add .contains, .starts_with, .ends_with proof of concept Query add .contains, .starts_with, .ends_with, .raw_like Jul 3, 2026
@ElijahAhianyo
ElijahAhianyo marked this pull request as ready for review July 5, 2026 23:05
@ElijahAhianyo ElijahAhianyo changed the title Query add .contains, .starts_with, .ends_with, .raw_like Query add pattern matching on Field Reference types Jul 5, 2026
@ElijahAhianyo
ElijahAhianyo requested review from m4tx and seqre July 5, 2026 23:30
Comment thread cot/src/db/impl_mysql.rs
Comment thread cot/src/db/query/expr/like.rs
Comment thread docs/databases/queries.md

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have some docs added to the guide about this cool new feature 😉

Comment thread cot/src/db/query/expr.rs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, extracting Expr to a separate module is probably a good idea, given how much code it is.

Comment thread cot/src/db/query/expr/like.rs Outdated
fn iraw_like<V: Into<String>>(self, other: V) -> Expr;
}

impl<T: ToDbFieldValue + 'static> ExprLike<T> for FieldRef<T> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering - should we implement this for any T: ToDbFieldValue, or maybe we should somehow restrict this to String-only columns? (similarly to how we implement ExprMul, for instance) What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, I think we definitely need to restrict this to String-only columns. We need a way to tell that a current Field is a Text/String Field. I see we implement DatabaseField for every DB type where we specify the ColumnType, but I'm not sure how we can use that trait as bounds here. My intuition here is to define a TextField marker trait and implement that for all DbFieldValue types that turn into text:

pub trait TextField: DbFieldValue {}

impl TextField for String {}
impl TextField for common_types::Email {}
impl TextField for common_types::Url {}
impl <const LIMIT: u32> TextField for LimitedString<LIMIT>{}

impl <T: TextField + 'static> ExprLike <T> for FieldRef<T> {
...
}

@m4tx m4tx changed the title Query add pattern matching on Field Reference types feat(orm): string matching in Query Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-docs Area: Documentation C-lib Crate: cot (main library crate) C-macros Crate: cot-macros

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants