josx / ra-data-feathers

A feathers rest client for react-admin
MIT License
157 stars 58 forks source link

[Feature Request] `q` for searches #175

Open strarsis opened 2 years ago

strarsis commented 2 years ago

react-admin often uses q as full text search parameter. Currently FeathersJS will try to use q as column (which usually doesn't exist in the table) and report a query error.

How can this functionality be added? Must the data provider handle this or a hook in FeathersJS app?

Edit: Just using source="fieldName[$like]" sadly won't work as the field name is automatically escaped (probably already in react-admin), also the value is not automatically wrapped in % for an open start + end of string.

strarsis commented 2 years ago

Currently I use this server-side (FeathersJS) hook to achieve the functionality:

import { HookContext } from '@feathersjs/feathers';
const withSearch = (sourceField: string = 'q', targetField: string = 'name', caseSensitive: boolean = false) => {
  return async (context: HookContext) => {
    if (context.type === 'before') {
      if (context?.params?.query && context.params.query[sourceField]) {
        // convert to like field (KnexJS FeathersJS Adapter)
        // @see https://github.com/feathersjs-ecosystem/feathers-knex/blob/master/test/index.test.js#L205
        const value = context.params.query[sourceField];

        // Note: Some databases like SQLite don't support `$ilike` (case-insensitive `$like`)...
        const targetOp = '$like';

        // ... hence internally handle case sensitivity checks
        const targetValue = caseSensitive ? value : value.toLowerCase();

        // set new parameter that is understood by KnexJS FeathersJS Adapter
        // e.g. `fieldName[$like] = '%value%'`
        context.params.query[targetField] = {
          [targetOp]: `\%${targetValue}\%`, // `%[...]%` for open text start + end
        };

        // clean up old field from query (otherwise causes query error (non-existing column))
        delete context.params.query[sourceField];

        return context;
      }
    }

  }
}
export default withSearch;
const withSearchHook = withSearch('q', 'name');
// [...]
export default {
  before: {
    // [...]
    find: [
      withSearchHook,
    ],
    // [...]
    get: [
      withSearchHook,
    ],
    // [...]
// [...]

So for the GET request URL https://localhost:3030/items?q=test the query q=TeSt is rewritten to the new query `name[$like] = '%test%'.

The old q=TeSt query is removed as it causes a SQL error (column doesn't exist) or is somehow otherwise excluded anyway.

This results in a full-text search in the name field (note: in that specific field only, it is not a full-text search for the whole record, that would be something different) (without case-sensitivity (false)).

josx commented 2 years ago

Did you try customQueryOperator as an option?

https://github.com/josx/ra-data-feathers#data-provider-restclient

strarsis commented 2 years ago

@josx: Would customQueryOperators: [ '$like' ] allow react-admin to do those searches as full text search in the name field? SQLite as backend doesn't support $ilike (case-insensitive searches) and the search query has to be surrounded with % to allow it to match partially. The knex FeathersJS adapter is used.

The customQueryOperator option indeed looks like it would offer this functionality and it would be great to just pass a parameter to get it.

josx commented 2 years ago

customQueryOperator is operator to be use on filter key when quering feathers. Allows to use custom operators from various feathers-database-adapters. Any params.filter included in custom operators will be sent to feathers.

Please check tests https://github.com/josx/ra-data-feathers/blob/master/test/restClient.spec.js#L293