Skip to content

Complete and thoroughly tested parser for RSQL/FIQL written in Typescript(Javascript). Tool for filtering array of objects using RSQL/FIQL. Built with zero dependencies.

License

Notifications You must be signed in to change notification settings

mw-experts/rsql

Repository files navigation

RSQL / FIQL tools

Complete and thoroughly tested parser for RSQL/FIQL written in Typescript(Javascript). Tools to work with RSQL: matcher, filter, converter to SQL. Built with zero dependencies.

Installing

npm install @mw-experts/rsql

or

yarn add @mw-experts/rsql

Usage

Import library

Import library as CommonJS module:

const rsql = require('@mw-experts/rsql');

Import library as ES6 module:

import { RsqlMatcher } from '@mw-experts/rsql';
import { RsqlFilter } from '@mw-experts/rsql';
import { RsqlToSqlConverter } from '@mw-experts/rsql';

or

import * as rsql from '@mw-experts/rsql';

Import library as standalone script in a browser:

<script src="../node_modules/@mw-experts/rsql/dist/rsql-browser.js"></script>

About RSQL / FIQL

RSQL is a query language for parametrized filtering of entries in RESTful APIs.

It’s based on FIQL (Feed Item Query Language) an URI-friendly syntax for expressing filters across the entries in an Atom Feed.

The simplicity of RSQL and its capability to express complex queries in a compact and HTTP URI-friendly way makes it a good candidate for becoming a generic query language for searching REST endpoints.

For example, you can query your resource like this:

/movies?search=name=="Kill Bill";year=gt=2003

or

/movies?search=director.lastName==Nolan and year>=2000.

RSQL introduces simple and composite operators which can be used to build basic and complex queries.

Basic operators:

Basic Operator Description
== Equal To
!= Not Equal To
=gt= Greater Than
> Greater Than
=ge= Greater Or Equal To
>= Greater Or Equal To
=lt= Less Than
< Less Than
=le= Less Or Equal To
<= Less Or Equal To
=in= In
=out= Not in
=includes-all= Includes all
=includes-one= Includes one

These operators can be used to do all sort of simple queries, for example:

  • name==Fero: find all people whose name is Fero
  • street!=Darna: find all people who do not live at Darna
  • age=gt=10: find all people older than 10 (exclusive)
  • age>10: find all people older than 10 (exclusive)
  • age=ge=10: find all people older than 10 (inclusive)
  • age>=10: find all people older than 10 (inclusive)
  • house=lt=3: find all people who have less than 3 houses
  • house<3: find all people who have less than 3 houses
  • house=le=3: find all people who have less than or 3 houses
  • house<=3: find all people who have less than or 3 houses
  • name=in=(Fero,Jane) find all people whose name is Fero or Jane
  • name=out=(Alex,Mike) find all people whose name is not Alex or Mike
  • list=includes-all=(item1,item2) find all records where list is array and includes item1 and item2
  • list=includes-one=(item1,item2) find all records where list is array and includes item1 or item2

Composite operators:

Composite Operator Description
; Logical AND
and Logical AND
, Logical OR
or Logical OR

These operators can be used to join the simple queries and build more involved queries which can be as complex as required. Here are some examples:

  • age=gt=10;age=lt=20: find all people older than 10 and younger than 20
  • age=gt=10 and age=lt=20: find all people older than 10 and younger than 20
  • age=lt=5,age=gt=30: find all people younger than 5 or older than 30
  • age<5 or age>30: find all people younger than 5 or older than 30
  • age=gt=10;age=lt=20;name=in=(Fero,Jane): find all people older than 10 and younger than 20 with names either Fero or Jane.

Fields and Values

Fields can only consist of next regexp symbols: [\w-.] (A-Za-z0-9_-.)

  • field.name==value
  • field-name==value
  • field_name==value
  • FieldName==value
  • FIELD_NAME_777==value

Values can only consist of next regexp symbols:

  • in double quotes - space, any unicode letter, any unicode number, _, -, ., ', (, )
  • in single quotes - space, any unicode letter, any unicode number, _, -, ., ", (, )
  • without quotes - any unicode letter, any unicode number, _, -, .
  • with == or != operators you can also use asterisk * as a wildcard

Ordering

By default, operators evaluated from left to right. However, a parenthesized expression can be used to change the precedence.

  • age=lt=20;(name==Fero,name==Jane): find all people younger than 20 with names either Fero or Jane.

Convert RSQL to SQL

const rsqlStr = 'name=="Kill Bill",year=ge=2000';
let result;

