From a00cf9c8322ac56bed608dde0e840c185d92f1c5 Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Tue, 28 Jan 2020 03:05:23 -0500 Subject: [PATCH] add support for like_any operator --- .../wizard/model/SqlDefaultSearchField.java | 38 +++++----- .../model/entityconfig/EntityFieldType.java | 76 ++++++++++++------- .../annotations/ECSearchable.java | 2 + .../model/search/SearchBoundComparison.java | 21 ++++- .../model/search/SearchBoundSqlFunction.java | 12 +++ 5 files changed, 101 insertions(+), 48 deletions(-) diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/SqlDefaultSearchField.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/SqlDefaultSearchField.java index 4282731..0cc1759 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/SqlDefaultSearchField.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/SqlDefaultSearchField.java @@ -6,9 +6,7 @@ import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; import org.cobbzilla.wizard.model.entityconfig.annotations.ECField; import org.cobbzilla.wizard.model.entityconfig.annotations.ECForeignKey; import org.cobbzilla.wizard.model.entityconfig.annotations.ECSearchable; -import org.cobbzilla.wizard.model.search.SearchBound; -import org.cobbzilla.wizard.model.search.SearchBoundBuilder; -import org.cobbzilla.wizard.model.search.SearchField; +import org.cobbzilla.wizard.model.search.*; import java.lang.reflect.Field; import java.util.ArrayList; @@ -51,22 +49,26 @@ public class SqlDefaultSearchField implements SearchField { @Override public SearchBound[] getBounds() { final List bounds = new ArrayList<>(); EntityFieldType fieldType = search.type(); - if (fieldType == none_set) { - if (!empty(search.bounds())) { - try { - final SearchBoundBuilder builder = instantiate(search.bounds()); - return builder.build(bound, value, params, locale); - } catch (Exception e) { - return die("getBounds(" + bound + "): error invoking custom SearchBoundBuilder: " + search.bounds() + ": " + e); - } + if (!empty(search.bounds())) { + try { + final SearchBoundBuilder builder = instantiate(search.bounds()); + return builder.build(bound, value, params, locale); + } catch (Exception e) { + return die("getBounds(" + bound + "): error invoking custom SearchBoundBuilder: " + search.bounds() + ": " + e); + } + + } else if (!empty(search.operators())) { + for (SearchBoundComparison op : search.operators()) { + bounds.add(op.bind(name(), SearchFieldType.string)); + } + + } else if (fieldType == none_set) { + final ECField ecField = f.getAnnotation(ECField.class); + final ECForeignKey ecForeignKey = f.getAnnotation(ECForeignKey.class); + if (ecForeignKey != null) { + fieldType = reference; } else { - final ECField ecField = f.getAnnotation(ECField.class); - final ECForeignKey ecForeignKey = f.getAnnotation(ECForeignKey.class); - if (ecForeignKey != null) { - fieldType = reference; - } else { - fieldType = ecField != null && ecField.type() != none_set ? ecField.type() : guessFieldType(f); - } + fieldType = ecField != null && ecField.type() != none_set ? ecField.type() : guessFieldType(f); } } if (fieldType != null) { diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldType.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldType.java index 1491fd9..b693912 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldType.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/EntityFieldType.java @@ -2,8 +2,10 @@ package org.cobbzilla.wizard.model.entityconfig; import com.fasterxml.jackson.annotation.JsonCreator; import lombok.AllArgsConstructor; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.cobbzilla.wizard.model.entityconfig.validation.*; +import org.cobbzilla.wizard.model.search.SearchFieldType; import org.cobbzilla.wizard.validation.ValidationResult; import org.cobbzilla.wizard.validation.Validator; @@ -40,94 +42,94 @@ public enum EntityFieldType { email (new EntityConfigFieldValidator_email()), /** an integer-valued number */ - integer (new EntityConfigFieldValidator_integer()), + integer (SearchFieldType.integer, new EntityConfigFieldValidator_integer()), /** a real number */ - decimal (new EntityConfigFieldValidator_decimal()), + decimal (SearchFieldType.decimal, new EntityConfigFieldValidator_decimal()), /** an integer-valued monetary amount */ - money_integer (null), + money_integer (SearchFieldType.integer), /** a real-valued monetary amount */ - money_decimal (null), + money_decimal (SearchFieldType.decimal), /** a boolean value */ - flag (new EntityConfigFieldValidator_boolean()), + flag (SearchFieldType.flag, new EntityConfigFieldValidator_boolean()), /** a date value */ - date (null), + date (SearchFieldType.integer), /** a date value in the past (before current date) */ - date_past (null), + date_past (SearchFieldType.integer), /** a date value in the future (or current date) */ - date_future (null), + date_future (SearchFieldType.integer), /** a field for age */ - age (null), + age (SearchFieldType.integer), /** a 4-digit year field */ - year (null), + year (SearchFieldType.integer), /** a 4-digit year field that starts with the current year and goes into the past */ - year_past (null), + year_past (SearchFieldType.integer), /** a 4-digit year field that starts with the current year and goes into the future */ - year_future (null), + year_future (SearchFieldType.integer), /** a 4-digit year and 2-digit month field (YYYY-MM) */ - year_and_month (null), + year_and_month (SearchFieldType.integer), /** a 4-digit year and 2-digit month field (YYYY-MM) field that starts with the current year and goes into the past */ - year_and_month_past (null), + year_and_month_past (SearchFieldType.integer), /** a 4-digit year and 2-digit month field (YYYY-MM) field that starts with the current year and goes into the future */ - year_and_month_future (null), + year_and_month_future (SearchFieldType.integer), /** a date or date/time value, represented as milliseconds since 1/1/1970 */ - epoch_time (new EntityConfigFieldValidator_integer()), + epoch_time (SearchFieldType.integer, new EntityConfigFieldValidator_integer()), /** a date or date/time value, represented as milliseconds since 1/1/1970 */ - expiration_time (new EntityConfigFieldValidator_integer()), + expiration_time (SearchFieldType.integer, new EntityConfigFieldValidator_integer()), /** millisecond value for a time duration */ - time_duration (new EntityConfigFieldValidator_integer()), + time_duration (SearchFieldType.integer, new EntityConfigFieldValidator_integer()), /** a time-zone (for example America/New York) */ - time_zone (null), + time_zone (), /** a locale (for example en_US) */ - locale (null), + locale (), /** a 3-letter currency code (for example USD) */ - currency (null), + currency (), /** an IPv4 address */ - ip4 (null), + ip4 (), /** an IPv6 address */ - ip6 (null), + ip6 (), /** a hostname */ - hostname (null), + hostname (), /** a fully-qualified domain name */ - fqdn (null), + fqdn (), /** a 2-letter US state abbreviation */ - us_state (null), + us_state (), /** a US ZIP code */ - us_zip (null), + us_zip (), /** HTTP URL */ http_url (new EntityConfigFieldValidator_httpUrl()), /** a reference to another EntityConfig instance */ - reference (null), + reference (), /** a base64-encoded PNG image */ - base64_png (null), + base64_png (), /** an embedded sub-object */ embedded (new EntityConfigFieldValidator_embedded()), @@ -135,8 +137,24 @@ public enum EntityFieldType { /** a US phone number */ us_phone (new EntityConfigFieldValidator_USPhone()); + @Getter private SearchFieldType searchFieldType; private EntityConfigFieldValidator fieldValidator; + EntityFieldType (EntityConfigFieldValidator validator) { + this.searchFieldType = SearchFieldType.string; + this.fieldValidator = validator; + } + + EntityFieldType (SearchFieldType searchFieldType) { + this.searchFieldType = searchFieldType; + this.fieldValidator = null; + } + + EntityFieldType () { + this.searchFieldType = SearchFieldType.string; + this.fieldValidator = null; + } + /** Jackson-hook to create a new instance based on a string, case-insensitively */ @JsonCreator public static EntityFieldType fromString(String val) { return valueOf(val.toLowerCase()); } diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/annotations/ECSearchable.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/annotations/ECSearchable.java index 9bacace..08abba6 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/annotations/ECSearchable.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/entityconfig/annotations/ECSearchable.java @@ -1,6 +1,7 @@ package org.cobbzilla.wizard.model.entityconfig.annotations; import org.cobbzilla.wizard.model.entityconfig.EntityFieldType; +import org.cobbzilla.wizard.model.search.SearchBoundComparison; import org.cobbzilla.wizard.model.search.SqlViewFieldSetter; import org.jasypt.hibernate4.encryptor.HibernatePBEStringEncryptor; @@ -20,6 +21,7 @@ public @interface ECSearchable { String sortField() default ""; EntityFieldType type() default EntityFieldType.none_set; String bounds() default ""; + SearchBoundComparison[] operators() default {}; String entity() default ""; ECForeignKeySearchDepth fkDepth() default ECForeignKeySearchDepth.inherit; diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchBoundComparison.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchBoundComparison.java index d0b6ffd..0ace190 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchBoundComparison.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchBoundComparison.java @@ -6,6 +6,8 @@ import org.cobbzilla.util.collection.ComparisonOperator; import org.cobbzilla.util.time.TimePeriodType; import org.cobbzilla.util.time.TimeUtil; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -27,7 +29,20 @@ public enum SearchBoundComparison { lt (sqlCompare(ComparisonOperator.lt.sql, SearchBoundComparison::parseCompareArgument)), le (sqlCompare(ComparisonOperator.le.sql, SearchBoundComparison::parseCompareArgument)), - like (sqlCompare( "ilike", SearchBoundComparison::parseLikeArgument)), + like (sqlCompare(Constants.ILIKE, SearchBoundComparison::parseLikeArgument)), + + like_any((bound, params, value, locale) -> { + final String[] values = value.split(Constants.ILIKE_SEP); + final List valueList = Arrays.asList(values); + final String[] operators = new String[values.length]; + Arrays.fill(operators, Constants.ILIKE); + final SearchBoundValueFunction[] valueFuncs = new SearchBoundValueFunction[values.length]; + Arrays.fill(valueFuncs, (SearchBoundValueFunction) (bound1, value1, locale1) -> value1); + for (int i=0; i(valueList), null, locale); + }), is_null (sqlNullCompare(true)), not_null(sqlNullCompare(false)), @@ -126,4 +141,8 @@ public enum SearchBoundComparison { public static String customPrefix(String op) { return SearchBoundComparison.custom.name() + OP_SEP + op + OP_SEP; } + public static class Constants { + public static final String ILIKE = "ilike"; + public static final String ILIKE_SEP = ","; + } } diff --git a/wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchBoundSqlFunction.java b/wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchBoundSqlFunction.java index c8291bd..f59d5e9 100644 --- a/wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchBoundSqlFunction.java +++ b/wizard-common/src/main/java/org/cobbzilla/wizard/model/search/SearchBoundSqlFunction.java @@ -38,6 +38,18 @@ public interface SearchBoundSqlFunction { }; } + static SearchBoundSqlFunction sqlOrCompare(String[] operators, SearchBoundValueFunction[] valueFunctions, String[] values) { + return (bound, params, value, locale) -> { + final StringBuilder b = new StringBuilder(); + for (int i = 0; i < operators.length; i++) { + if (b.length() > 0) b.append(") OR ("); + b.append(bound.getName()).append(" ").append(operators[i]).append(" ?"); + params.add(valueFunctions[i].paramValue(bound, values[i], locale)); + } + return b.insert(0, "(").append(")").toString(); + }; + } + static SearchBoundSqlFunction sqlNullCompare(boolean isNull) { return (bound, params, value, locale) -> bound.getName() + " IS " + (!isNull ? "NOT " : "") + " NULL"; }