From 0715bc1b104e96e2af64b89d9e0fccfea40953ca Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Sun, 26 Jan 2020 01:32:54 +0059 Subject: [PATCH] Refactor matching filter abstraction level. --- src/queries.js | 136 ++++++++++++++++++++++++++--------------- src/userquerywidget.js | 27 ++++++++ 2 files changed, 115 insertions(+), 48 deletions(-) diff --git a/src/queries.js b/src/queries.js index 7ed4fef..b946255 100644 --- a/src/queries.js +++ b/src/queries.js @@ -62,13 +62,13 @@ export function do_tag_query(query, database) { export const MatchTypeEnum = { MATCH_EQUALS: 1, - MATCH_REGEXP_CASEINSENSITIVE: 2, + MATCH_NATURAL: 2, }; export const MatchAgainstEnum = { - MATCH_RESULT_NAME: 1, // Match on the name of whatever object type we are querying for - MATCH_IMAGE_NAME: 2, // Match on the name of the relevant image(s), if any - MATCH_ALBUM_NAME: 3, // Match on the name of the relevant album(s), if any + MATCH_IMAGE_NAME: 1, // Match on the name of the relevant image(s), if any + MATCH_ALBUM_NAME: 2, // Match on the name of the relevant album(s), if any + MATCH_ALBUM_NAME_IN_TREE: 3, // Match on any album name in the album tree branch, including parents MATCH_TAG_NAME: 4, // Match on the name of the relevant tag(s), if any } @@ -102,17 +102,40 @@ export class ConstFilter extends ResultFilter { simplify() { return this; } } +export class NegationFilter extends ResultFilter { + constructor(rtype, body) { + super(rtype); + this.body = body; + } + + body = new ConstFilter(this.return_type, false); + + to_sql_where() { + return "NOT (" + this.body.to_sql_where + ")"; + } + + simplify() { + var f = this.body.simplify(); + if(f.is_true()) { + return new ConstFilter(this.result_type, false); + } + if(f.is_false()) { + return new ConstFilter(this.result_type, true); + } + return this; + } +} + export class MatchingFilter extends ResultFilter { - constructor(rtype, against, from, mtype, negate) { + constructor(rtype, against, from, mtype) { super(rtype); this.match_against = against; this.match_from = from; this.match_type = mtype; - this.negate = negate; } - // What string to match against, e.g. "Image.name" (field) or "\"Image.name\"" (literal) - match_against = ""; + // What string to match against. + match_against = MatchAgainstEnum.MATCH_IMAGE_NAME; // optional string used in the filtering match_from = ""; @@ -120,26 +143,39 @@ export class MatchingFilter extends ResultFilter { // How to use the matching string match_type = MatchTypeEnum.MATCH_EQUALS; - // If true, negates the filter - // TODO: make negation its own filter type - negate = false; - to_sql_where() { - var match_type_str = false; - var match_against = this.match_against; - var match_from = this.match_from; - - if (this.match_type == MatchTypeEnum.MATCH_EQUALS) { - match_type_str = "="; - } else if (this.match_type == MatchTypeEnum.MATCH_REGEXP_CASEINSENSITIVE) { - match_type_str = " REGEXP "; - match_from = "LOWER(" + match_from + ")"; - match_against = "LOWER(" + match_against + ")"; - } else { - throw new Error('Unsupported match type: ' + this.match_type); + if(this.match_against == MatchAgainstEnum.MATCH_IMAGE_NAME) { + if(this.match_type == MatchTypeEnum.MATCH_EQUALS) { + return '(Images.name="' + this.match_from + '")'; + } else if(this.match_type == MatchTypeEnum.MATCH_NATURAL) { + var expr = '"' + '.*' + escape_regex(this.match_from) + '.*' + '"'; + return "(LOWER(Images.name) REGEXP LOWER(" + expr +"))"; + } + } else if(this.match_against == MatchAgainstEnum.MATCH_TAG_NAME) { + if(this.match_type == MatchTypeEnum.MATCH_EQUALS) { + return '(Tags.name="' + this.match_from + '")'; + } else if(this.match_type == MatchTypeEnum.MATCH_NATURAL) { + var expr = '"' + '.*' + escape_regex(this.match_from) + '.*' + '"'; + return '(LOWER(Tags.name) REGEXP LOWER("' + expr + '"))'; + } + } else if(this.match_against == MatchAgainstEnum.MATCH_ALBUM_NAME) { + if(this.match_type == MatchTypeEnum.MATCH_EQUALS) { + var expr = '\/(.*\/)*' + escape_regex(this.match_from); + return '(Albums.relativePath REGEXP "' + expr + '")'; + } else if(this.match_type == MatchTypeEnum.MATCH_NATURAL) { + throw new Error("Natural matching on album names is not yet supported."); + } + } else if(this.match_against == MatchAgainstEnum.MATCH_ALBUM_NAME_IN_TREE) { + if(this.match_type == MatchTypeEnum.MATCH_EQUALS) { + var expr = escape_regex(this.match_from) + '(\/[^\/]+)*'; + return '(Albums.relativePath REGEXP "' + expr + '")'; + } else if(this.match_type == MatchTypeEnum.MATCH_NATURAL) { + throw new Error("Natural matching on album tree names is not yet supported."); + } } - return "(" + (this.negate ? "NOT " : "") + match_against + match_type_str + match_from + ")"; + console.log(this); + throw new Error("Unsupported matching filter for SQL generation."); } simplify() { return this; } } @@ -255,43 +291,47 @@ function filter_from_text_segment(result_type, segment) { // Option 1: match on image name var name_filter = new MatchingFilter( result_type, - "Images.name", - '"' + '.*' + escape_regex(segment['text']) + '.*' + '"', - MatchTypeEnum.MATCH_REGEXP_CASEINSENSITIVE, - segment['negated'] + MatchAgainstEnum.MATCH_IMAGE_NAME, + segment['text'], + MatchTypeEnum.MATCH_NATURAL ); // Option 2: match on album path var album_filter = new MatchingFilter( result_type, - "Albums.relativePath", - '"' + escape_regex(segment['text']) + '(\/[^\/]+)*' + '"', - MatchTypeEnum.MATCH_REGEXP_CASEINSENSITIVE, - segment['negated'] + MatchAgainstEnum.MATCH_ALBUM_NAME, + segment['text'], + MatchTypeEnum.MATCH_EQUALS ); filter = new LogicalOperatorFilter(result_type, name_filter, album_filter, LogicalOperatorEnum.OR); + if(segment['negated']) { + filter = new NegationFilter(result_type, filter); + } } else if (result_type == ResultTypeEnum.ALBUM) { - // Match against the album "name", which is the basename of its relative path. - // TODO is it nicer to match on relative path? + // TODO: We need a natural matcher for album names var name_filter = new MatchingFilter( result_type, - "Albums.relativePath", - '"' + '\/(.*\/)*' + escape_regex(segment['text']) + '"', - MatchTypeEnum.MATCH_REGEXP_CASEINSENSITIVE, - segment['negated'] + MatchAgainstEnum.MATCH_ALBUM_NAME, + segment['text'], + MatchTypeEnum.MATCH_EQUALS ); filter = name_filter; + if(segment['negated']) { + filter = new NegationFilter(result_type, filter); + } } else if (result_type == ResultTypeEnum.TAG) { // Match against the tag name. var name_filter = new MatchingFilter( result_type, - "Tags.name", - '"' + '.*' + escape_regex(segment['text']) + '.*' + '"', - MatchTypeEnum.MATCH_REGEXP_CASEINSENSITIVE, - segment['negated'] + MatchAgainstEnum.MATCH_TAG_NAME, + segment['text'], + MatchTypeEnum.MATCH_EQUALS ); filter = name_filter; + if(segment['negated']) { + filter = new NegationFilter(result_type, filter); + } } return filter.simplify(); @@ -320,9 +360,9 @@ export function user_query_from_search_string(search_string) { export function user_query_from_browsed_album(album_path) { var r = new UserQuery(); - var match_type = MatchTypeEnum.MATCH_REGEXP_CASEINSENSITIVE; - var match_text = '"' + escape_regex(album_path) + '(\/[^\/]+)*' + '"'; - var match_against = "Albums.relativePath"; + var match_type = MatchTypeEnum.MATCH_EQUALS; + var match_against = MatchAgainstEnum.MATCH_ALBUM_NAME_IN_TREE; + var match_text = album_path; r.image_filter = new MatchingFilter(ResultTypeEnum.IMAGE, match_against, match_text, match_type, false).simplify(); r.album_filter = new ConstFilter(ResultTypeEnum.ALBUM, false).simplify(); @@ -332,8 +372,8 @@ export function user_query_from_browsed_album(album_path) { export function user_query_from_browsed_tag(name) { var r = new UserQuery(); var match_type = MatchTypeEnum.MATCH_EQUALS; - var match_text = '"' + name + '"'; - var match_against = "Tags.name"; + var match_text = name; + var match_against = MatchAgainstEnum.MATCH_TAG_NAME; r.image_filter = new MatchingFilter(ResultTypeEnum.IMAGE, match_against, match_text, match_type, false).simplify(); r.album_filter = new ConstFilter(ResultTypeEnum.ALBUM, false).simplify(); r.tag_filter = new ConstFilter(ResultTypeEnum.TAG, false).simplify(); diff --git a/src/userquerywidget.js b/src/userquerywidget.js index 543e235..d275e88 100644 --- a/src/userquerywidget.js +++ b/src/userquerywidget.js @@ -6,6 +6,7 @@ import FormControlLabel from '@material-ui/core/FormControlLabel'; import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline'; import Typography from '@material-ui/core/Typography'; import Button from '@material-ui/core/Button'; +import LabelIcon from '@material-ui/icons/Label'; import { makeStyles } from '@material-ui/core/styles'; @@ -28,10 +29,36 @@ const useStyles = makeStyles(theme => ({ }, })); +export function TagEqualsExpressionControl(props) { + const classes = useStyles(); + const { name } = props; + + var pretty_name = name + .replace(/^"/g, '') + .replace(/"$/g, ''); + + return ( + <> + + + ); +} + export function MatchingFilterExpressionControl(props) { const classes = useStyles(); const { expr } = props; + if(expr.match_type == MatchTypeEnum.MATCH_EQUALS && + expr.match_against == "Tags.name") { + // This is an exact tag match. + return + } + var opstr = ""; if (expr.match_type == MatchTypeEnum.MATCH_EQUALS) { opstr = " = ";