try {
  result = rsql.RsqlToSqlConverter.getInstance().convert(rsqlStr);
} catch (e) {
  console.warn(e);
}

console.log(result);

// will output:
// ("name" = 'Kill Bill' OR "year" >= '2000')

You can have different field names wrapper, for example if you use Mysql. Just pass a second argument to convert method like this:

const rsqlStr = 'name=="Kill Bill",year=ge=2000';
let result;

try {
  result = rsql.RsqlToSqlConverter.getInstance().convert(rsqlStr, '`');
} catch (e) {
  console.warn(e);
}

console.log(result);

// will output:
// (`name` = 'Kill Bill' OR `year` >= '2000')
  • "=includes-all=", "=includes-one=" operators are not supported in RsqlToSqlConverter
  • Not included in browser bundle

Filter array of objects

const data = [
  { name: 'Kill Bill', year: 2006 },
  { name: 'Terminator', year: 1998 },
  { name: 'Matrix', year: 2000 },
];

const rsqlStr = 'name=="Kill Bill",year=ge=2000';
let result = [];

try {
  result = rsql.RsqlFilter.getInstance().filter(rsqlStr, data);
} catch (e) {
  console.warn(e);
}

console.log(result);

// will output:
// [
//  { name: 'Kill Bill', year: 2006 },
//  { name: 'Matrix', year: 2000 },
// ]

You can ask for deep nested properties using dot separator - someObj.prop, or array notation - someList[0].prop:

const data = [
    {
        deep: {
            nested: {
                field: 777
            }
        },
        list: [1, 2, 3]
    }
]

const rsql = 'deep.nested.field==777';
const rsql = 'list[1]==2';

Match item

const data = { name: 'Matrix', year: 2000 };

const rsqlStr = 'name=="Kill Bill";year=ge=2000';

try {
  result = rsql.RsqlMatcher.getInstance().match(rsqlStr, data);
} catch (e) {
  console.warn(e);
}

console.log(result);

// will output: false

Value part of matching expression also can be used as path to value, example:

const data = [
    {
        deep: {
            nested: {
                field: 2
            }
        },
        list: [1, 2, 3]
    }
]

const rsqlStr = 'deep.nested.field==list[1]';

try {
  result = rsql.RsqlMatcher.getInstance().match(rsqlStr, data);
} catch (e) {
  console.warn(e);
}

console.log(result);

// will output: true

Match many items

typescript version:

const rsqlStr = 'name=="Kill Bill";year=ge=2000';

const tokens: Token<RsqlTokenType>[] = RsqlTokenizer.getInstance().tokenize(rsqlStr);
const ast: RsqlAstRootNode = RsqlParser.getInstance().parse(tokens);
const matcher: RsqlMatcher = RsqlMatcher.getInstance();

try {
  result1 = matcher.matchWithPreparedAst(ast, { name: 'Matrix', year: 2000 });
  result2 = matcher.matchWithPreparedAst(ast, { name: 'Kill Bill', year: 2021 });
} catch (e) {
  console.warn(e);
}

console.log(result1);
// will output: false

console.log(result2);
// will output: true

javascript version:

const rsqlStr = 'name=="Kill Bill";year=ge=2000';

const tokens = rsql.RsqlTokenizer.getInstance().tokenize(rsqlStr);
const ast = rsql.RsqlParser.getInstance().parse(tokens);
const matcher = rsql.RsqlMatcher.getInstance();

try {
  result1 = matcher.matchWithPreparedAst(ast, { name: 'Matrix', year: 2000 });
  result2 = matcher.matchWithPreparedAst(ast, { name: 'Kill Bill', year: 2021 });
} catch (e) {
  console.warn(e);
}

console.log(result1);
// will output: false

console.log(result2);
// will output: true

Filter and Matcher is case-insensitive

  • name==Marina and name==marina will give the same results - Marina, marina

Usage of wildcard

You can use wildcard * in == and != operators. Example:

  • name==Ma* find all items where name starts from Ma - Marina, Maxim, Maria
  • value==*de* find all items where value starts and ends with any symbols - Made, abcdefg

Comparison rules:

  • "==", "!=" before comparison data converts to string
  • "=gt=", ">", "=ge=", ">=", "=lt=", "<", "=le=", "<=" before comparison data converts to number
  • "=in=", "=out=" before comparison data converts to string
  • "=includes-all=", "=includes-one=" before comparison data converts to array of strings

Authors

License

This project is licensed under the MIT License - see the LICENSE.md file for details

About

Complete and thoroughly tested parser for RSQL/FIQL written in Typescript(Javascript). Tool for filtering array of objects using RSQL/FIQL. Built with zero dependencies.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •