Skip to content

Repository files navigation

rsql_parser

A Ruby parser library for RSQL and FIQL query expressions. Parses query strings into structured Ruby hashes that can be used to build database queries, filter collections, or power search APIs.

  • FIQL (Feed Item Query Language): RFC draft
  • RSQL: a superset of FIQL with additional convenience syntax

Installation

Add to your Gemfile:

gem'rsql_parser'

Or install directly:

gem install rsql_parser

Quick Start

require'rsql_parser'result=RsqlParser.parse('name=="Kill Bill";year=gt=2003')# => {# type: :COMBINATION,# operator: :AND,# lhs: { type: :CONSTRAINT, selector: "name", comparison: "==", argument: "Kill Bill" },# rhs: { type: :CONSTRAINT, selector: "year", comparison: "=gt=", argument: "2003" }# }

Return Value Structure

Every call to RsqlParser.parse returns a node hash. There are two node types:

:CONSTRAINT — a single condition

KeyTypeDescription
:typeSymbolAlways :CONSTRAINT
:selectorStringThe field/attribute name
:comparisonStringThe comparison operator
:argumentString or ArrayThe value(s) to compare against
RsqlParser.parse('year==2003')# => { type: :CONSTRAINT, selector: "year", comparison: "==", argument: "2003" }

:COMBINATION — two conditions joined by a logical operator

KeyTypeDescription
:typeSymbolAlways :COMBINATION
:operatorSymbol:AND or :OR
:lhsHashLeft-hand side node
:rhsHashRight-hand side node
RsqlParser.parse('a==1;b==2')# => {# type: :COMBINATION,# operator: :AND,# lhs: { type: :CONSTRAINT, selector: "a", comparison: "==", argument: "1" },# rhs: { type: :CONSTRAINT, selector: "b", comparison: "==", argument: "2" }# }

Syntax Reference

Selectors

A selector is a field name consisting of unreserved characters: letters, digits, and -._~:.

name
created_at
user.email
http://schema.org/name

Comparison Operators

FIQL / RSQL operators

OperatorMeaningExample
==Equalname==Alice
!=Not equalstatus!=inactive
=gt=Greater thanyear=gt=2000
=gte=Greater than or equalyear=gte=2000
=lt=Less thanprice=lt=100
=lte=Less than or equalprice=lte=100
=in=In a setstatus=in=(a,b,c)
=out=Not in a setstatus=out=(x,y)
=custom=Any custom operatorfield=op=value

Custom FIQL operators follow the pattern =[a-z!]*= — any lowercase letters or ! between two = signs.

Simplified comparison operators

OperatorMeaningExample
>Greater thanyear>2000
>=Greater than or equalyear>=2000
<Less thanprice<100
<=Less than or equalprice<=100

Logical Operators

Conditions can be combined with AND and OR. AND has higher precedence than OR.

Symbol syntax

SymbolOperatorExample
;ANDa==1;b==2
,ORa==1,b==2

Keyword syntax (case-insensitive)

KeywordOperatorExample
and / ANDANDa==1 and b==2
or / ORORa==1 or b==2

Symbol and keyword syntax can be mixed freely. Whitespace around keywords is ignored.

Arguments

Unquoted values

Sequences of unreserved characters ([a-zA-Z0-9\-._~:]):

year==2003
status==active
date==2018-09-01T12:14:28Z

Single-quoted strings

Allows spaces, semicolons, commas, and double quotes inside the value. Use \' to include a literal single quote:

name=='Kill;"Bill"'
tag=='it\'s fine'

Double-quoted strings

Allows spaces, semicolons, commas, and single quotes inside the value. Use \" to include a literal double quote:

name=="Kill Bill"
title=="She said \"hello\""

Array arguments

A parenthesised, comma-separated list. Used with operators like =in=:

status=in=(active,pending,review)
name=in=("Kill Bill","Pulp Fiction")

The :argument key will contain a Ruby Array instead of a String:

RsqlParser.parse('genre=in=(sci-fi,action)')# => { type: :CONSTRAINT, selector: "genre", comparison: "=in=",# argument: ["sci-fi", "action"] }

Grouping

Parentheses override the default AND-before-OR precedence:

# Without grouping: (a AND b) OR cRsqlParser.parse('a==1;b==2,c==3')# With grouping: a AND (b OR c)RsqlParser.parse('a==1;(b==2,c==3)')

Examples

require'rsql_parser'# Single constraintRsqlParser.parse('year==2003')# => { type: :CONSTRAINT, selector: "year", comparison: "==", argument: "2003" }# Simplified comparison syntaxRsqlParser.parse('price<=99')# => { type: :CONSTRAINT, selector: "price", comparison: "<=", argument: "99" }# AND combination (semicolon and keyword are equivalent)RsqlParser.parse('name=="Kill Bill" and year=gt=2003')RsqlParser.parse('name=="Kill Bill";year=gt=2003')# OR combinationRsqlParser.parse('status==active or status==pending')RsqlParser.parse('status==active,status==pending')# Array argumentRsqlParser.parse("genre=in=(sci-fi,action);year>2000")# => {# type: :COMBINATION,# operator: :AND,# lhs: { type: :CONSTRAINT, selector: "genre", comparison: "=in=",# argument: ["sci-fi", "action"] },# rhs: { type: :CONSTRAINT, selector: "year", comparison: ">",# argument: "2000" }# }# Chained AND — right-associative treeRsqlParser.parse('a=eq=b;c=ne=d;e=gt=f')# => {# type: :COMBINATION, operator: :AND,# lhs: { type: :CONSTRAINT, selector: "a", comparison: "=eq=", argument: "b" },# rhs: {# type: :COMBINATION, operator: :AND,# lhs: { type: :CONSTRAINT, selector: "c", comparison: "=ne=", argument: "d" },# rhs: { type: :CONSTRAINT, selector: "e", comparison: "=gt=", argument: "f" }# }# }# Grouping to change precedenceRsqlParser.parse('a=eq=b;(c=ne=d,e=gt=f)')# => {# type: :COMBINATION, operator: :AND,# lhs: { type: :CONSTRAINT, selector: "a", comparison: "=eq=", argument: "b" },# rhs: {# type: :COMBINATION, operator: :OR,# lhs: { type: :CONSTRAINT, selector: "c", comparison: "=ne=", argument: "d" },# rhs: { type: :CONSTRAINT, selector: "e", comparison: "=gt=", argument: "f" }# }# }# Escaped quotes inside stringsRsqlParser.parse('title=="She said \"hello\""')# => { type: :CONSTRAINT, selector: "title", comparison: "==",# argument: 'She said "hello"' }

Operator Precedence

From highest to lowest:

  1. Parentheses ( )
  2. AND — ; or and
  3. OR — , or or

Requirements

  • Ruby >= 2.7.0
  • racc ~> 1.8

Development

# Run tests
rake test# Regenerate the lexer after editing lib/rsql_parser/lexer.rex
ruby -roedipus_lex -e " lex = OedipusLex.new lex.parse_file('lib/rsql_parser/lexer.rex') File.write('lib/rsql_parser/lexer.rex.rb', lex.generate)"

Development dependencies: oedipus_lex ~> 2.6, minitest ~> 5.21, rake ~> 13.0.

Contributing

Open a pull request with your changes and a corresponding test.

License

MIT © Ekzo

About

RSQL/FIQL parser for Ruby

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages