Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions docs/content/2.api/3.query/where-like.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: 'whereLike()'
description: 'Add a where clause matching a SQL LIKE style pattern to the query.'
---

# `whereLike()`

Filters records with a SQL `LIKE` style pattern. `%` matches any number of characters, `_` matches exactly one character. Matching is case insensitive by default — pass `true` as third argument for case sensitive matching.

## Usage

````ts
import { useRepo } from 'pinia-orm'
import User from './models/User'

const userRepo = useRepo(User)

// All users whose name contains "john" (case insensitive).
userRepo.whereLike('name', '%john%').get()

// All users whose name starts with "John" (case sensitive).
userRepo.whereLike('name', 'John%', true).get()

// Single character wildcard: matches "John Doe" and "Jahn Doe".
userRepo.whereLike('name', 'J_hn Doe').get()

// Combine with other where clauses.
userRepo.where('active', true).orWhereLike('name', 'jane%').get()
````

## Typescript Declarations

````ts
function whereLike(field: string, value: string | number, caseSensitive = false): Query
function orWhereLike(field: string, value: string | number, caseSensitive = false): Query
````
16 changes: 16 additions & 0 deletions packages/pinia-orm/src/query/Query.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Pinia } from 'pinia'
import { acceptHMRUpdate } from 'pinia'
import {
compareLike,
compareWithOperator,
generateKey,
groupBy,
Expand Down Expand Up @@ -289,6 +290,21 @@ export class Query<M extends Model = Model> {
return this.where(query => query[field as keyof Model] != null)
}

/**
* Add a "where like" clause to the query. The value may contain `%` as
* a wildcard for any number of characters and `_` for a single character.
*/
whereLike (field: string, value: string | number, caseSensitive = false): this {
return this.where(query => compareLike(query[field as keyof Model], value, caseSensitive))
}

/**
* Add an "or where like" clause to the query.
*/
orWhereLike (field: string, value: string | number, caseSensitive = false): this {
return this.orWhere(query => compareLike(query[field as keyof Model], value, caseSensitive))
}

/**
* Add a "where has" clause to the query.
*/
Expand Down
9 changes: 9 additions & 0 deletions packages/pinia-orm/src/repository/Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ export interface Repository<M extends Model = Model> {
* Add a where clause to get all results where `field` is not null
*/
whereNotNull (field: string): Query<M>
/**
* Add a where clause where `field` matches a SQL LIKE style pattern.
* `%` matches any number of characters, `_` a single character.
*/
whereLike (field: string, value: string | number, caseSensitive?: boolean): Query<M>
/**
* Add an "or where like" clause to the query.
*/
orWhereLike (field: string, value: string | number, caseSensitive?: boolean): Query<M>
/**
* Find the model with the given id.
*/
Expand Down
15 changes: 15 additions & 0 deletions packages/pinia-orm/src/support/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ export function isNullish (value: any): value is undefined | null {
return value === undefined || value === null
}

/**
* Compare a value against a SQL LIKE style pattern where `%` matches any
* sequence of characters and `_` matches a single character.
*/
export function compareLike (value: any, pattern: string | number, caseSensitive = false): boolean {
if (isNullish(value)) { return false }

const source = String(pattern)
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
.replace(/%/g, '[\\s\\S]*')
.replace(/_/g, '[\\s\\S]')

return new RegExp(`^${source}$`, caseSensitive ? '' : 'i').test(String(value))
}

/**
* Check if the given value is a Date object.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { describe, expect, it } from 'vitest'

import { Model, useRepo } from '../../../src'
import { Attr, Num, Str } from '../../../src/decorators'
import { assertInstanceOf, assertModels, fillState } from '../../helpers'

describe('feature/repository/retrieves_where_like', () => {
class User extends Model {
static entity = 'users'

@Attr() id!: any
@Str('') name!: string
@Num(0) age!: number
}

const state = {
users: {
1: { id: 1, name: 'John Doe', age: 30 },
2: { id: 2, name: 'Jane Doe', age: 30 },
3: { id: 3, name: 'johnny walker', age: 20 },
4: { id: 4, name: 'Doe John', age: 40 },
},
}

it('can filter with a contains pattern', () => {
const userRepo = useRepo(User)

fillState(state)

const users = userRepo.whereLike('name', '%john%').get()

const expected = [
{ id: 1, name: 'John Doe', age: 30 },
{ id: 3, name: 'johnny walker', age: 20 },
{ id: 4, name: 'Doe John', age: 40 },
]

expect(users).toHaveLength(3)
assertInstanceOf(users, User)
assertModels(users, expected)
})

it('can filter with a starts with pattern', () => {
const userRepo = useRepo(User)

fillState(state)

const users = userRepo.whereLike('name', 'john%').get()

const expected = [
{ id: 1, name: 'John Doe', age: 30 },
{ id: 3, name: 'johnny walker', age: 20 },
]

expect(users).toHaveLength(2)
assertModels(users, expected)
})

it('matches case sensitive when the flag is set', () => {
const userRepo = useRepo(User)

fillState(state)

const users = userRepo.whereLike('name', 'john%', true).get()

expect(users).toHaveLength(1)
assertModels(users, [{ id: 3, name: 'johnny walker', age: 20 }])
})

it('supports single character wildcards', () => {
const userRepo = useRepo(User)

fillState(state)

const users = userRepo.whereLike('name', 'J_hn Doe').get()

expect(users).toHaveLength(1)
assertModels(users, [{ id: 1, name: 'John Doe', age: 30 }])
})

it('matches without wildcards like an equality check', () => {
const userRepo = useRepo(User)

fillState(state)

const users = userRepo.whereLike('name', 'jane doe').get()

expect(users).toHaveLength(1)
assertModels(users, [{ id: 2, name: 'Jane Doe', age: 30 }])
})

it('can filter with `orWhereLike`', () => {
const userRepo = useRepo(User)

fillState(state)

const users = userRepo.where('age', 40).orWhereLike('name', 'jane%').get()

const expected = [
{ id: 2, name: 'Jane Doe', age: 30 },
{ id: 4, name: 'Doe John', age: 40 },
]

expect(users).toHaveLength(2)
assertModels(users, expected)
})

it('can filter non string fields', () => {
const userRepo = useRepo(User)

fillState(state)

const users = userRepo.whereLike('age', '3%').get()

const expected = [
{ id: 1, name: 'John Doe', age: 30 },
{ id: 2, name: 'Jane Doe', age: 30 },
]

expect(users).toHaveLength(2)
assertModels(users, expected)
})
})
Loading