ActionScript3 collection query API proposal


Published on

ActionScript3 collection query API proposal.
Collection Querying API similar to LINQ 2 Objects (OO style).

Published in: Education, Technology, Business
  • Be the first to comment

  • Be the first to like this

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

ActionScript3 collection query API proposal

  1. 1. ActionScript3 collection query OO STYLE - LINQ TO OBJECTS IMPLEMENTATION PROPOSAL
  2. 2. Introduction  Draft implementation, just an idea, would be great if we have something like this in ActionScript (but more robust, complex, optimized of course)  Purpose – collection sorting, filtering, grouping wrapper  OO-Style LINQ to Objects lib for flex
  3. 3. Usage examples – first, last, any, itemAt //get first with title containing case insensitive string “Voislav Seselj” var query:IQuery = new CollectionQuery(_news); var firstItem:NewsFeed = query.where(new Conditions().contains("title", “Voislav Seselj", false)).first() as NewsFeed; //last item published in 2013 or 2014 var lastItem:NewsFeed = query.where(new Conditions().eq("year", 2014).OR.eq("year", 2013)).last() as NewsFeed; var usersQuery:IQuery = new CollectionQuery(); if(usersQuery.from(users).where(new Conditions().eq("name", "Peter Pan")).any()) { //excellent, there is a user (or users) with name "Peter Pan“ } //get third adault from the end of the collection ordered by name ascending and lastname //descending. var user:User = new CollectionQuery(users) .where(new Conditions().gte("years", 18)) .orderBy("name").orderBy("lastname", false) .itemAt(2) as User;
  4. 4. Usage examples – where, select, limit //get users for given conditions ordered by company and then by years property var users:IList = usersQuery.where(new Conditions().lt("years", 18).gt("years", 5).OR .eq("company", "Company 1")) .orderBy("company“).orderBy("years") .execute() as IList; //select collection containing anonymous objects with properties given in select clause //extracted from user objects, for given conditions, ordered by salary with ascending order //and compare as numerics. //Limit to maximum 10 results var users:IList = new CollectionQuery() .select("name, lastname, company, salary") .from(users) .where(new Conditions().gte("salary", 40000).lte("salary", 45000)) //AND by default .orderBy("salary", true, true) .limit(10).execute() as IList;
  5. 5. Usage examples – nested complex objects //select streets from complex member “address” for each user in users collection var streets:IList ="address.street").from(users).execute() as IList; //select anonymous objects containing user’s name, lastname, address->number, address//>street, address->uniqueId->value, for users having address number greater then 400 in //Kragujevac city, or having address number lower then 400 and live in Belgrade city. //Order list by city ascending and by address number descending comparing address.number //property as numbers. var users:IList = usersQuery .select("name, lastname, address.number, address.street, address.uniqueId.value") .from(users) .where(new Conditions() .gt("address.number", 400).eq("", "Kragujevac").OR. lt("address.number", 400).eq("", "Belgrade")) .orderBy("") .orderBy("address.number", false, true) .execute() as IList;
  6. 6. Usage examples – groupBy //group users named Marija or Slavisa by city, ordere by name and then by lastname var query:IQuery = new CollectionQuery(); var groupResults:IQueryGroupResult = query .select("id, name, lastname, address.street") //get anonimous objects .from(users) .where(new Conditions() .eq("name", "Slaviša").OR .eq("name“,"Marija").AND.contains("lastname", "ić")) .orderBy("name").orderBy("lastname", false) //order by name , lastname descending .groupBy("") //group by city .execute() as IQueryGroupResult; //get grouped results for each(var city:Object in groupResults.keys) //iterate through key collection { printUsersFromCity(key, groupResults[key] as IList); //and print collection for each key } if(groupResults.keys.contains("Kragujevac")) //test if there is a key { var kragujevcani:IList = groupResults["Kragujevac"] as IList; //print the collection }
  7. 7. API  ICondition  IQuery  IQueryGroupResult  IQueryItemResult  CollectionQuery  Conditions  Predicate  QueryGroupResult  QueryResultItem
  8. 8. IQuery /** * Query interface * @author Slavisa */ public interface IQuery { function select(value:String = null):IQuery; //select columns function from(coll:IList):IQuery; //sets collection to be queried function where(conditions:ICondition):IQuery; //sets condition chain function groupBy(property:String):IQuery; //sets group keys function orderBy(property:String, ascending:Boolean = true, numeric:Boolean = false):IQuery; //order by given property function limit(count:int = 100):IQuery; //limits result’s item count function first():Object; //returns first or null function last():Object; //returns last or null function itemAt(index:int):Object; //returns item by the given index, or null function execute():Object; //actual execution, returns IList or IQueryGroupResult object function any():Boolean; //determines if at least one result exists }
  9. 9. ICondition /** * Condition interface * @author Slavisa */ public interface ICondition { function eq(prop:String, value:Object):ICondition; //equal function diff(prop:String, value:Object):ICondition; //not equal function contains(prop:String, value:String, caseSensitive:Boolean = true):ICondition; //contains function lt(prop:String, value:Object):ICondition; //lower than function lte(prop:String, value:Object):ICondition; //lower than or equal function gt(prop:String, value:Object):ICondition; //greater than function gte(prop:String, value:Object):ICondition; //greater than or equal function get OR():ICondition; function get AND():ICondition; function get root():ICondition; } //OR – new condition group //AND – new condition group //get root condition
  10. 10. IQueryItemResult, QueryResultItem, IQueryGroupResult /** * Query Item result interface * @author Slavisa */ public interface IQueryItemResult { } /** * Query Item result dynamic class * Stores custom selection (anonimous object) * @author Slavisa */ public dynamic class QueryResultItem implements IQueryItemResult { } /** * Query Grouped results * @author pokimsla */ public interface IQueryGroupResult { function get keys():ArrayCollection; function get length():int; } //gets keys collection //gets number of groups
  11. 11. QueryGroupResult public dynamic class QueryGroupResult extends Dictionary implements IQueryGroupResult { //Gets array collection of group keys public function get keys():ArrayCollection { var keys:ArrayCollection = new ArrayCollection(); for(var key:Object in this) { keys.addItem(key); } return keys; } //number of keys public function get length():int { return keys.length; } }
  12. 12. Predicate /** * Predicate class * @author Slavisa * Used for combining multiple conditions joined with AND operator */ [Bindable] public class Predicate implements ICondition { /* Predicate types */ public static const EQ:String = "equal"; public static const DIFF:String = "different"; public static const CONTAINS:String = "contains"; public static const LT:String = "less"; public static const GT:String = "greater"; public static const GTE:String = "greater or equal"; public static const LTE:String = "lower or equal"; /* operators */ public static const OPERATOR_OR:String = "or"; public static const OPERATOR_AND:String = "and"; public var property:String; //property for comparison public var value:Object; //value to compare with public var type:String; //predicate type public var conditions:Conditions; //joining conditions (group of predicates to which this predicate belongs) public var attributes:Object; //additional attributes private var _root:ICondition; //root condition
  13. 13. Predicate public function Predicate(cond:ICondition, prop:String, val:Object, type:String, attributes:Object = null) { this._root = cond.root; //set the root condition = prop; this.value = val; this.type = type; this.conditions = cond as Conditions; this.conditions.addPredicate(this); //authomaticly add me to the predicate list this.attributes = attributes; } //creates next predicate (not equal) public function diff(prop:String, value:Object):ICondition { return createNextPredicate(prop, value, DIFF); } //creates next predicate (equal) public function eq(prop:String, value:Object):ICondition { return createNextPredicate(prop, value, EQ); } … //create next predicate for given values private function createNextPredicate(prop:String, value:Object, type:String, attributes:Object = null):ICondition { var pred:Predicate = new Predicate(conditions, prop, value, type, attributes); return pred; }
  14. 14. Predicate // Creates new condition with OR relation public function get OR():ICondition { var condition:Conditions = new Conditions(); condition.type = OPERATOR_OR; condition.root = root; = condition; return condition; } //creates new condition with AND relation public function get AND():ICondition { var condition:Conditions = new Conditions(); condition.type = OPERATOR_AND; condition.root = root; = condition; return condition; } //gets root condition public function get root():ICondition { return _root; } }
  15. 15. Conditions /** * Condition contains group of predicates, used for joining multiple condition groups (predicates) * @author Slavisa * <br/> * Condition chain boolean result is calculated from last to root condition group */ public class Conditions implements ICondition { private var _predicates:ArrayCollection=new ArrayCollection(); //predicate collection private var _next:ICondition; //next condition in a row private var _root:Conditions; //starting condition public var type:String; //starting condition shouldn't have a type property populated. EQ, OR, AND, LT... //Condition contains group of predicates, used for joining multiple condition groups (predicates) public function Conditions() { _root = this; } /** * add predicates to a collection * @param predicate * predicates are validated in groups with ANR logical operator */ public function addPredicate(predicate:ICondition):void { _predicates.addItem(predicate); }
  16. 16. Conditions //creates and appends diff predicate public function diff(prop:String, value:Object):ICondition { return new Predicate(this, prop, value, Predicate.DIFF); } //creates and appends eq predicate public function eq(prop:String, value:Object):ICondition { return new Predicate(this, prop, value, Predicate.EQ); } //… other predicates, getters and setters //OR condition does nothing on Conditions instance public function get OR():ICondition { return this; } //AND condition does nothing on Conditions instance public function get AND():ICondition { return this; } }
  17. 17. CollectionQuery /** * Actual collection query implementation * @author Slavisa */ public class CollectionQuery implements IQuery { private var _coll:IList; //collection private var _conditions:ICondition; //conditions private var _comparatorGroups:ArrayCollection; //comparator groups built before collection iteration private const _comparatorMap:Object = createComparatorMap(); //comparator delegates private var _selectColumns:String = null; //select columns. If null, row items are selected private var _limitCount:int = 0; //limited item count private var _groupBy:String = null; //group by this property private var _orderBy:ArrayCollection = new ArrayCollection(); //order by these properties private var _hasGroupBy:Boolean = false; private var _hasOrderBy:Boolean = false; //ctor public function CollectionQuery(coll:IList = null, selectColumns:String = null) { _coll = coll; this._selectColumns = selectColumns; }
  18. 18. CollectionQuery //creates comparator delegate map private function createComparatorMap():Object { var map:Object = new Object(); map[Predicate.DIFF] = diff; map[Predicate.EQ] = eq; map[Predicate.GT] = gt; map[Predicate.GTE] = gte; map[Predicate.LT] = lt; map[Predicate.LTE] = lte; map[Predicate.CONTAINS] = contains; return map; } //contains comparator private function contains(a:Object, b:Object, attributes:Object = null):Boolean { var strA:String = attributes && attributes.caseSensitive ? String(a) : String(a).toLowerCase(); var strB:String = attributes && attributes.caseSensitive ? String(b) : String(b).toLowerCase(); return strA.indexOf(strB) > -1; } //equal comparator private function eq(a:Object, b:Object, attributes:Object = null):Boolean { return a == b; } //…other comparators
  19. 19. CollectionQuery //sets select properties public function select(value:String = null):IQuery { _selectColumns = value; return this; } //sets target collection public function from(coll:IList):IQuery { _coll = coll; return this; } //sets conditions root public function where(conditions:ICondition):IQuery { this._conditions = conditions.root; return this; } /* Similar for : groupBy orderBy limit */
  20. 20. CollectionQuery public function first():Object { if(_hasGroupBy) throw new Error("First() is allowed only for non-grouped results"); var result:IList = runExecution(1) as IList; return result.length > 0 ? result[0] : null; } public function last():Object { if(_hasGroupBy) throw new Error("Last() is allowed only for non-grouped results"); var result:IList = runExecution(1, true) as IList; return result.length > 0 ? result[0] : null; } public function itemAt(index:int):Object { if(_hasGroupBy) throw new Error("ItemAt() is allowed only for non-grouped results"); if(index < 0) throw new ArgumentError("Invalid argument for itemAt method"); var result:IList = runExecution(index + 1) as IList; return result.length > (index + 1) ? null : result[index]; }
  21. 21. CollectionQuery public function execute():Object //interface impl { return runExecution(_limitCount); } //actual query execution private function runExecution(limit:int = 0, reverse:Boolean = false):Object { implementation details excluded for insufficient space, if anyone cares send me an email to, I’ll be happy to provide all you need } private function orderCollection(collection:ArrayCollection):void { if(_orderBy.length == 0) return; var sort:Sort = new Sort(); sort.fields = new Array(); for each(var fieldDef:Object in _orderBy) { var sortField:SortField = new SortField(fieldDef.field, false, !fieldDef.ascending, fieldDef.numeric); sort.fields.push(sortField); } collection.sort = sort; collection.refresh(); }
  22. 22. CollectionQuery /** * Creates result item * @param item * @return Row Item or anonymous object with properties generated from selectColumns variable */ private function createResultItem(item:Object):Object { if(_selectColumns == null) //return row item return item; var columns:Array = _selectColumns.split(","); //split columns if(columns.length == 1) return extractPropertyValue(item, columns[0]); //if only one property, than return it var res:QueryResultItem = new QueryResultItem(); //anonymous dynamic object for each(var column:String in columns) //populate anonymous object { var col:String = StringUtil.trim(column); var newPropname:String = col.replace(/[.]/g, '_'); res[newPropname] = extractPropertyValue(item, col); //set property value } return res; }
  23. 23. CollectionQuery /** * extracts property from given item */ private function extractPropertyValue(item:Object, property:String):Object { var single:String = StringUtil.trim(property); if(single.indexOf(".") == -1) { if( !item.hasOwnProperty(single) ) throw new ArgumentError("Property with name "" + single + "" does not exist!"); return item[single]; } //recursive call when extracting complex member’s properties return extractPropertyValue(item[property.substring(0, property.indexOf("."))], property.substring(property.indexOf(".") + 1)); }
  24. 24. CollectionQuery /** * Actual filtering logic * @param item collection item currently being validated * @param validationGroups condition groups to be validated with * @return Boolean * <ul> * <li>validation groups empty - return true</li> * <li>single item in validation group - return predicate validation result</li> * <li>iterate through all condition groups and its predicates and validate them all</li> * </ul> */ private function filterFunction(item:Object, validationGroups:ArrayCollection):Boolean { if(validationGroups.length == 0) return true; if(validationGroups.length == 1) return validatePredicates(item, _comparatorGroups[0].predicates); var result:Boolean = validatePredicates(item, _comparatorGroups[0].predicates); for (var i:int = 0; i < _comparatorGroups.length - 1; i++) { result = _comparatorGroups[i].operation == Predicate.OPERATOR_AND ? result && validatePredicates(item, _comparatorGroups[i + 1].predicates) : result || validatePredicates(item, _comparatorGroups[i + 1].predicates); } return result; }
  25. 25. What’s next  Optimization  Validation  Interface expansion  “In” and “Between” conditions  Joins  Comparison between two properties  Query reset  Nested conditions  And much more…
  26. 26. Also kewl to have - code snippets I <template autoinsert="true" context="" deleted="false" description="Query first item by the given conditions" enabled="true" name="query_first"> var query:IQuery = new CollectionQuery(${_collection}); var firstItem:${type} = query.where(new Conditions().${operation:values(eq, lt, lte, gt, gte, diff, contains)}("${prop}", ${value})).first() as ${type}; </template> <template autoinsert="true" context="" deleted="false" description="Order queried collection" enabled="true" name="query_order"> var query:IQuery = new CollectionQuery(${_collection}); var orderedResults:IList = query.where(new Conditions() .${operation:values(eq, lt, lte, gt, gte, diff, contains)}("${prop}", ${value})) .orderBy("${orderProp}", ${ascending:values(true, false)}, ${numeric:values(false, true)}) .execute() as IList; </template>
  27. 27. Code snippets II <template autoinsert="true" context="" deleted="false" description="Grouping query results" enabled="true" name="query_group"> var query:IQuery = new CollectionQuery(${_collection}); var groups:IQueryGroupResult = query .where(new Conditions() .${operation:values(eq, lt, lte, gt, gte, diff, contains)}("${prop}", ${value})) .groupBy("${groupProperty}") .execute() as IQueryGroupResult; for each(var key:Object in groups.keys) { var groupedItems:ArrayCollection = groups[key] as ArrayCollection; } </template>
  28. 28. Contact details  Email>