A powerful search query language parser with SQL output support, inspired by Elasticsearch and Tantivy query syntax.
- Multi-pass recursive descent parser
- Comprehensive type checking and validation
- Rich error reporting with position information
- SQL output with support for multiple search strategies
- React component with Monaco editor integration
- Complete test coverage
cd search-input-query-demo
npm install
npm run dev
npm install search-input-query-parser
import {
parseSearchInputQuery,
type FieldSchema
} from 'search-input-query-parser';
// Define your field schemas
const schemas: FieldSchema[] = [
{ name: 'title', type: 'string' },
{ name: 'price', type: 'number' },
{ name: 'date', type: 'date' },
{ name: 'in_stock', type: 'boolean' }
];
// Parse a search query
const query = 'title:"winter boots" AND price:>100';
const result = parseSearchInputQuery(query, schemas);
if (result.type === 'SEARCH_QUERY') {
// Handle successful parse where the expression is in AST format
console.log(result.expression);
} else {
// Handle validation errors
console.log(result.errors);
}
npm install search-input-query-react
import { SearchInputQuery, EditorTheme } from 'search-input-query-react';
import type { Expression, FieldSchema, ValidationError } from 'search-input-query-parser';
// Define your schemas
const schemas: FieldSchema[] = [
{ name: 'title', type: 'string' },
{ name: 'price', type: 'number' },
{ name: 'date', type: 'date' },
{ name: 'in_stock', type: 'boolean' }
];
// Define your editor theme (optional, defaults provided)
const editorTheme: EditorTheme = {
base: 'vs',
inherit: true,
rules: [
{ token: 'keyword', foreground: '#794938', fontStyle: 'bold' },
{ token: 'field', foreground: '#234A97', fontStyle: 'bold' },
{ token: 'value', foreground: '#0B6125' }
// Add more token rules as needed
],
colors: {
'editor.foreground': '#24292F',
'editor.background': '#FFFFFF',
// Add more color settings as needed
}
};
function SearchComponent() {
const handleSearchResult = (result: {
expression: Expression | null;
parsedResult: string;
errors: ValidationError[];
}) => {
if (result.errors.length === 0) {
// Handle successful parse
console.log('Parsed expression:', result.expression);
console.log('Stringified result:', result.parsedResult);
} else {
// Handle validation errors
console.log('Parse errors:', result.errors);
}
};
return (
<SearchInputQuery
schemas={schemas}
onSearchResult={handleSearchResult}
editorTheme={editorTheme}
/>
);
}
Prop | Type | Required | Description |
---|---|---|---|
schemas |
FieldSchema[] |
Yes | Array of field definitions for validation and auto-completion |
onSearchResult |
(result: SearchResult) => void |
Yes | Callback fired on query changes with parse results |
editorTheme |
EditorTheme |
No | Monaco editor theme configuration |
- Monaco editor integration with syntax highlighting
- Real-time validation and error highlighting
- Auto-completion for fields and operators
- Support for all query syntax features
- Customizable editor theme
- Error decorations with hover messages
- Auto-closing quotes and brackets
The React component provides a rich editing experience with immediate feedback on query validity. It handles all parsing and validation internally, providing clean results through the onSearchResult
callback.
The parser can convert search queries into SQL WHERE clauses using three different search strategies:
- ILIKE - Case-insensitive pattern matching
- tsvector - PostgreSQL full-text search
- ParadeDB - BM25-based full-text search
import {
searchStringToIlikeSql,
searchStringToTsVectorSql,
searchStringToParadeDBSql,
type FieldSchema
} from 'search-input-query-parser';
// Define searchable columns and schemas
const searchableColumns = ['title', 'description'];
const schemas: FieldSchema[] = [
{ name: 'title', type: 'string' },
{ name: 'price', type: 'number' },
{ name: 'date', type: 'date' },
{ name: 'in_stock', type: 'boolean' }
];
// Convert a search query to SQL
const query = 'winter boots AND price:>100';
// Using ILIKE (case-insensitive pattern matching)
const ilikeResult = searchStringToIlikeSql(query, searchableColumns, schemas);
// Result:
// {
// text: "((lower(title) LIKE lower($1) OR lower(description) LIKE lower($1)) AND price > $2)",
// values: ["%winter boots%", 100]
// }
// Using tsvector (PostgreSQL full-text search)
const tsvectorResult = searchStringToTsVectorSql(query, searchableColumns, schemas);
// Result:
// {
// text: "((to_tsvector('english', title) || to_tsvector('english', description)) @@ plainto_tsquery('english', $1) AND price > $2)",
// values: ["winter boots", 100]
// }
// Using ParadeDB (BM25 search)
const paradedbResult = searchStringToParadeDBSql(query, searchableColumns, schemas);
// Result:
// {
// text: "((title @@@ $1 OR description @@@ $1) AND price @@@ '>' || $2)",
// values: ['"winter boots"', 100]
// }
Feature | ILIKE | tsvector | ParadeDB |
---|---|---|---|
Case Sensitivity | Case-insensitive | Case-insensitive | Case-sensitive |
Pattern Matching | Simple wildcards | Language-aware tokens | BM25 ranking |
Performance | Slower on large datasets | Fast with proper indexes | Fast with proper indexes |
Setup Required | None | PostgreSQL extension | ParadeDB extension |
Best For | Simple searches, small datasets | Advanced text search | Relevance-based search |
// Common options for all search types
interface SearchQueryOptions {
language?: string; // Language for text analysis (default: 'english')
}
// Example with options
const result = searchStringToTsVectorSql(
query,
searchableColumns,
schemas,
{
language: 'spanish'
}
);
The converters return objects with text
(the WHERE clause) and values
(the parameter values):
import { searchStringToIlikeSql } from 'search-input-query-parser';
const base = 'SELECT * FROM products';
const { text, values } = searchStringToIlikeSql(query, searchableColumns, schemas);
const fullQuery = `${base} WHERE ${text}`;
// Using with node-postgres
const result = await client.query(fullQuery, values);
// Using with Prisma
const result = await prisma.$queryRaw`${Prisma.raw(base)} WHERE ${Prisma.raw(text)}`;
Category | Expression Pattern | Example | Description |
---|---|---|---|
Basic Terms | |||
Single Term | term |
boots |
Matches documents containing the term |
Quoted Term | "term with spaces" |
"winter boots" |
Matches exact phrase |
Wildcard | term* |
boot* |
Matches terms starting with the prefix |
Field Values | |||
Basic Field | field:value |
color:red |
Matches specific field value |
Quoted Field | field:"value with spaces" |
category:"winter boots" |
Matches exact phrase in field |
Field Wildcard | field:value* |
title:winter* |
Matches field values starting with prefix |
Numeric Ranges | |||
Greater Than | field:>number |
price:>100 |
Values greater than |
Greater Equal | field:>=number |
price:>=100 |
Values greater than or equal |
Less Than | field:<number |
price:<50 |
Values less than |
Less Equal | field:<=number |
price:<=50 |
Values less than or equal |
Between Range | field:min..max |
price:10..20 |
Values in inclusive range |
Open Range Up | field:number.. |
price:10.. |
Values greater than or equal |
Open Range Down | field:..number |
price:..20 |
Values less than or equal |
Date Ranges | |||
Date Equals | field:YYYY-MM-DD |
date:2024-01-01 |
Exact date match |
Date Range | field:YYYY-MM-DD..YYYY-MM-DD |
date:2024-01-01..2024-12-31 |
Dates in inclusive range |
Date Compare | field:>YYYY-MM-DD |
date:>2024-01-01 |
Dates after specified date |
Logical Operators | |||
AND | expr1 AND expr2 |
boots AND leather |
Both expressions must match |
OR | expr1 OR expr2 |
boots OR sandals |
Either expression must match |
NOT | NOT expr |
NOT leather |
Negates the expression |
Implicit AND | expr1 expr2 |
boots leather |
Space between terms implies AND |
Grouping | |||
Parentheses | (expr1 OR expr2) AND expr3 |
(boots OR sandals) AND leather |
Groups expressions for precedence |
IN Operator | |||
IN List | field:IN(val1,val2) |
status:IN(active,pending) |
Field matches any listed value |
Quoted IN | field:IN("val 1","val 2") |
category:IN("winter boots","summer shoes") |
IN with quoted values |
Notes:
- All operators (AND, OR, NOT, IN) are case-insensitive
- Field names are case-insensitive
- Field names must start with a letter and can contain letters, numbers, underscores, and hyphens
- Quotes can be escaped within quoted strings using backslash (
\"
) - Multiple expressions can be combined using logical operators and parentheses
- The parser follows standard operator precedence: NOT > AND > OR
- Whitespace is ignored around operators and parentheses