mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #3244 from omnivore-app/improve-advanced-search
improve advanced search
This commit is contained in:
commit
528163905d
7 changed files with 149 additions and 287 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { LiqeQuery } from '@omnivore/liqe'
|
||||
import { ExpressionToken, LiqeQuery } from '@omnivore/liqe'
|
||||
import { DateTime } from 'luxon'
|
||||
import { DeepPartial, ObjectLiteral } from 'typeorm'
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'
|
||||
|
|
@ -130,7 +130,8 @@ export const sortParamsToSort = (
|
|||
}
|
||||
|
||||
const getColumnName = (field: string) => {
|
||||
switch (field) {
|
||||
const lowerCaseField = field.toLowerCase()
|
||||
switch (lowerCaseField) {
|
||||
case 'language':
|
||||
return 'item_language'
|
||||
case 'subscription':
|
||||
|
|
@ -138,17 +139,17 @@ const getColumnName = (field: string) => {
|
|||
return 'subscription'
|
||||
case 'site':
|
||||
return 'site_name'
|
||||
case 'wordsCount':
|
||||
case 'wordscount':
|
||||
return 'word_count'
|
||||
case 'readPosition':
|
||||
case 'readposition':
|
||||
return 'reading_progress_bottom_percent'
|
||||
case 'saved':
|
||||
case 'read':
|
||||
case 'updated':
|
||||
case 'published':
|
||||
return `${field}_at`
|
||||
return `${lowerCaseField}_at`
|
||||
default:
|
||||
return field
|
||||
return lowerCaseField
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,50 +168,58 @@ export const buildQuery = (
|
|||
return query
|
||||
}
|
||||
|
||||
const serializeImplicitField = (
|
||||
expression: ExpressionToken
|
||||
): string | null => {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression')
|
||||
}
|
||||
|
||||
const value = expression.value?.toString()
|
||||
|
||||
if (value === undefined || value === '') {
|
||||
return null
|
||||
}
|
||||
|
||||
const param = `implicit_field_${parameters.length}`
|
||||
const alias = `rank_${parameters.length}`
|
||||
selects.push({
|
||||
column: `ts_rank_cd(library_item.search_tsv, websearch_to_tsquery('english', :${param}))`,
|
||||
alias,
|
||||
})
|
||||
|
||||
orders.push({ by: alias, order: SortOrder.DESCENDING })
|
||||
|
||||
return escapeQueryWithParameters(
|
||||
`websearch_to_tsquery('english', :${param}) @@ library_item.search_tsv`,
|
||||
{ [param]: value }
|
||||
)
|
||||
}
|
||||
|
||||
const serializeTagExpression = (ast: LiqeQuery): string | null => {
|
||||
if (ast.type !== 'Tag') {
|
||||
throw new Error('Expected a tag expression.')
|
||||
throw new Error('Expected a tag expression')
|
||||
}
|
||||
|
||||
const { field, expression } = ast
|
||||
|
||||
if (field.type === 'ImplicitField') {
|
||||
return serializeImplicitField(expression)
|
||||
} else {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const value = expression.value?.toString()
|
||||
|
||||
if (value === undefined || value === '') {
|
||||
// ignore empty values
|
||||
return null
|
||||
}
|
||||
|
||||
const param = `implicit_field_${parameters.length}`
|
||||
const alias = `rank_${parameters.length}`
|
||||
selects.push({
|
||||
column: `ts_rank_cd(library_item.search_tsv, websearch_to_tsquery('english', :${param}))`,
|
||||
alias,
|
||||
})
|
||||
const value = expression.value?.toString()
|
||||
if (!value) {
|
||||
// ignore empty values
|
||||
return null
|
||||
}
|
||||
|
||||
orders.push({ by: alias, order: SortOrder.DESCENDING })
|
||||
|
||||
return escapeQueryWithParameters(
|
||||
`websearch_to_tsquery('english', :${param}) @@ library_item.search_tsv`,
|
||||
{ [param]: value }
|
||||
)
|
||||
} else {
|
||||
switch (field.name) {
|
||||
switch (field.name.toLowerCase()) {
|
||||
case 'in': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const folder = expression.value?.toString()
|
||||
if (!folder) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
switch (folder) {
|
||||
switch (value.toLowerCase()) {
|
||||
case InFilter.ALL:
|
||||
return null
|
||||
case InFilter.ARCHIVE:
|
||||
|
|
@ -224,7 +233,7 @@ export const buildQuery = (
|
|||
const param = `folder_${parameters.length}`
|
||||
const folderSql = escapeQueryWithParameters(
|
||||
`library_item.folder = :${param}`,
|
||||
{ [param]: folder }
|
||||
{ [param]: value }
|
||||
)
|
||||
sql = `(${sql} AND ${folderSql})`
|
||||
}
|
||||
|
|
@ -235,16 +244,7 @@ export const buildQuery = (
|
|||
}
|
||||
|
||||
case 'is': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const value = expression.value?.toString()
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
switch (value.toLowerCase()) {
|
||||
case ReadFilter.READ:
|
||||
return 'library_item.reading_progress_bottom_percent > 98'
|
||||
case ReadFilter.READING:
|
||||
|
|
@ -256,15 +256,6 @@ export const buildQuery = (
|
|||
}
|
||||
}
|
||||
case 'type': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const value = expression.value?.toString()
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
const param = `type_${parameters.length}`
|
||||
|
||||
return escapeQueryWithParameters(
|
||||
|
|
@ -275,16 +266,7 @@ export const buildQuery = (
|
|||
)
|
||||
}
|
||||
case 'label': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const value = expression.value?.toString()?.toLowerCase()
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
const labels = value.split(',')
|
||||
const labels = value.toLowerCase().split(',')
|
||||
return (
|
||||
labels
|
||||
.map((label) => {
|
||||
|
|
@ -313,15 +295,6 @@ export const buildQuery = (
|
|||
)
|
||||
}
|
||||
case 'sort': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const value = expression.value?.toString()
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
const [sort, sortOrder] = value.split('-')
|
||||
if (sort.toLowerCase() === 'score') {
|
||||
// score is not a column and is handled separately
|
||||
|
|
@ -329,7 +302,7 @@ export const buildQuery = (
|
|||
}
|
||||
|
||||
const order =
|
||||
sortOrder?.toUpperCase() === 'ASC'
|
||||
sortOrder?.toLowerCase() === 'asc'
|
||||
? SortOrder.ASCENDING
|
||||
: SortOrder.DESCENDING
|
||||
|
||||
|
|
@ -338,16 +311,7 @@ export const buildQuery = (
|
|||
return null
|
||||
}
|
||||
case 'has': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const value = expression.value?.toString()
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
switch (value.toLowerCase()) {
|
||||
case HasFilter.HIGHLIGHTS:
|
||||
return "library_item.highlight_annotations <> '{}'"
|
||||
case HasFilter.LABELS:
|
||||
|
|
@ -362,19 +326,10 @@ export const buildQuery = (
|
|||
case 'read':
|
||||
case 'updated':
|
||||
case 'published': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const date = expression.value?.toString()
|
||||
if (!date) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
let startDate: Date | undefined
|
||||
let endDate: Date | undefined
|
||||
// check for special date filters
|
||||
switch (date.toLowerCase()) {
|
||||
switch (value.toLowerCase()) {
|
||||
case 'today':
|
||||
startDate = DateTime.local().startOf('day').toJSDate()
|
||||
break
|
||||
|
|
@ -392,19 +347,19 @@ export const buildQuery = (
|
|||
break
|
||||
default: {
|
||||
// check for date ranges
|
||||
const [start, end] = date.split('..')
|
||||
const [start, end] = value.split('..')
|
||||
// validate date
|
||||
if (start && start !== '*') {
|
||||
startDate = new Date(start)
|
||||
if (isNaN(startDate.getTime())) {
|
||||
throw new Error('Invalid start date.')
|
||||
throw new Error('Invalid start date')
|
||||
}
|
||||
}
|
||||
|
||||
if (end && end !== '*') {
|
||||
endDate = new Date(end)
|
||||
if (isNaN(endDate.getTime())) {
|
||||
throw new Error('Invalid end date.')
|
||||
throw new Error('Invalid end date')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -425,15 +380,6 @@ export const buildQuery = (
|
|||
case 'subscription':
|
||||
case 'rss':
|
||||
case 'language': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const value = expression.value?.toString()
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
const columnName = getColumnName(field.name)
|
||||
const param = `term_${field.name}_${parameters.length}`
|
||||
|
||||
|
|
@ -450,16 +396,6 @@ export const buildQuery = (
|
|||
case 'description':
|
||||
case 'note':
|
||||
case 'site': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
// normalize the term to lower case
|
||||
const value = expression.value?.toString()?.toLowerCase()
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
const columnName = getColumnName(field.name)
|
||||
const param = `match_${field.name}_${parameters.length}`
|
||||
const wildcardParam = `match_${field.name}_wildcard_${parameters.length}`
|
||||
|
|
@ -473,13 +409,9 @@ export const buildQuery = (
|
|||
)
|
||||
}
|
||||
case 'includes': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const ids = expression.value?.toString()?.split(',')
|
||||
const ids = value.split(',')
|
||||
if (!ids || ids.length === 0) {
|
||||
throw new Error('Expected a value.')
|
||||
throw new Error('Expected ids')
|
||||
}
|
||||
|
||||
const param = `includes_${parameters.length}`
|
||||
|
|
@ -488,16 +420,7 @@ export const buildQuery = (
|
|||
[param]: ids,
|
||||
})
|
||||
}
|
||||
case 'recommendedBy': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const value = expression.value?.toString()
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
case 'recommendedby': {
|
||||
const param = `recommendedBy_${parameters.length}`
|
||||
if (value === '*') {
|
||||
// select all if * is provided
|
||||
|
|
@ -512,17 +435,8 @@ export const buildQuery = (
|
|||
)
|
||||
}
|
||||
case 'no': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
const value = expression.value?.toString()
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
let column = ''
|
||||
switch (value) {
|
||||
switch (value.toLowerCase()) {
|
||||
case 'highlight':
|
||||
column = 'highlight_annotations'
|
||||
break
|
||||
|
|
@ -543,41 +457,33 @@ export const buildQuery = (
|
|||
case 'event':
|
||||
// mode is ignored and used only by the frontend
|
||||
return null
|
||||
case 'readPosition':
|
||||
case 'wordsCount': {
|
||||
if (expression.type !== 'LiteralExpression') {
|
||||
throw new Error('Expected a literal expression.')
|
||||
}
|
||||
|
||||
let value = expression.value?.toString()
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
|
||||
case 'readposition':
|
||||
case 'wordscount': {
|
||||
const column = getColumnName(field.name)
|
||||
|
||||
const operatorRegex = /([<>]=?)/
|
||||
const operator = value.match(operatorRegex)?.[0]
|
||||
if (!operator) {
|
||||
throw new Error('Expected a value.')
|
||||
throw new Error('Expected operator')
|
||||
}
|
||||
|
||||
value = value.replace(operatorRegex, '')
|
||||
if (!value) {
|
||||
throw new Error('Expected a value.')
|
||||
}
|
||||
const newValue = value.replace(operatorRegex, '')
|
||||
|
||||
const param = `range_${field.name}_${parameters.length}`
|
||||
|
||||
return escapeQueryWithParameters(
|
||||
`library_item.${column} ${operator} :${param}`,
|
||||
{
|
||||
[param]: parseInt(value, 10),
|
||||
[param]: parseInt(newValue, 10),
|
||||
}
|
||||
)
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unexpected keyword: ${field.name}`)
|
||||
// treat unknown fields as implicit fields
|
||||
return serializeImplicitField({
|
||||
...expression,
|
||||
value: `${field.name}:${value}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -594,7 +500,7 @@ export const buildQuery = (
|
|||
} else if (ast.operator.operator === 'OR') {
|
||||
operator = 'OR'
|
||||
} else {
|
||||
throw new Error('Unexpected operator.')
|
||||
throw new Error('Unexpected operator')
|
||||
}
|
||||
|
||||
const left = serialize(ast.left)
|
||||
|
|
@ -635,7 +541,7 @@ export const buildQuery = (
|
|||
return `(${serialized})`
|
||||
}
|
||||
|
||||
throw new Error('Missing AST type.')
|
||||
return null
|
||||
}
|
||||
|
||||
return serialize(searchQuery)
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ __ -> whitespace_character:+ {% (data) => data[0].length %}
|
|||
|
||||
whitespace_character -> [ \t\n\v\f] {% id %}
|
||||
|
||||
# Numbers
|
||||
decimal -> "-":? [0-9]:+ ("." [0-9]:+):? {%
|
||||
(data) => parseFloat(
|
||||
(data[0] || "") +
|
||||
data[1].join("") +
|
||||
(data[2] ? "."+data[2][1].join("") : "")
|
||||
)
|
||||
%}
|
||||
# # Numbers
|
||||
# decimal -> "-":? [0-9]:+ ("." [0-9]:+):? {%
|
||||
# (data) => parseFloat(
|
||||
# (data[0] || "") +
|
||||
# data[1].join("") +
|
||||
# (data[2] ? "."+data[2][1].join("") : "")
|
||||
# )
|
||||
# %}
|
||||
|
||||
# Double-quoted string
|
||||
dqstring -> "\"" dstrchar:* "\"" {% (data) => data[1].join('') %}
|
||||
|
|
@ -198,9 +198,9 @@ field ->
|
|||
| dqstring {% (data, start) => ({type: 'LiteralExpression', name: data[0], quoted: true, quotes: 'double', location: {start, end: start + data[0].length + 2}}) %}
|
||||
|
||||
expression ->
|
||||
decimal {% (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length}, type: 'LiteralExpression', quoted: false, value: Number(data.join(''))}}) %}
|
||||
| regex {% (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length}, type: 'RegexExpression', value: data.join('')}}) %}
|
||||
| range {% (data) => data[0] %}
|
||||
# decimal {% (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length}, type: 'LiteralExpression', quoted: false, value: Number(data.join(''))}}) %}
|
||||
regex {% (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length}, type: 'RegexExpression', value: data.join('')}}) %}
|
||||
# | range {% (data) => data[0] %}
|
||||
| unquoted_value {% (data, start, reject) => {
|
||||
const value = data.join('');
|
||||
|
||||
|
|
@ -236,36 +236,36 @@ expression ->
|
|||
| sqstring {% (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length + 2}, type: 'LiteralExpression', quoted: true, quotes: 'single', value: data.join('')}}) %}
|
||||
| dqstring {% (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length + 2}, type: 'LiteralExpression', quoted: true, quotes: 'double', value: data.join('')}}) %}
|
||||
|
||||
range ->
|
||||
range_open decimal " TO " decimal range_close {% (data, start) => {
|
||||
return {
|
||||
location: {
|
||||
start,
|
||||
},
|
||||
type: 'Tag',
|
||||
expression: {
|
||||
location: {
|
||||
start: data[0].location.start,
|
||||
end: data[4].location.start + 1,
|
||||
},
|
||||
type: 'RangeExpression',
|
||||
range: {
|
||||
min: data[1],
|
||||
minInclusive: data[0].inclusive,
|
||||
maxInclusive: data[4].inclusive,
|
||||
max: data[3],
|
||||
}
|
||||
}
|
||||
}
|
||||
} %}
|
||||
|
||||
range_open ->
|
||||
"[" {% (data, start) => ({location: {start}, inclusive: true}) %}
|
||||
| "{" {% (data, start) => ({location: {start}, inclusive: false}) %}
|
||||
|
||||
range_close ->
|
||||
"]" {% (data, start) => ({location: {start}, inclusive: true}) %}
|
||||
| "}" {% (data, start) => ({location: {start}, inclusive: false}) %}
|
||||
# range ->
|
||||
# range_open decimal " TO " decimal range_close {% (data, start) => {
|
||||
# return {
|
||||
# location: {
|
||||
# start,
|
||||
# },
|
||||
# type: 'Tag',
|
||||
# expression: {
|
||||
# location: {
|
||||
# start: data[0].location.start,
|
||||
# end: data[4].location.start + 1,
|
||||
# },
|
||||
# type: 'RangeExpression',
|
||||
# range: {
|
||||
# min: data[1],
|
||||
# minInclusive: data[0].inclusive,
|
||||
# maxInclusive: data[4].inclusive,
|
||||
# max: data[3],
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# } %}
|
||||
#
|
||||
# range_open ->
|
||||
# "[" {% (data, start) => ({location: {start}, inclusive: true}) %}
|
||||
# | "{" {% (data, start) => ({location: {start}, inclusive: false}) %}
|
||||
#
|
||||
# range_close ->
|
||||
# "]" {% (data, start) => ({location: {start}, inclusive: true}) %}
|
||||
# | "}" {% (data, start) => ({location: {start}, inclusive: false}) %}
|
||||
|
||||
comparison_operator ->
|
||||
(
|
||||
|
|
@ -292,4 +292,4 @@ regex_flags ->
|
|||
[gmiyusd]:+ {% d => d[0].join('') %}
|
||||
|
||||
unquoted_value ->
|
||||
[a-zA-Z_*?@#$\u0080-\uFFFF] [a-zA-Z\.\-_*?@#$\u0080-\uFFFF]:* {% d => d[0] + d[1].join('') %}
|
||||
[a-zA-Z_*?@#$\u0080-\uFFFF0-9] [a-zA-Z\.\-_*?@#$\u0080-\uFFFF0-9]:* {% d => d[0] + d[1].join('') %}
|
||||
|
|
|
|||
|
|
@ -42,22 +42,6 @@ const grammar: Grammar = {
|
|||
{"name": "__$ebnf$1", "symbols": ["__$ebnf$1", "whitespace_character"], "postprocess": (d) => d[0].concat([d[1]])},
|
||||
{"name": "__", "symbols": ["__$ebnf$1"], "postprocess": (data) => data[0].length},
|
||||
{"name": "whitespace_character", "symbols": [/[ \t\n\v\f]/], "postprocess": id},
|
||||
{"name": "decimal$ebnf$1", "symbols": [{"literal":"-"}], "postprocess": id},
|
||||
{"name": "decimal$ebnf$1", "symbols": [], "postprocess": () => null},
|
||||
{"name": "decimal$ebnf$2", "symbols": [/[0-9]/]},
|
||||
{"name": "decimal$ebnf$2", "symbols": ["decimal$ebnf$2", /[0-9]/], "postprocess": (d) => d[0].concat([d[1]])},
|
||||
{"name": "decimal$ebnf$3$subexpression$1$ebnf$1", "symbols": [/[0-9]/]},
|
||||
{"name": "decimal$ebnf$3$subexpression$1$ebnf$1", "symbols": ["decimal$ebnf$3$subexpression$1$ebnf$1", /[0-9]/], "postprocess": (d) => d[0].concat([d[1]])},
|
||||
{"name": "decimal$ebnf$3$subexpression$1", "symbols": [{"literal":"."}, "decimal$ebnf$3$subexpression$1$ebnf$1"]},
|
||||
{"name": "decimal$ebnf$3", "symbols": ["decimal$ebnf$3$subexpression$1"], "postprocess": id},
|
||||
{"name": "decimal$ebnf$3", "symbols": [], "postprocess": () => null},
|
||||
{"name": "decimal", "symbols": ["decimal$ebnf$1", "decimal$ebnf$2", "decimal$ebnf$3"], "postprocess":
|
||||
(data) => parseFloat(
|
||||
(data[0] || "") +
|
||||
data[1].join("") +
|
||||
(data[2] ? "."+data[2][1].join("") : "")
|
||||
)
|
||||
},
|
||||
{"name": "dqstring$ebnf$1", "symbols": []},
|
||||
{"name": "dqstring$ebnf$1", "symbols": ["dqstring$ebnf$1", "dstrchar"], "postprocess": (d) => d[0].concat([d[1]])},
|
||||
{"name": "dqstring", "symbols": [{"literal":"\""}, "dqstring$ebnf$1", {"literal":"\""}], "postprocess": (data) => data[1].join('')},
|
||||
|
|
@ -213,9 +197,7 @@ const grammar: Grammar = {
|
|||
{"name": "field", "symbols": [/[_a-zA-Z$]/, "field$ebnf$1"], "postprocess": (data, start) => ({type: 'LiteralExpression', name: data[0] + data[1].join(''), quoted: false, location: {start, end: start + (data[0] + data[1].join('')).length}})},
|
||||
{"name": "field", "symbols": ["sqstring"], "postprocess": (data, start) => ({type: 'LiteralExpression', name: data[0], quoted: true, quotes: 'single', location: {start, end: start + data[0].length + 2}})},
|
||||
{"name": "field", "symbols": ["dqstring"], "postprocess": (data, start) => ({type: 'LiteralExpression', name: data[0], quoted: true, quotes: 'double', location: {start, end: start + data[0].length + 2}})},
|
||||
{"name": "expression", "symbols": ["decimal"], "postprocess": (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length}, type: 'LiteralExpression', quoted: false, value: Number(data.join(''))}})},
|
||||
{"name": "expression", "symbols": ["regex"], "postprocess": (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length}, type: 'RegexExpression', value: data.join('')}})},
|
||||
{"name": "expression", "symbols": ["range"], "postprocess": (data) => data[0]},
|
||||
{"name": "expression", "symbols": ["unquoted_value"], "postprocess": (data, start, reject) => {
|
||||
const value = data.join('');
|
||||
|
||||
|
|
@ -250,32 +232,6 @@ const grammar: Grammar = {
|
|||
} },
|
||||
{"name": "expression", "symbols": ["sqstring"], "postprocess": (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length + 2}, type: 'LiteralExpression', quoted: true, quotes: 'single', value: data.join('')}})},
|
||||
{"name": "expression", "symbols": ["dqstring"], "postprocess": (data, start) => ({type: 'Tag', expression: {location: {start, end: start + data.join('').length + 2}, type: 'LiteralExpression', quoted: true, quotes: 'double', value: data.join('')}})},
|
||||
{"name": "range$string$1", "symbols": [{"literal":" "}, {"literal":"T"}, {"literal":"O"}, {"literal":" "}], "postprocess": (d) => d.join('')},
|
||||
{"name": "range", "symbols": ["range_open", "decimal", "range$string$1", "decimal", "range_close"], "postprocess": (data, start) => {
|
||||
return {
|
||||
location: {
|
||||
start,
|
||||
},
|
||||
type: 'Tag',
|
||||
expression: {
|
||||
location: {
|
||||
start: data[0].location.start,
|
||||
end: data[4].location.start + 1,
|
||||
},
|
||||
type: 'RangeExpression',
|
||||
range: {
|
||||
min: data[1],
|
||||
minInclusive: data[0].inclusive,
|
||||
maxInclusive: data[4].inclusive,
|
||||
max: data[3],
|
||||
}
|
||||
}
|
||||
}
|
||||
} },
|
||||
{"name": "range_open", "symbols": [{"literal":"["}], "postprocess": (data, start) => ({location: {start}, inclusive: true})},
|
||||
{"name": "range_open", "symbols": [{"literal":"{"}], "postprocess": (data, start) => ({location: {start}, inclusive: false})},
|
||||
{"name": "range_close", "symbols": [{"literal":"]"}], "postprocess": (data, start) => ({location: {start}, inclusive: true})},
|
||||
{"name": "range_close", "symbols": [{"literal":"}"}], "postprocess": (data, start) => ({location: {start}, inclusive: false})},
|
||||
{"name": "comparison_operator$subexpression$1", "symbols": [{"literal":":"}]},
|
||||
{"name": "comparison_operator$subexpression$1$string$1", "symbols": [{"literal":":"}, {"literal":"="}], "postprocess": (d) => d.join('')},
|
||||
{"name": "comparison_operator$subexpression$1", "symbols": ["comparison_operator$subexpression$1$string$1"]},
|
||||
|
|
@ -299,8 +255,8 @@ const grammar: Grammar = {
|
|||
{"name": "regex_flags$ebnf$1", "symbols": ["regex_flags$ebnf$1", /[gmiyusd]/], "postprocess": (d) => d[0].concat([d[1]])},
|
||||
{"name": "regex_flags", "symbols": ["regex_flags$ebnf$1"], "postprocess": d => d[0].join('')},
|
||||
{"name": "unquoted_value$ebnf$1", "symbols": []},
|
||||
{"name": "unquoted_value$ebnf$1", "symbols": ["unquoted_value$ebnf$1", /[a-zA-Z\.\-_*?@#$\u0080-\uFFFF]/], "postprocess": (d) => d[0].concat([d[1]])},
|
||||
{"name": "unquoted_value", "symbols": [/[a-zA-Z_*?@#$\u0080-\uFFFF]/, "unquoted_value$ebnf$1"], "postprocess": d => d[0] + d[1].join('')}
|
||||
{"name": "unquoted_value$ebnf$1", "symbols": ["unquoted_value$ebnf$1", /[a-zA-Z\.\-_*?@#$\u0080-\uFFFF0-9]/], "postprocess": (d) => d[0].concat([d[1]])},
|
||||
{"name": "unquoted_value", "symbols": [/[a-zA-Z_*?@#$\u0080-\uFFFF0-9]/, "unquoted_value$ebnf$1"], "postprocess": d => d[0] + d[1].join('')}
|
||||
],
|
||||
ParserStart: "main",
|
||||
};
|
||||
|
|
|
|||
|
|
@ -93,12 +93,12 @@ test('name:/(david)|(john)/', testQuery, ['david', 'john']);
|
|||
test('name:/(David)|(John)/', testQuery, []);
|
||||
test('name:/(David)|(John)/i', testQuery, ['david', 'john']);
|
||||
|
||||
test('height:[200 TO 300]', testQuery, ['robert', 'noah']);
|
||||
test('height:[220 TO 300]', testQuery, ['robert', 'noah']);
|
||||
test('height:{220 TO 300]', testQuery, ['noah']);
|
||||
test('height:[200 TO 225]', testQuery, ['robert', 'noah']);
|
||||
test('height:[200 TO 225}', testQuery, ['robert']);
|
||||
test('height:{220 TO 225}', testQuery, []);
|
||||
test.skip('height:[200 TO 300]', testQuery, ['robert', 'noah']);
|
||||
test.skip('height:[220 TO 300]', testQuery, ['robert', 'noah']);
|
||||
test.skip('height:{220 TO 300]', testQuery, ['noah']);
|
||||
test.skip('height:[200 TO 225]', testQuery, ['robert', 'noah']);
|
||||
test.skip('height:[200 TO 225}', testQuery, ['robert']);
|
||||
test.skip('height:{220 TO 225}', testQuery, []);
|
||||
|
||||
test('NOT David', testQuery, ['john', 'mike', 'robert', 'noah', 'foo bar', 'fox']);
|
||||
test('-David', testQuery, ['john', 'mike', 'robert', 'noah', 'foo bar', 'fox']);
|
||||
|
|
@ -115,12 +115,12 @@ test('name:David OR name:John', testQuery, ['david', 'john']);
|
|||
test('name:"david" OR name:"john"', testQuery, ['david', 'john']);
|
||||
test('name:"David" OR name:"John"', testQuery, []);
|
||||
|
||||
test('height:=175', testQuery, ['john', 'mike']);
|
||||
test('height:>200', testQuery, ['robert', 'noah']);
|
||||
test('height:>220', testQuery, ['noah']);
|
||||
test('height:>=220', testQuery, ['robert', 'noah']);
|
||||
test.skip('height:=175', testQuery, ['john', 'mike']);
|
||||
test.skip('height:>200', testQuery, ['robert', 'noah']);
|
||||
test.skip('height:>220', testQuery, ['noah']);
|
||||
test.skip('height:>=220', testQuery, ['robert', 'noah']);
|
||||
|
||||
test('height:=175 AND NOT name:mike', testQuery, ['john']);
|
||||
test.skip('height:=175 AND NOT name:mike', testQuery, ['john']);
|
||||
|
||||
test('"member"', testQuery, ['robert']);
|
||||
|
||||
|
|
@ -138,9 +138,9 @@ test('subscribed:true', testQuery, ['noah']);
|
|||
test('email:/[^.:@\\s](?:[^:@\\s]*[^.:@\\s])?@[^.@\\s]+(?:\\.[^.@\\s]+)*/', testQuery, ['noah']);
|
||||
|
||||
test('phoneNumber:"404-050-2611"', testQuery, ['noah']);
|
||||
test('phoneNumber:404', testQuery, ['noah']);
|
||||
test.skip('phoneNumber:404', testQuery, ['noah']);
|
||||
|
||||
test('balance:364', testQuery, ['noah']);
|
||||
test.skip('balance:364', testQuery, ['noah']);
|
||||
|
||||
test('(David)', testQuery, ['david']);
|
||||
test('(name:david OR name:john)', testQuery, ['david', 'john']);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ test(
|
|||
],
|
||||
);
|
||||
|
||||
test(
|
||||
test.skip(
|
||||
'matches or',
|
||||
testQuery,
|
||||
'name:foo OR name:bar OR height:=180',
|
||||
|
|
@ -181,7 +181,7 @@ test.skip(
|
|||
],
|
||||
);
|
||||
|
||||
test(
|
||||
test.skip(
|
||||
'matches number',
|
||||
testQuery,
|
||||
'height:=180',
|
||||
|
|
@ -195,7 +195,7 @@ test(
|
|||
],
|
||||
);
|
||||
|
||||
test(
|
||||
test.skip(
|
||||
'matches range',
|
||||
testQuery,
|
||||
'height:[100 TO 200]',
|
||||
|
|
|
|||
|
|
@ -529,7 +529,7 @@ test.skip('foo: bar', testQuery, {
|
|||
type: 'Tag',
|
||||
});
|
||||
|
||||
test('foo:123', testQuery, {
|
||||
test.skip('foo:123', testQuery, {
|
||||
expression: {
|
||||
location: {
|
||||
end: 7,
|
||||
|
|
@ -564,7 +564,7 @@ test('foo:123', testQuery, {
|
|||
type: 'Tag',
|
||||
});
|
||||
|
||||
test('foo:=123', testQuery, {
|
||||
test.skip('foo:=123', testQuery, {
|
||||
expression: {
|
||||
location: {
|
||||
end: 8,
|
||||
|
|
@ -636,7 +636,7 @@ test.skip('foo:= 123', testQuery, {
|
|||
type: 'Tag',
|
||||
});
|
||||
|
||||
test('foo:=-123', testQuery, {
|
||||
test.skip('foo:=-123', testQuery, {
|
||||
expression: {
|
||||
location: {
|
||||
end: 9,
|
||||
|
|
@ -671,7 +671,7 @@ test('foo:=-123', testQuery, {
|
|||
type: 'Tag',
|
||||
});
|
||||
|
||||
test('foo:=123.4', testQuery, {
|
||||
test.skip('foo:=123.4', testQuery, {
|
||||
expression: {
|
||||
location: {
|
||||
end: 10,
|
||||
|
|
@ -706,7 +706,7 @@ test('foo:=123.4', testQuery, {
|
|||
type: 'Tag',
|
||||
});
|
||||
|
||||
test('foo:>=123', testQuery, {
|
||||
test.skip('foo:>=123', testQuery, {
|
||||
expression: {
|
||||
location: {
|
||||
end: 9,
|
||||
|
|
@ -2415,7 +2415,7 @@ test('(foo:bar OR baz:qux) OR quuz:corge', testQuery, {
|
|||
type: 'LogicalExpression',
|
||||
});
|
||||
|
||||
test('[1 TO 2]', testQuery, {
|
||||
test.skip('[1 TO 2]', testQuery, {
|
||||
expression: {
|
||||
location: {
|
||||
end: 8,
|
||||
|
|
@ -2438,7 +2438,7 @@ test('[1 TO 2]', testQuery, {
|
|||
type: 'Tag',
|
||||
});
|
||||
|
||||
test('{1 TO 2]', testQuery, {
|
||||
test.skip('{1 TO 2]', testQuery, {
|
||||
expression: {
|
||||
location: {
|
||||
end: 8,
|
||||
|
|
@ -2461,7 +2461,7 @@ test('{1 TO 2]', testQuery, {
|
|||
type: 'Tag',
|
||||
});
|
||||
|
||||
test('[1 TO 2}', testQuery, {
|
||||
test.skip('[1 TO 2}', testQuery, {
|
||||
expression: {
|
||||
location: {
|
||||
end: 8,
|
||||
|
|
@ -2484,7 +2484,7 @@ test('[1 TO 2}', testQuery, {
|
|||
type: 'Tag',
|
||||
});
|
||||
|
||||
test('{1 TO 2}', testQuery, {
|
||||
test.skip('{1 TO 2}', testQuery, {
|
||||
expression: {
|
||||
location: {
|
||||
end: 8,
|
||||
|
|
|
|||
|
|
@ -52,19 +52,19 @@ test('foo:bar', testQuery);
|
|||
// https://github.com/gajus/liqe/issues/19
|
||||
test.skip('foo: bar', testQuery);
|
||||
|
||||
test('foo:123', testQuery);
|
||||
test.skip('foo:123', testQuery);
|
||||
|
||||
test('foo:=123', testQuery);
|
||||
test.skip('foo:=123', testQuery);
|
||||
|
||||
// https://github.com/gajus/liqe/issues/18
|
||||
// https://github.com/gajus/liqe/issues/19
|
||||
test.skip('foo:= 123', testQuery);
|
||||
|
||||
test('foo:=-123', testQuery);
|
||||
test.skip('foo:=-123', testQuery);
|
||||
|
||||
test('foo:=123.4', testQuery);
|
||||
test.skip('foo:=123.4', testQuery);
|
||||
|
||||
test('foo:>=123', testQuery);
|
||||
test.skip('foo:>=123', testQuery);
|
||||
|
||||
test('foo:true', testQuery);
|
||||
|
||||
|
|
@ -124,10 +124,10 @@ test('(foo:bar OR (baz:qux OR quuz:corge))', testQuery);
|
|||
|
||||
test('((foo:bar OR baz:qux) OR quuz:corge)', testQuery);
|
||||
|
||||
test('[1 TO 2]', testQuery);
|
||||
test.skip('[1 TO 2]', testQuery);
|
||||
|
||||
test('{1 TO 2]', testQuery);
|
||||
test.skip('{1 TO 2]', testQuery);
|
||||
|
||||
test('[1 TO 2}', testQuery);
|
||||
test.skip('[1 TO 2}', testQuery);
|
||||
|
||||
test('{1 TO 2}', testQuery);
|
||||
test.skip('{1 TO 2}', testQuery);
|
||||
|
|
|
|||
Loading…
Reference in a new issue