| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| |
| package mx.olap |
| { |
| |
| import flash.utils.getTimer; |
| |
| import mx.collections.ArrayCollection; |
| import mx.collections.CursorBookmark; |
| import mx.collections.IList; |
| import mx.collections.IViewCursor; |
| import mx.collections.ISort; |
| import mx.collections.ISortField; |
| import mx.collections.Sort; |
| import mx.collections.SortField; |
| import mx.core.mx_internal; |
| import mx.events.CubeEvent; |
| |
| use namespace mx_internal; |
| |
| /** |
| * @private |
| */ |
| public class DefaultCubeImpl implements IOLAPCubeImpl |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * Arrays holding indices of valid rows and columns. |
| * They are used to eliminate rows and columns which do not |
| * contain even a single valid value. |
| */ |
| private var validRows:Array; |
| private var validColumns:Array; |
| |
| /** |
| * @private |
| * Index of row currently being processed. |
| */ |
| private var queryCubeRowIndex:int; |
| |
| /** |
| * @private |
| * Index of column currently being processed. |
| */ |
| private var queryCubeColIndex:int; |
| |
| /** |
| * @private |
| * Indicates whether the query results cube has been built or not. |
| */ |
| private var queryCubeBuilt:Boolean; |
| |
| /** |
| * @private |
| * Indicates whether the tuples for the query has been built or not. |
| */ |
| private var queryTuplesBuilt:Boolean; |
| |
| /** |
| * @private |
| * progress and total values to be processed in the query |
| */ |
| private var _queryProgress:int = 0; |
| private var _queryTotal:int = 0; |
| |
| /** |
| * @private |
| * A single query tuple. |
| */ |
| private var queryTuple:OLAPTuple; |
| |
| private var queryAxisPositions:Array; |
| |
| // the column positions on the result's column axis |
| private var colPositions:Array; // array of OLAPAxisPositions |
| |
| // the row positions on the result's row axis |
| private var rowPositions:Array; // array of OLAPAxisPositions |
| |
| // the slicer positions on the result's slicer axis |
| private var slicerPositions:Array; |
| |
| // the result object containing the query result |
| private var newResult:OLAPResult; |
| |
| // the column axis of the result |
| private var colAxis:OLAPResultAxis; |
| |
| // the row axis of the result |
| private var rowAxis:OLAPResultAxis; |
| |
| // the slicer axis of the result |
| private var slicerAxis:OLAPResultAxis; |
| |
| // container for all the tuples in the query |
| private var queryTuples:Array; |
| |
| //same as rowPositions/columnPositions/slierPositions |
| private var colPos:IList; |
| private var rowPos:IList; |
| |
| //the members on the slicer axis which need to be removed |
| //from the tuple to read aggregation result from the query cube |
| private var removableSlicerMembers:IList; |
| |
| //if user has specified a measure on the slicer axis |
| //this one would point to it |
| private var measureInSlice:IOLAPMember; |
| |
| //private var tupleCubeMembers:Array = []; |
| |
| //array of all levels from all dimensions |
| private var levels:Array; //of IOLAPLevels; |
| |
| //flag to indicate whether we are done with preparing |
| //for building the cube |
| private var prepared:Boolean = false; |
| |
| //the bookmark to keep track of the data row we are processing |
| private var currentPosition:CursorBookmark = CursorBookmark.FIRST; |
| |
| //the dataProvider cursor |
| private var iterator:IViewCursor = null; |
| |
| //saved sort value |
| private var oldSort:ISort; |
| |
| //sort object used to gather members of dimensions |
| private var newSort:ISort; |
| |
| //Cube builder instance |
| private var nodeBuilder:CubeNodeBuilder; |
| |
| //query cube builder instance |
| private var queryCubeBuilder:QueryCubeBuilder; |
| |
| /** |
| * @private |
| * The function which should be invoked next, to build the query result. |
| * Each action function has the following signature. |
| * function action_fn_name(query:IOLAPQuery):Boolean |
| * Each action function performs its work and after completion switches |
| * the pointer to the next function that should be called. |
| * The last function to get called should set the pointer back to the |
| * 'prepareForNewQuery' function. |
| */ |
| private var actionFunction:Function = prepareForNewQuery; |
| |
| //flag indicates whether the query has a row axis or not |
| private var validRowPositions:Boolean = true; |
| |
| //query row count can be zero when user has specified only column axis |
| private var queryRowCount:int; |
| |
| //result row count is always > 0 |
| private var resultRowCount:int; |
| |
| //number of elements in the first query axis |
| private var queryColCount:int; |
| |
| // a container to keep track of latest indices of query axis |
| // positions while tuples are being generated |
| private var tupleIndexObject:Object = {}; |
| |
| //if true signals that we have finished generating |
| //all the tuples. |
| private var reachedEnd:Boolean = false; |
| |
| /*private var tempStartTime:int; |
| private var tOnce:Boolean; |
| private var cOnce:Boolean; |
| private var rOnce:Boolean; |
| |
| private var totalRunCount:int = 0; |
| private var totalTupleTime:Number = 0; |
| private var totalCubeTime:Number = 0; |
| private var totalResultTime:Number= 0; |
| */ |
| |
| protected var queryProgressEventThreashold:int = 1000; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| * The cube on which the query is being run. |
| */ |
| private var _cube:OLAPCube; |
| |
| public function set cube(c:IOLAPCube):void |
| { |
| _cube = c as OLAPCube; |
| } |
| |
| /** |
| * Returns the number of query tuples which have been processed |
| * in the current iteration. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get queryProgress():int |
| { |
| return _queryProgress; |
| } |
| |
| /** |
| * Returns the total number of query tuples being processed. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get queryTotal():int |
| { |
| return _queryTotal; |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Methods |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Populates the result object with axis and data values computed based |
| * on the query. The computation happens in steps/stages. |
| * Returns true when the result is completely computed. A return value of |
| * false indicates that the function needs to be called again to continue |
| * the operation. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function execute(query:IOLAPQuery, result:OLAPResult):Boolean |
| { |
| newResult = result; |
| |
| var startTime:int = getTimer(); |
| var actionResult:Boolean; |
| var timeTaken:int; |
| |
| // if total time taken for a action is less than 10 ms |
| // call the action function again |
| do |
| { |
| actionResult = actionFunction(query); |
| timeTaken = getTimer() - startTime; |
| } |
| while (!actionResult && timeTaken < 10); |
| |
| |
| return actionResult; |
| } |
| |
| /** |
| * @inheritDoc |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function cancelQuery(q:IOLAPQuery):void |
| { |
| actionFunction = prepareForNewQuery; |
| |
| //let us release all references |
| queryCubeBuilder = null; |
| validRows = validColumns = null; |
| queryTuples = null; |
| } |
| |
| /** |
| * @inheritDoc |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function cancelRefresh():void |
| { |
| prepared = false; |
| currentPosition = CursorBookmark.FIRST; |
| iterator = null; |
| |
| if (newSort) |
| { |
| _cube.dataProvider.sort = oldSort; |
| _cube.dataProvider.refresh(); |
| } |
| } |
| |
| public function refresh():void |
| { |
| //trace("Call buildCubeIteratively"); |
| //while (!buildCubeIteratively()); |
| } |
| |
| /** |
| * @private |
| * Updates the OLAP cube by processing one data row at a time. |
| * Returns true when cube is completely built. A return value of false |
| * indicates that this function needs to be called again to complete |
| * cube building. |
| */ |
| public function buildCubeIteratively():Boolean |
| { |
| if (!_cube.dataProvider) |
| return true; |
| |
| var level:OLAPLevel; |
| var attr:OLAPAttribute; |
| |
| if (!prepared) |
| { |
| levels = []; //of IOLAPLevels; |
| |
| // create as many levels as number of dimensions |
| for each (var dim:IOLAPDimension in _cube.dimensions) |
| { |
| //TODO should we skip this or just use this? |
| if (dim.isMeasure) |
| continue; |
| |
| for each (attr in dim.attributes) |
| { |
| //include attributes which are not present in the hierarchy |
| if (!attr.userHierarchyLevel) |
| levels = levels.concat(attr.levels.toArray()); |
| } |
| |
| for each (var h:OLAPHierarchy in dim.hierarchies) |
| { |
| levels = levels.concat(h.levels.toArray()); |
| } |
| } |
| |
| // sort the data as it makes decision about completion of handling |
| |
| oldSort = _cube.dataProvider.sort; |
| |
| //enable check to compare sort-cube performance |
| //if (!oldSort) |
| { |
| newSort = new Sort; |
| var fields:Array = []; |
| |
| var field:ISortField ; |
| for each (level in levels) |
| { |
| if (level.attribute && level.attribute.userDataFunction) |
| { |
| attr = level.attribute; |
| field= new SortField(attr.dataField); |
| field.compareFunction = attr.dataCompareFunction; |
| fields.push(field); |
| } |
| else |
| { |
| field= new SortField(level.dataField); |
| fields.push(field); |
| } |
| } |
| |
| newSort.fields = fields; |
| _cube.dataProvider.sort = newSort; |
| _cube.dataProvider.refresh(); |
| } |
| nodeBuilder = null; |
| queryCubeBuilder = null; |
| initNodeBuilder(); |
| prepared = true; |
| |
| iterator = _cube.dataProvider.createCursor(); |
| return false; |
| } |
| |
| // go through each row of data |
| iterator.seek(currentPosition); |
| |
| if (!iterator.afterLast && currentPosition != CursorBookmark.LAST) |
| { |
| var currentData:Object = iterator.current; |
| // Signals the builder to prepare itself for a new data row. |
| nodeBuilder.moveToNextRound(); |
| var values:Array = []; |
| for each (level in levels) |
| { |
| // go through each dataField in the order of dimension (TODO or user specified order) |
| var value:Object = level.dataFunction(currentData, level.dataField); |
| values.push(value); |
| } |
| nodeBuilder.addValueToNodeBuilder(values, currentData); |
| |
| iterator.moveNext(); |
| currentPosition = iterator.bookmark; |
| return false; |
| } |
| else |
| { |
| if (!completeNodeBuilder()) |
| return false; |
| prepared = false; |
| currentPosition = CursorBookmark.FIRST; |
| iterator = null; |
| |
| if (newSort) |
| { |
| _cube.dataProvider.sort = oldSort; |
| _cube.dataProvider.refresh(); |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * @private |
| * Method to be invoked repeatedly to generate tuples |
| * for each combination of row and column query axis position |
| * intersection. reachedEnd flag should be set to false to start |
| * a fresh round of iteration. |
| */ |
| private function getNextTuple():OLAPTuple |
| { |
| //if we have reached end returns null |
| if (reachedEnd) |
| return null; |
| |
| var tempTuple:OLAPTuple = queryTuple; |
| |
| //add members from all query axes. |
| var axisIndex:int = 0; |
| for each (var pos:Array in queryAxisPositions) |
| { |
| tempTuple.addMembers(pos[ tupleIndexObject[axisIndex] ].members); |
| ++axisIndex; |
| } |
| //adjust for the last increment |
| --axisIndex; |
| |
| //increment the axis index of the last query axis |
| //if the last element of the last query axis has been reached |
| //reset it to zero and increment the index of last-1 query axis |
| //do this till we reach the last element of the first query axis |
| var lastIndex:int = ++tupleIndexObject[axisIndex]; |
| if (lastIndex >= pos.length) |
| { |
| while(lastIndex >= pos.length) |
| { |
| tupleIndexObject[axisIndex] = 0; |
| ++tupleIndexObject[axisIndex-1]; |
| lastIndex = tupleIndexObject[axisIndex-1]; |
| --axisIndex; |
| //have we reached the last element of the first query axis? |
| if (axisIndex != -1) |
| pos = queryAxisPositions[axisIndex]; |
| else |
| { |
| //done! |
| reachedEnd = true; |
| break; |
| } |
| } |
| } |
| |
| return tempTuple; |
| } |
| |
| /** |
| * @private |
| * Builds a array of tuples to build the query cube. |
| */ |
| private function buildQueryTuples(query:IOLAPQuery):Boolean |
| { |
| var member:IOLAPMember; |
| |
| if (queryCubeColIndex < colPositions.length) |
| { |
| while (queryCubeRowIndex < queryRowCount || (!validRowPositions && queryCubeRowIndex == 0)) |
| { |
| do |
| { |
| //queryTuple would contain the resultant tuple |
| getNextTuple(); |
| |
| if (isTupleValid(queryTuple)) |
| { |
| validRows[queryCubeRowIndex] = 1; |
| validColumns[queryCubeColIndex] = 1; |
| |
| if (!queryTuples[queryCubeRowIndex][queryCubeColIndex]) |
| queryTuples[queryCubeRowIndex][queryCubeColIndex] = [ queryTuple ]; |
| else |
| { |
| queryTuples[queryCubeRowIndex][queryCubeColIndex].push(queryTuple); |
| } |
| //allocate new tuple for next iteration |
| queryTuple = new OLAPTuple(); |
| } |
| else |
| { |
| //we didn't use the tuple so we can reuse it |
| queryTuple.clear(); |
| } |
| } while(!reachedEnd && queryCubeColIndex == tupleIndexObject[0] && |
| queryCubeRowIndex == tupleIndexObject[1]); |
| |
| ++queryCubeRowIndex; |
| return false; |
| } |
| |
| queryCubeRowIndex = 0; |
| ++queryCubeColIndex; |
| return false; |
| } |
| |
| //remove unused columns and rows. |
| var n:int = validRows.length; |
| for (var i:int = n - 1; i > -1; --i) |
| { |
| if (validRows[i] == undefined) |
| { |
| rowPositions.splice(i, 1); |
| queryTuples.splice(i, 1); |
| } |
| } |
| |
| queryRowCount = rowPositions.length; |
| |
| n = validColumns.length; |
| for (i = n - 1; i > -1; --i) |
| { |
| if (validColumns[i] == undefined) |
| { |
| colPositions.splice(i, 1); |
| for each (var a:Array in queryTuples) |
| { |
| a.splice(i, 1); |
| } |
| } |
| } |
| |
| _queryProgress = 0; |
| queryCubeRowIndex = 0; |
| queryCubeColIndex = 0; |
| queryTuplesBuilt = true; |
| validRows = validColumns = null; |
| return true; |
| } |
| |
| /** |
| * @private |
| * Builds the query cube step by step. Returns false |
| * if the process is not complete and this function |
| * needs to be called again. |
| * |
| */ |
| private function buildQueryCube(query:IOLAPQuery):Boolean |
| { |
| var member:IOLAPMember; |
| var cubeToRead:CubeNode = rootNode; |
| //build the sub cube with the query result values |
| var tuple:OLAPTuple;// = new OLAPTuple; |
| |
| if (!queryCubeBuilder) |
| { |
| //prepare a new query result cube |
| initQueryNodeBuilder(); |
| |
| validRows = new Array(queryRowCount); |
| validColumns = new Array(colPositions.length); |
| queryCubeRowIndex = 0; |
| queryCubeColIndex = 0; |
| } |
| |
| if (queryCubeColIndex < colPositions.length) |
| { |
| var tupleMember:IOLAPMember; |
| while (queryCubeRowIndex < queryRowCount || (!validRowPositions && queryCubeRowIndex == 0)) |
| { |
| var tArray:Array = queryTuples[queryCubeRowIndex][queryCubeColIndex]; |
| |
| if (!tArray) |
| { |
| ++queryCubeRowIndex; |
| continue; |
| } |
| |
| for each (tuple in tArray) |
| { |
| var generatedTupleMembers:Array = tuple.membersArray; |
| |
| var value:* = cubeToRead; |
| |
| //skip the last measure level; |
| var memCount:int = generatedTupleMembers.length -1; |
| var tupleIndex:int; |
| var userMembers:Array = tuple.explicitMembers.toArray(); |
| validRows[queryCubeRowIndex] = 1; |
| validColumns[queryCubeColIndex] = 1; |
| |
| value = cubeToRead; |
| moveToNextRoundQuery(); |
| |
| var prevMemberName:String; |
| for (tupleIndex = 0; tupleIndex < memCount; ++tupleIndex) |
| { |
| tupleMember = generatedTupleMembers[tupleIndex]; |
| //check for member existence |
| //Is this check redundant as the tuple has already been validated? |
| if (value.hasOwnProperty(tupleMember.name)) |
| { |
| value = value[tupleMember.name]; |
| prevMemberName = tupleMember.uniqueName; |
| addValueToQueryNodeBuilder(tupleMember.uniqueName, value); |
| } |
| else |
| { |
| // remove the position from the axis |
| value = undefined; |
| break; |
| } |
| } |
| |
| if (value != undefined) |
| { |
| if (value is CubeNode) |
| value = value[allNodePropertyName]; |
| |
| var measure:OLAPMeasure = generatedTupleMembers[generatedTupleMembers.length-1] as OLAPMeasure; |
| addMeasureToQueryNodeBuilder(tupleMember.uniqueName, value, measure); |
| } |
| } |
| ++queryCubeRowIndex; |
| return false; |
| } |
| |
| queryCubeRowIndex = 0; |
| ++queryCubeColIndex; |
| return false; |
| } |
| |
| completeQueryNodeBuilding(); |
| |
| //remove unused columns and rows. |
| for (var temp:int = validRows.length-1; temp > -1; --temp) |
| { |
| if (validRows[temp] == undefined) |
| { |
| rowPositions.splice(temp, 1); |
| queryTuples.splice(temp, 1); |
| } |
| } |
| |
| queryRowCount = rowPositions.length; |
| |
| for (temp = validColumns.length-1; temp > -1; --temp) |
| { |
| if (validColumns[temp] == undefined) |
| { |
| colPositions.splice(temp, 1); |
| for each (var a:Array in queryTuples) |
| { |
| a.splice(temp, 1); |
| } |
| } |
| } |
| |
| _queryProgress = 0; |
| queryCubeRowIndex = 0; |
| queryCubeColIndex = 0; |
| queryCubeBuilt = true; |
| return true; |
| } |
| |
| /** |
| * @private |
| * Prepares the result object. |
| * |
| */ |
| private function prepareResult(query:IOLAPQuery):void |
| { |
| queryCubeBuilt = false; |
| queryTuplesBuilt = false; |
| colPositions = []; |
| rowPositions = []; |
| queryTuples = []; |
| queryTuple = new OLAPTuple(); |
| |
| queryAxisPositions = []; |
| |
| var member:IOLAPMember; |
| var position:OLAPAxisPosition; |
| var queryIndex:int = 0; |
| var queryAxis:OLAPQueryAxis = query.getAxis(queryIndex) as OLAPQueryAxis; |
| while (queryAxis) |
| { |
| var queryPositions:Array = []; |
| for each (var t:OLAPTuple in queryAxis.tuples) |
| { |
| position = new OLAPAxisPosition; |
| for each (member in t.explicitMembers) |
| position.addMember(member); |
| queryPositions.push(position); |
| } |
| |
| if (queryPositions.length) |
| { |
| queryAxisPositions.push(queryPositions); |
| |
| //add a new result axis |
| newResult.setAxis(queryIndex, new OLAPResultAxis()); |
| |
| //initialize the index object map |
| tupleIndexObject[queryIndex] = 0; |
| } |
| |
| ++queryIndex; |
| queryAxis = query.getAxis(queryIndex) as OLAPQueryAxis; |
| } |
| |
| colPositions = queryAxisPositions[0]; |
| |
| if (queryAxisPositions[1]) |
| rowPositions = queryAxisPositions[1]; |
| |
| colAxis = newResult.getAxis(0) as OLAPResultAxis; |
| rowAxis = newResult.getAxis(1) as OLAPResultAxis; |
| if (!rowAxis) |
| { |
| rowAxis = new OLAPResultAxis(); |
| newResult.setAxis(OLAPResult.ROW_AXIS, rowAxis); |
| } |
| |
| slicerAxis = newResult.getAxis(2) as OLAPResultAxis; |
| slicerPositions = queryAxisPositions[2]; |
| |
| } |
| |
| /** |
| * @private |
| * Dispatch the query progress event |
| * |
| */ |
| private function dispatchCubeProgress(progress:int, total:int, message:String=null):void |
| { |
| var ev:CubeEvent = new CubeEvent(CubeEvent.QUERY_PROGRESS); |
| ev.progress = progress; |
| ev.total = total; |
| if (!message) |
| ev.message = "Processing cell : " + progress + " of " + total; |
| ev.message = message; |
| _cube.dispatchEvent(ev); |
| } |
| |
| /** |
| * @private |
| * Insert a value into the object result. |
| * |
| */ |
| private function insertResult():Boolean |
| { |
| var mainCubeTuple:OLAPTuple;//= new OLAPTuple; |
| while (queryCubeColIndex < colPos.length) |
| { |
| while (queryCubeRowIndex < rowPos.length || (rowPos.length == 0 && queryCubeRowIndex ==0)) |
| { |
| var tArray:Array = queryTuples[queryCubeRowIndex][queryCubeColIndex]; |
| |
| if (!tArray) |
| { |
| ++queryCubeRowIndex; |
| continue; |
| } |
| |
| // we use the first tuple in the tuples array to read the resultant |
| // aggregation value |
| mainCubeTuple = tArray[0]; |
| |
| // we need to initialize it for each loop |
| var value:* = undefined; |
| |
| //attempt a read from the query cube. |
| if (queryRootNode) |
| { |
| // to read from the query cube we need to remove |
| // the slicer members as we need to only read |
| // the aggregated value. |
| if (removableSlicerMembers && removableSlicerMembers.length) |
| { |
| mainCubeTuple.removeElementsAtEnd(removableSlicerMembers.length); |
| if (measureInSlice) |
| mainCubeTuple.addMember(measureInSlice); |
| } |
| value = readCubeCell(queryRootNode, false, mainCubeTuple.membersArray); |
| } |
| //attempt a read from the main cube as we failed to read from the |
| //query cube. |
| else if (value == undefined) |
| { |
| //OLAPTrace.traceMsg("Reading from the main cube.", OLAPTrace.TRACE_LEVEL_2); |
| value = readCubeCell(rootNode, true, mainCubeTuple.membersArray); |
| } |
| |
| if (value != undefined) |
| { |
| validRows[queryCubeRowIndex] = 1; |
| validColumns[queryCubeColIndex] = 1; |
| newResult.setCell(queryCubeRowIndex, queryCubeColIndex, Number(value)); |
| } |
| |
| ++queryCubeRowIndex; |
| return false; |
| } |
| queryCubeRowIndex = 0; |
| ++queryCubeColIndex; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * @private |
| * Prepare for building the query tuples, query cube and query result. |
| * |
| */ |
| private function prepareForNewQuery(query:IOLAPQuery):Boolean |
| { |
| /* |
| ++totalRunCount; |
| tempStartTime = getTimer(); |
| tOnce = cOnce = rOnce = false; |
| */ |
| queryCubeBuilder = null; |
| prepareResult(query); |
| validRows = validColumns = null; |
| _queryProgress = 0; |
| |
| // Even if user has not specified a row axis |
| // we will have one row in the result |
| if (rowPositions.length) |
| { |
| validRowPositions = true; |
| queryRowCount = resultRowCount = rowPositions.length; |
| } |
| else |
| { |
| validRowPositions = false; |
| // we will have minimum of one row |
| resultRowCount = 1; |
| queryRowCount = 0; |
| } |
| |
| _queryTotal = resultRowCount * colPositions.length; |
| dispatchCubeProgress(_queryProgress, _queryTotal); |
| newResult.query = query; |
| actionFunction = buildQueryTuplesAction; |
| |
| |
| validRows = new Array(resultRowCount); |
| validColumns = new Array(colPositions.length); |
| queryCubeRowIndex = 0; |
| queryCubeColIndex = 0; |
| queryTuples = new Array(resultRowCount); |
| for (var index:int = 0; index < resultRowCount; ++index) |
| queryTuples[index] = new Array(colPositions.length); |
| reachedEnd = false; |
| return false; |
| } |
| |
| /** |
| * @private |
| * The action function which in turn calls the buildQueryTyples function. |
| * |
| */ |
| private function buildQueryTuplesAction(query:IOLAPQuery):Boolean |
| { |
| if (!buildQueryTuples(query)) |
| { |
| if (_cube.hasEventListener(CubeEvent.QUERY_PROGRESS)) |
| { |
| var newProgress:int = queryCubeColIndex * resultRowCount + queryCubeRowIndex; |
| if (newProgress - _queryProgress > queryProgressEventThreashold) |
| { |
| _queryProgress = newProgress; |
| dispatchCubeProgress(_queryProgress, _queryTotal, "First Pass - Processing cell : " + _queryProgress + " of " + _queryTotal); |
| } |
| } |
| return false; |
| } |
| |
| /* code for performance measurement. |
| if (!tOnce) |
| { |
| totalTupleTime += (getTimer() - tempStartTime); |
| trace("Time taken for building Query tuples:" + (totalTupleTime/totalRunCount)); |
| tempStartTime = getTimer(); |
| tOnce = true; |
| } |
| */ |
| |
| actionFunction = buildQueryCubeAction; |
| return false; |
| } |
| |
| /** |
| * @private |
| * The action function which in turn calls the buildQueryCube function. |
| * |
| */ |
| private function buildQueryCubeAction(query:IOLAPQuery):Boolean |
| { |
| if (slicerAxis) |
| { |
| if (!buildQueryCube(query)) |
| { |
| if (_cube.hasEventListener(CubeEvent.QUERY_PROGRESS)) |
| { |
| var newProgress:int = queryCubeColIndex * resultRowCount + queryCubeRowIndex; |
| if (newProgress - _queryProgress > queryProgressEventThreashold) |
| { |
| _queryProgress = newProgress; |
| dispatchCubeProgress(_queryProgress, _queryTotal, "Second Pass - Processing cell : " + _queryProgress + " of " + _queryTotal); |
| } |
| } |
| |
| return false; |
| } |
| } |
| /* code for performance measurement. |
| if (!cOnce) |
| { |
| totalCubeTime += getTimer() - tempStartTime; |
| trace("Time taken for building Query tuples + queryCube:" + (totalCubeTime/totalRunCount)); |
| tempStartTime = getTimer(); |
| cOnce = true; |
| } |
| */ |
| |
| actionFunction = buildQueryResultAction; |
| |
| prepareToGenerateResult(); |
| |
| return false; |
| } |
| |
| /** |
| * @private |
| * |
| */ |
| private function prepareToGenerateResult():void |
| { |
| var member:IOLAPMember; |
| if (queryCubeRowIndex == 0 && queryCubeColIndex == 0) |
| { |
| dispatchCubeProgress(_queryTotal, _queryTotal); |
| colAxis.positions = new ArrayCollection(colPositions); |
| colPos = colAxis.positions; |
| validColumns = new Array(colPos.length); |
| |
| rowAxis.positions = new ArrayCollection(rowPositions); |
| rowPos = rowAxis.positions; |
| validRows = new Array(rowPos.length); |
| |
| _queryProgress = 0; |
| _queryTotal = resultRowCount * colPositions.length; |
| |
| removableSlicerMembers = new ArrayCollection(); |
| |
| if (slicerAxis) |
| { |
| var slicerPos:IList = slicerAxis.positions = new ArrayCollection(slicerPositions); |
| |
| //gather members which need to be removed from tuples |
| var mems:ArrayCollection = slicerPos[0].members; |
| for (var mIndex:int = 0; mIndex < mems.length; ++mIndex) |
| { |
| member = mems.getItemAt(mIndex) as IOLAPMember; |
| removableSlicerMembers.addItem(member); |
| if (member.isMeasure) |
| measureInSlice = member; |
| } |
| } |
| } |
| } |
| |
| /** |
| * @private |
| * The action function which in turn calls the insertResult function. |
| * |
| */ |
| private function buildQueryResultAction(query:IOLAPQuery):Boolean |
| { |
| if (!insertResult()) |
| { |
| if (_cube.hasEventListener(CubeEvent.QUERY_PROGRESS)) |
| { |
| var newProgress:int = queryCubeColIndex * rowPos.length + queryCubeRowIndex; |
| if (newProgress - _queryProgress > queryProgressEventThreashold) |
| { |
| _queryProgress = newProgress; |
| dispatchCubeProgress(_queryProgress, _queryTotal, "Third Pass - Processing cell : " + _queryProgress + " of " + _queryTotal); |
| } |
| } |
| |
| return false; |
| } |
| |
| /* code for performance measurement. |
| if (!rOnce) |
| { |
| totalResultTime += (getTimer() - tempStartTime); |
| trace("Time taken for building Query tuples + queryCube + result :" + (totalResultTime/totalRunCount)); |
| tempStartTime = getTimer(); |
| rOnce = true; |
| } |
| */ |
| dispatchCubeProgress(_queryTotal, _queryTotal, "Third Pass - Processing cell : " + _queryTotal + " of " + _queryTotal); |
| |
| //remove unused columns and rows. |
| for (var temp:int = validRows.length-1; temp > -1; --temp) |
| { |
| if (validRows[temp] == undefined) |
| { |
| rowPos.removeItemAt(temp); |
| newResult.removeRowData(temp); |
| } |
| } |
| |
| for (temp = validColumns.length-1; temp > -1; --temp) |
| { |
| if (validColumns[temp] == undefined) |
| { |
| colPos.removeItemAt(temp); |
| newResult.removeColumnData(temp); |
| } |
| } |
| |
| //we are done with this query |
| actionFunction = prepareForNewQuery; |
| |
| //let us release all references |
| queryCubeBuilder = null; |
| validRows = validColumns = null; |
| queryTuples = null; |
| |
| return true; |
| } |
| |
| /** |
| * @private |
| * Returns a value reading it from the cube based on the members array |
| * describing the path to the cell that should be read. |
| * The mainCube flag indicates whether rootNode points to the main OLAP cube or |
| * a query cube. |
| */ |
| private function readCubeCell(rootNode:CubeNode, mainCube:Boolean, |
| tupleCubeMembers:Array):* |
| { |
| var value:* = rootNode; |
| for each (var index:IOLAPMember in tupleCubeMembers) |
| { |
| var memberName:String; |
| if (index.isMeasure) |
| { |
| memberName = index.name; |
| } |
| else if (index.isAll) |
| { |
| memberName = mainCube ? allNodePropertyName : allQueryNodePropertyName; |
| //if (!slicerAxis) |
| // memberName = index.uniqueName ; |
| } |
| else |
| { |
| memberName = mainCube ? index.name : index.uniqueName; |
| } |
| |
| if (value.hasOwnProperty(memberName)) |
| { |
| value = value[memberName]; |
| } |
| else |
| { |
| value = undefined; |
| break; |
| } |
| } |
| |
| if (value != undefined) |
| { |
| // user has stopped at this level |
| // pick the agg all value from the levels below. |
| if (value is CubeNode) |
| { |
| if (mainCube) |
| value = value[allNodePropertyName]; |
| else |
| value = value[allQueryNodePropertyName]; |
| } |
| |
| if (index is OLAPMeasure) |
| { |
| //user has chosen measure on the axis |
| //we will use this |
| //value = value[index.name]; |
| //if (!(value is Number)) |
| // trace("Invalid assumption about having a value"); |
| } |
| else |
| { |
| var measure:OLAPMeasure = _cube.findDimension("Measures").defaultMember as OLAPMeasure; |
| value = value[measure.name]; |
| } |
| } |
| |
| return value; |
| } |
| |
| /* |
| * @private |
| */ |
| mx_internal function sortData():void |
| { |
| for each (var dim:IOLAPDimension in _cube.dimensions) |
| { |
| for each (var hierarchy:OLAPHierarchy in dim.hierarchies) |
| { |
| levels = levels.concat(hierarchy.levels.toArray()); |
| } |
| } |
| |
| // sort the data as it makes decision about completion of handling |
| var newSort:ISort = new Sort; |
| var fields:Array = []; |
| |
| for each (var level:OLAPLevel in levels) |
| { |
| var field:ISortField = new SortField(level.dataField); |
| fields.push(field); |
| } |
| |
| newSort.fields = fields; |
| _cube.dataProvider.sort = newSort; |
| _cube.dataProvider.refresh(); |
| } |
| |
| // cube builder API |
| |
| /** |
| * @private |
| * Signals the start of main OLAP cube building. |
| * |
| */ |
| private function initNodeBuilder():void |
| { |
| if (!nodeBuilder) |
| { |
| nodeBuilder = new CubeNodeBuilder; |
| nodeBuilder.cube = _cube; |
| nodeBuilder.levelPreviousToMeasuresIndex = levels.length - 1; |
| } |
| nodeBuilder.initNodeBuilding(); |
| } |
| |
| /** |
| * @private |
| * Property Name used by the main cube in each node for a property, |
| * which points to the aggregation node of nodes below. |
| * |
| */ |
| private function get allNodePropertyName():String |
| { |
| return nodeBuilder.allNodePropertyName; |
| } |
| |
| /** |
| * Returns the root CubeNode of the main OLAP cube. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| mx_internal function get rootNode():CubeNode |
| { |
| return nodeBuilder.rootNode; |
| } |
| |
| /** |
| * @private |
| * |
| */ |
| private function addValueToNodeBuilder(value:Object, data:Object):void |
| { |
| nodeBuilder.addValueToNodeBuilder(value, data); |
| } |
| |
| /** |
| * @private |
| * Signals end of input data processing. The node builder |
| * finalizes the cube by computing aggregates. |
| * |
| */ |
| private function completeNodeBuilder():Boolean |
| { |
| return nodeBuilder.completeNodeBuilding(); |
| } |
| |
| /** |
| * @private |
| * QUERY cube builder API |
| */ |
| private function initQueryNodeBuilder():void |
| { |
| if (!queryCubeBuilder) |
| { |
| queryCubeBuilder = new QueryCubeBuilder; |
| queryCubeBuilder.cube = _cube; |
| } |
| queryCubeBuilder.initNodeBuilding(); |
| } |
| |
| /** |
| * @private |
| * Property Name used by the query cube in each node for a property, |
| * which points to the aggregation node of nodes below. |
| * |
| */ |
| private function get allQueryNodePropertyName():String |
| { |
| return queryCubeBuilder.allNodePropertyName; |
| } |
| |
| /** |
| * @private |
| * Returns the root node for the query cube. |
| * |
| */ |
| private function get queryRootNode():CubeNode |
| { |
| if (queryCubeBuilder) |
| return queryCubeBuilder.rootNode; |
| return null; |
| } |
| |
| /** |
| * @private |
| * Adds a value to the query cube. |
| * |
| */ |
| private function addValueToQueryNodeBuilder(value:Object, data:Object):void |
| { |
| queryCubeBuilder.addValueToNodeBuilder(value, data); |
| } |
| |
| /** |
| * @private |
| * Adds a measure value to the query cube. |
| * |
| */ |
| private function addMeasureToQueryNodeBuilder(value:Object, data:Object, measure:OLAPMeasure):void |
| { |
| queryCubeBuilder.addMeasureValueToNode(value, data, measure); |
| } |
| |
| /** |
| * @private |
| * Signals the queryCube builder to finalize the cube so that |
| * results can be read from it. |
| * |
| */ |
| private function completeQueryNodeBuilding():void |
| { |
| queryCubeBuilder.completeNodeBuilding(); |
| } |
| |
| /** |
| * @private |
| * Signals end of processing one tuple in the query. |
| */ |
| private function moveToNextRoundQuery():void |
| { |
| queryCubeBuilder.moveToNextRound(); |
| } |
| |
| /** |
| * A helper function which returns true if the Tuple addresses a valid |
| * cell in the cube. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| mx_internal function isTupleValid(tuple:OLAPTuple):Boolean |
| { |
| var generatedTupleMembers:Array = tuple.membersArray; |
| |
| var value:* = rootNode; |
| |
| //skip the last measure level; |
| var memCount:int = generatedTupleMembers.length -1; |
| var tupleMember:IOLAPMember; |
| for (var tupleIndex:int = 0; tupleIndex < memCount; ++tupleIndex) |
| { |
| tupleMember = generatedTupleMembers[tupleIndex]; |
| if (tupleMember.isAll) |
| { |
| value = value[allNodePropertyName]; |
| } |
| else |
| { |
| //move to the next level |
| value = value[tupleMember.name]; |
| //absense of any value indicates a invalid tuple. |
| if (value === undefined) |
| return false; |
| } |
| |
| } |
| return true; |
| } |
| } |
| } |