package gw.rating.rtm.matchop uses gw.rating.rtm.domain.OrderedPersistenceAdapter uses gw.api.database.Query uses java.lang.Comparable uses java.lang.Integer uses java.lang.IllegalStateException uses gw.api.database.Restriction uses java.util.List @ReadOnly class StatelessLongestSubstringMatch extends StatelessMatchOperator { construct(matchOp : RateTableMatchOp) { super(matchOp) initConverter() } private function compareByFirstCharacters(a : Comparable, b : Comparable) : int { if (a typeis String and b typeis String) { return a.charAt(0) - b.charAt(0) } else { throw new IllegalStateException("StatelessLongestSubstringMatch only works with Strings") } } // For this matchop we group by the first character override function compareRowValues(a : Comparable, b : Comparable) : int { return compareByFirstCharacters(a, b) } // For this matchop we binary search by the first character, then post-filter override function compare(bean: OrderedPersistenceAdapter, arg: Comparable ): int { return compareByFirstCharacters(bean.get(ArgIndex), arg) } override function filter(results : List, arg : Comparable, normalized : boolean) : List { var rows = super.filter(results, arg, normalized) if (rows.size() == 0) return rows // now we cherry-pick :-( // Because we sorted only on first character, and also pre-filtered on first character, // the set of rows are correctly ordered for the successive matchops. Our cherry-picked // result should (we think) also be correctly ordered. // TODO PERF: Using where isn't cheap due to conversion of iterable. Write faster util // (Measure this before the util is written :-) ) return rows.where(\ bean -> (arg as String).startsWith(bean.get(ArgIndex) as String)) } override public property get OrderDependent() : boolean { return true } override function getScore(bean: OrderedPersistenceAdapter, arg: Comparable ): Comparable { var rowValue = bean.get(this.ArgIndex) if (rowValue typeis String) { return rowValue.length as Integer } else { throw new IllegalStateException("StatelessLongestSubstringMatch only works with Strings") } } override function filter(query: Query , arg: Comparable ) { if (arg typeis String ) { query.and(\ restriction -> restriction.or(\ or1 -> { addRestrictions(ColumnName, arg.trim(), or1 as Restriction)}) ) } else { throw new IllegalStateException("StatelessLongestSubstringMatch only works with Strings") } } override function convertParam(v: Comparable , paramIndex: int): Comparable { return v } /** * this adds n restrictions where n = value.length * ex: if value = "ABC", then this generates * or.compare(colName,Equals,"A") * or.compare(colName,Equals,"AB") * or.compare(colName,Equals,"ABC") * * NOTE: This style of query may be a performance issue...our DB expert * is of the opinion that it would be faster to do an exact query for the * full string, then "relax" by knocking off the last character and doing * the query again, etc. rather than combining all of them into one query. * But the relax architecture is not set up to handle that right now. */ private function addRestrictions(colName:String, value:String, res:Restriction): Restriction { for(i in 0|..value.length) { res.compare(colName, Equals, value.substring(0, i)) } return res } }