package org.olap4j.driver.olap4ld.linkeddata; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.olap4j.OlapException; import org.olap4j.driver.olap4ld.helper.Olap4ldLinkedDataUtil; import org.semanticweb.yars.nx.Node; public class LogicalToPhysical { // Input to OLAP operator private List<Node[]> slicedDimensions = new ArrayList<Node[]>(); private List<Node[]> rollupslevels = new ArrayList<Node[]>(); private List<Node[]> rollupshierarchies = new ArrayList<Node[]>(); private List<List<Node[]>> membercombinations = new ArrayList<List<Node[]>>(); private List<Node[]> hierarchysignature = new ArrayList<Node[]>(); private List<Node[]> projectedMeasures = new ArrayList<Node[]>(); private LinkedDataCubesEngine engine; public LogicalToPhysical(LinkedDataCubesEngine embeddedSesameEngine) { this.engine = embeddedSesameEngine; } /** * * * Design decisions The LogicalOperators only get as input a cube and the * parameters, but not specifically the metadata of the cube. * * The PhysicalIterators get as input another iterator and the parameters, * but also the metadata of the iterator. I create PhysicalIterators for * metadata, they get as input the repo and the parameters (restrictions). * Every LogicalOperator could be wrapped, get as input the identifier of a * cube and the parameters and return a new identifier. * * Mioeur2eur is another LogicalOperator that could get wrapped, get as * input the identifier of a cube and the parameters and return a new * identifier. Mioeur2eur would also include PhysicalIterators. The * Mioeur2eur iterator would get as input another iterator, * BaseCubeIterator, and return the conversion. Example: * http://olap4ld.googlecode.com/dic/aggreg95#00; * http://olap4ld.googlecode.com/dic/geo#US; * http://olap4ld.googlecode.com/dic/indic_na#VI_PPS_EU28_HAB; 2012; 149; * */ /** * * This method recursively creates a physical query plan from a logical one. * * @param node * @return */ public PhysicalOlapIterator compile(LogicalOlapOp node) { PhysicalOlapIterator theIterator = null; // If we have Drill-Across, we recursively compile and use nested loop. if (node instanceof DrillAcrossOp) { LogicalOlapOp inputop1 = ((DrillAcrossOp) node).inputop1; LogicalOlapOp inputop2 = ((DrillAcrossOp) node).inputop2; PhysicalOlapIterator iterator1; PhysicalOlapIterator iterator2; PhysicalOlapIterator intermediateIterator; /* * Otherwise: * * * We may have Slice/Dice/etc. over BaseCube -> No problem. * We * may have Convert-Cube etc. over BaseCube -> No problem. * We may * have Drill-Across over BaseCube -> Problem. */ if (inputop1 instanceof BaseCubeOp) { intermediateIterator = compile(inputop1); iterator1 = createOlap2SparqlIterator(intermediateIterator); } else { iterator1 = compile(inputop1); } if (inputop2 instanceof BaseCubeOp) { intermediateIterator = compile(inputop2); iterator2 = createOlap2SparqlIterator(intermediateIterator); } else { iterator2 = compile(inputop2); } theIterator = new DrillAcrossNestedLoopJoinSesameIterator( iterator1, iterator2); } // The thing is, we may have SPARQL2OLAP over Convert-Cube or over // Base-Cube. if (node instanceof RollupOp || node instanceof SliceOp || node instanceof DiceOp || node instanceof ProjectionOp) { LogicalOlapOp inputop = null; if (node instanceof RollupOp) { // For rollup, RollupOp ro = (RollupOp) node; // Question is, are there sliced dimensions contained, already? // No. Only // rolled-up dimensions. // Are there dimensions mentioned, that are not roll-up but stay // on the // lowest level? Should be. this.rollupslevels = ro.rollupslevels; this.rollupshierarchies = ro.rollupshierarchies; inputop = ro.inputOp; } if (node instanceof SliceOp) { SliceOp so = (SliceOp) node; // we do not need to do anything with SliceOp // We assume that slice is called only once per query plan. this.slicedDimensions = so.slicedDimensions; inputop = so.inputOp; } if (node instanceof DiceOp) { DiceOp dop = (DiceOp) node; // Question: Is that corresponding to OLAP-2-SPARQL algorithm? // Yes. this.membercombinations = dop.membercombinations; // Hierarchy signature needed for max_level_height this.hierarchysignature = dop.hierarchysignature; inputop = dop.inputOp; } if (node instanceof ProjectionOp) { ProjectionOp po = (ProjectionOp) node; this.projectedMeasures = po.projectedMeasures; inputop = po.inputOp; } // Check inputop. if (inputop instanceof BaseCubeOp || inputop instanceof ConvertCubeOp) { PhysicalOlapIterator iterator1 = compile(inputop); theIterator = createOlap2SparqlIterator(iterator1); } else { theIterator = compile(inputop); } } if (node instanceof ConvertCubeOp) { ConvertCubeOp so = (ConvertCubeOp) node; if (so.inputOp2 == null) { PhysicalOlapIterator iterator = compile(so.inputOp1); theIterator = new ConvertSparqlDerivedDatasetIterator(engine, iterator, null, so.conversioncorrespondence); } else if (so.inputOp1 == so.inputOp2) { // If both operators are the same, we can reuse the iterator. // Unfortunately, this does not work for further nested equal // operators. // If inside a logical olap query plan the same function is run // on // the same dataset, we // still have a problem that it is run twice. we could have a // hashmap storing // previous operators and reusing them if needed. PhysicalOlapIterator iterator = compile(so.inputOp1); theIterator = new ConvertSparqlDerivedDatasetIterator(engine, iterator, iterator, so.conversioncorrespondence); } else { PhysicalOlapIterator iterator1 = compile(so.inputOp1); PhysicalOlapIterator iterator2 = compile(so.inputOp2); theIterator = new ConvertSparqlDerivedDatasetIterator(engine, iterator1, iterator2, so.conversioncorrespondence); } } if (node instanceof BaseCubeOp) { theIterator = new BaseCubeSparqlDerivedDatasetIterator(engine, ((BaseCubeOp) node).dataseturi); // XXX: Not sure whether that will make problems. // theIterator = createOlap2SparqlIterator(theIterator); } return theIterator; } private PhysicalOlapIterator createOlap2SparqlIterator( PhysicalOlapIterator iterator1) { PhysicalOlapIterator theIterator = null; try { Restrictions restrictions = new Restrictions(); List<Node[]> measures = iterator1.getMeasures(restrictions); List<Node[]> dimensions = iterator1.getDimensions(restrictions); // List<Node[]> hierarchies = // iterator1.getHierarchies(restrictions); List<Node[]> levels = iterator1.getLevels(restrictions); // List<Node[]> members = iterator1.getMembers(restrictions); // Build slicesrollups Map<String, Integer> dimensionmap = Olap4ldLinkedDataUtil .getNodeResultFields(dimensions.get(0)); Map<String, Integer> levelmap = Olap4ldLinkedDataUtil .getNodeResultFields(levels.get(0)); // First, we have to create slicesrollups List<Node[]> slicesrollups = new ArrayList<Node[]>(); slicesrollups.add(levels.get(0)); List<Integer> levelheights = new ArrayList<Integer>(); // Header levelheights.add(-1); /* * Note, since we start from the list of dimensions of the cube, the * result dimensions will be in the ordering of the cube (and not * the MDX query). */ // Find dimensions not in sliced and not in rolluplevel (that are // rolled-up). List<Node[]> basedimensions = new ArrayList<Node[]>(); // Header basedimensions.add(dimensions.get(0)); boolean first = true; // We check all dimensions of the input dataset for (Node[] dimension : dimensions) { if (first) { first = false; continue; } // If in rollupslevels, add and continue. boolean containedInRollupsLevels = false; for (int i = 1; i < rollupslevels.size(); i++) { Map<String, Integer> rollupshierarchiesmap = Olap4ldLinkedDataUtil .getNodeResultFields(rollupshierarchies.get(0)); Node[] rolluplevel = rollupslevels.get(i); Node[] rollupshierarchy = rollupshierarchies.get(i); if (dimension[dimensionmap.get("?DIMENSION_UNIQUE_NAME")] .toString().equals( rolluplevel[levelmap .get("?DIMENSION_UNIQUE_NAME")] .toString())) { containedInRollupsLevels = true; slicesrollups.add(rolluplevel); /* * This is how you get levelHeight: * HIERARCHY_MAX_LEVEL_NUMBER - LEVEL_NUMBER. OR: * Hierarchy.maxdepth - Level.depth. */ Integer levelHeight = (new Integer( rollupshierarchy[rollupshierarchiesmap .get("?HIERARCHY_MAX_LEVEL_NUMBER")] .toString()) - new Integer( rolluplevel[levelmap.get("?LEVEL_NUMBER")] .toString())); levelheights.add(levelHeight); continue; } } boolean containedInSlicedDimensions = false; // We check whether a sliced dimension. for (Node[] sliceddimension : slicedDimensions) { if (dimension[dimensionmap.get("?DIMENSION_UNIQUE_NAME")] .toString().equals( sliceddimension[dimensionmap .get("?DIMENSION_UNIQUE_NAME")] .toString())) { containedInSlicedDimensions = true; } } // As usual also check whether Measure dimension. if (!containedInRollupsLevels && !containedInSlicedDimensions && !dimension[dimensionmap .get("?DIMENSION_UNIQUE_NAME")] .toString() .equals(Olap4ldLinkedDataUtil.MEASURE_DIMENSION_NAME)) { basedimensions.add(dimension); } } // Find lowest level of basedimensions and add first = true; for (Node[] basedimension : basedimensions) { if (first) { first = false; continue; } // Search for lowest level Node[] baselevel = null; first = true; for (Node[] level : levels) { if (first) { first = false; continue; } if (basedimension[dimensionmap .get("?DIMENSION_UNIQUE_NAME")].toString().equals( level[levelmap.get("?DIMENSION_UNIQUE_NAME")] .toString())) { if (baselevel == null) { baselevel = level; } int baselevelnumber = new Integer( baselevel[levelmap.get("?LEVEL_NUMBER")] .toString()); int levelnumber = new Integer( level[levelmap.get("?LEVEL_NUMBER")].toString()); if (baselevelnumber < levelnumber) { baselevel = level; } } } slicesrollups.add(baselevel); levelheights.add(0); } // Tests? // slicesrollups should contain a dimension for each apart from // slices. // Remember measure dimension that never gets sliced or rolled-up // Get number of non-measure dimensions int nonMeasureDimensions = dimensions.size() - 2; // Get size of sliced dimensions: int slicedDimensionsNumber; if (slicedDimensions.isEmpty()) { slicedDimensionsNumber = 0; } else { slicedDimensionsNumber = slicedDimensions.size() - 1; } int slicesrollupsshouldbesize = nonMeasureDimensions - slicedDimensionsNumber; if (slicesrollups.size() - 1 != slicesrollupsshouldbesize) { throw new UnsupportedOperationException( "Slicesrollups not properly created!"); } // Create projections List<Node[]> projections; if (projectedMeasures.isEmpty()) { projections = measures; } else { projections = projectedMeasures; } // Currently, we should have retrieved the data, already, therefore, // we // only have one node. // We use the OLAP-2-SPARQL algorithm. theIterator = new Olap2SparqlAlgorithmIterator(iterator1, this.engine, slicesrollups, levelheights, projections, membercombinations, hierarchysignature); } catch (OlapException e) { // TODO Auto-generated catch block e.printStackTrace(); } return theIterator; } }