KYLIN-2807, enhance validate of datamodel's update

Project: http://git-wip-us.apache.org/repos/asf/kylin/repo
Commit: http://git-wip-us.apache.org/repos/asf/kylin/commit/d873ca13
Tree: http://git-wip-us.apache.org/repos/asf/kylin/tree/d873ca13
Diff: http://git-wip-us.apache.org/repos/asf/kylin/diff/d873ca13

Branch: refs/heads/2622-2764
Commit: d873ca13aa67a90a99b66d99659ca571a3ab977f
Parents: cd9a515
Author: Cheng Wang <cheng.w...@kyligence.io>
Authored: Thu Aug 24 11:12:50 2017 +0800
Committer: Hongbin Ma <m...@kyligence.io>
Committed: Thu Aug 24 11:27:44 2017 +0800

----------------------------------------------------------------------
 .../apache/kylin/rest/service/ModelService.java | 123 ++++++++++++++-----
 .../kylin/rest/service/ModelServiceTest.java    |  94 +++++++++++++-
 2 files changed, 179 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kylin/blob/d873ca13/server-base/src/main/java/org/apache/kylin/rest/service/ModelService.java
----------------------------------------------------------------------
diff --git 
a/server-base/src/main/java/org/apache/kylin/rest/service/ModelService.java 
b/server-base/src/main/java/org/apache/kylin/rest/service/ModelService.java
index 14b2280..82632eb 100644
--- a/server-base/src/main/java/org/apache/kylin/rest/service/ModelService.java
+++ b/server-base/src/main/java/org/apache/kylin/rest/service/ModelService.java
@@ -22,6 +22,7 @@ import static 
org.apache.kylin.rest.controller2.ModelControllerV2.VALID_MODELNAM
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -299,55 +300,112 @@ public class ModelService extends BasicService {
         return true;
     }
 
-    private boolean checkIfBreakExistingCubes(DataModelDesc dataModelDesc, 
String project) throws IOException {
+    private List<String> getModelCols(DataModelDesc model) {
+        List<String> dimCols = new ArrayList<String>();
+
+        List<ModelDimensionDesc> dimensions = model.getDimensions();
+
+        for (ModelDimensionDesc dim : dimensions) {
+            String table = dim.getTable();
+            for (String c : dim.getColumns()) {
+                dimCols.add(table + "." + c);
+            }
+        }
+        return dimCols;
+    }
+
+    private List<String> getModelMeasures(DataModelDesc model) {
+        List<String> measures = new ArrayList<String>();
+
+        for (String s : model.getMetrics()) {
+            measures.add(s);
+        }
+        return measures;
+    }
+
+    private Map<String, List<String>> getInfluencedCubesByDims(List<String> 
dims, List<CubeInstance> cubes) {
+        Map<String, List<String>> influencedCubes = new HashMap<>();
+        for (CubeInstance cubeInstance : cubes) {
+            CubeDesc cubeDesc = cubeInstance.getDescriptor();
+            for (TblColRef tblColRef : 
cubeDesc.listDimensionColumnsIncludingDerived()) {
+                if (dims.contains(tblColRef.getIdentity()))
+                    continue;
+                if (influencedCubes.get(tblColRef.getIdentity()) == null) {
+                    List<String> candidates = new ArrayList<>();
+                    candidates.add(cubeInstance.getName());
+                    influencedCubes.put(tblColRef.getIdentity(), candidates);
+                } else
+                    
influencedCubes.get(tblColRef.getIdentity()).add(cubeInstance.getName());
+            }
+        }
+        return influencedCubes;
+    }
+
+    private Map<String, List<String>> 
getInfluencedCubesByMeasures(List<String> allCols, List<CubeInstance> cubes) {
+        Map<String, List<String>> influencedCubes = new HashMap<>();
+        for (CubeInstance cubeInstance : cubes) {
+            CubeDesc cubeDesc = cubeInstance.getDescriptor();
+            Set<TblColRef> tblColRefs = 
Sets.newHashSet(cubeDesc.listAllColumns());
+            
tblColRefs.removeAll(cubeDesc.listDimensionColumnsIncludingDerived());
+            for (TblColRef tblColRef : tblColRefs) {
+                if (allCols.contains(tblColRef.getIdentity()))
+                    continue;
+                if (influencedCubes.get(tblColRef.getIdentity()) == null) {
+                    List<String> candidates = new ArrayList<>();
+                    candidates.add(cubeInstance.getName());
+                    influencedCubes.put(tblColRef.getIdentity(), candidates);
+                } else
+                    
influencedCubes.get(tblColRef.getIdentity()).add(cubeInstance.getName());
+            }
+        }
+        return influencedCubes;
+    }
+
+    private String checkIfBreakExistingCubes(DataModelDesc dataModelDesc, 
String project) throws IOException {
         String modelName = dataModelDesc.getName();
         List<CubeInstance> cubes = cubeService.listAllCubes(null, project, 
modelName, true);
+        DataModelDesc originDataModelDesc = listAllModels(modelName, project, 
true).get(0);
 
+        StringBuilder checkRet = new StringBuilder();
         if (cubes != null && cubes.size() != 0) {
             dataModelDesc.init(getConfig(), 
getMetadataManager().getAllTablesMap(dataModelDesc.getProject()),
                     getMetadataManager().listDataModels());
 
-            List<String> dimCols = new ArrayList<String>();
-            List<String> dimAndMCols = new ArrayList<String>();
+            List<String> curModelDims = getModelCols(dataModelDesc);
+            List<String> curModelMeasures = getModelMeasures(dataModelDesc);
 
-            List<ModelDimensionDesc> dimensions = 
dataModelDesc.getDimensions();
-            String[] measures = dataModelDesc.getMetrics();
+            List<String> curModelDimsAndMeasures = new ArrayList<>();
+            curModelDimsAndMeasures.addAll(curModelDims);
+            curModelDimsAndMeasures.addAll(curModelMeasures);
 
-            for (ModelDimensionDesc dim : dimensions) {
-                String table = dim.getTable();
-                for (String c : dim.getColumns()) {
-                    dimCols.add(table + "." + c);
-                }
-            }
+            Map<String, List<String>> influencedCubesByDims = 
getInfluencedCubesByDims(curModelDims, cubes);
+            Map<String, List<String>> influencedCubesByMeasures = 
getInfluencedCubesByMeasures(curModelDimsAndMeasures,
+                    cubes);
 
-            dimAndMCols.addAll(dimCols);
-
-            for (String measure : measures) {
-                dimAndMCols.add(measure);
+            for (Map.Entry<String, List<String>> e : 
influencedCubesByDims.entrySet()) {
+                checkRet.append("Dimension: ");
+                checkRet.append(e.getKey());
+                checkRet.append(" can't be removed, It is referred in Cubes: 
");
+                checkRet.append(e.getValue().toString());
+                checkRet.append("\r\n");
             }
 
-            Set<TblColRef> usedDimCols = getUsedDimCols(modelName, 
project).keySet();
-            Set<TblColRef> usedNonDimCols = getUsedNonDimCols(modelName, 
project).keySet();
-
-            for (TblColRef tblColRef : usedDimCols) {
-                if (!dimCols.contains(tblColRef.getTableAlias() + "." + 
tblColRef.getName()))
-                    return false;
+            for (Map.Entry<String, List<String>> e : 
influencedCubesByMeasures.entrySet()) {
+                checkRet.append("Measure: ");
+                checkRet.append(e.getKey());
+                checkRet.append(" can't be removed, It is referred in Cubes: 
");
+                checkRet.append(e.getValue().toString());
+                checkRet.append("\r\n");
             }
 
-            for (TblColRef tblColRef : usedNonDimCols) {
-                if (!dimAndMCols.contains(tblColRef.getTableAlias() + "." + 
tblColRef.getName()))
-                    return false;
-            }
-
-            DataModelDesc originDataModelDesc = listAllModels(modelName, 
project, true).get(0);
             if 
(!dataModelDesc.getRootFactTable().equals(originDataModelDesc.getRootFactTable()))
-                return false;
+                checkRet.append("Root fact table can't be modified. \r\n");
 
             JoinsTree joinsTree = dataModelDesc.getJoinsTree(), 
originJoinsTree = originDataModelDesc.getJoinsTree();
             if (joinsTree.matchNum(originJoinsTree) != 
originDataModelDesc.getJoinTables().length + 1)
-                return false;
+                checkRet.append("The join shouldn't be modified in this 
model.");
         }
-        return true;
+        return checkRet.toString();
     }
 
     public void primaryCheck(DataModelDesc modelDesc) {
@@ -383,8 +441,9 @@ public class ModelService extends BasicService {
                 modelDesc = createModelDesc(projectName, modelDesc);
             } else {
                 // update
-                if (!checkIfBreakExistingCubes(modelDesc, projectName)) {
-                    throw new 
BadRequestException(msg.getUPDATE_MODEL_KEY_FIELD());
+                String error = checkIfBreakExistingCubes(modelDesc, 
projectName);
+                if (!error.isEmpty()) {
+                    throw new BadRequestException(error);
                 }
                 modelDesc = updateModelAndDesc(modelDesc);
             }

http://git-wip-us.apache.org/repos/asf/kylin/blob/d873ca13/server/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java
----------------------------------------------------------------------
diff --git 
a/server/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java 
b/server/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java
index 78b1979..b51d90c 100644
--- a/server/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java
+++ b/server/src/test/java/org/apache/kylin/rest/service/ModelServiceTest.java
@@ -30,6 +30,7 @@ import org.apache.kylin.job.exception.JobException;
 import org.apache.kylin.metadata.MetadataManager;
 import org.apache.kylin.metadata.model.ComputedColumnDesc;
 import org.apache.kylin.metadata.model.DataModelDesc;
+import org.apache.kylin.metadata.model.ModelDimensionDesc;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
@@ -62,7 +63,8 @@ public class ModelServiceTest extends ServiceTestBase {
     }
 
     @Test
-    public void testSuccessModelUpdateOnComputedColumn() throws IOException, 
JobException, NoSuchFieldException, IllegalAccessException {
+    public void testSuccessModelUpdateOnComputedColumn()
+            throws IOException, JobException, NoSuchFieldException, 
IllegalAccessException {
 
         List<DataModelDesc> dataModelDescs = 
modelService.listAllModels("ci_left_join_model", "default", true);
         Assert.assertTrue(dataModelDescs.size() == 1);
@@ -79,9 +81,11 @@ public class ModelServiceTest extends ServiceTestBase {
     }
 
     @Test
-    public void testFailureModelUpdateDueToComputedColumnConflict() throws 
IOException, JobException, NoSuchFieldException, IllegalAccessException {
+    public void testFailureModelUpdateDueToComputedColumnConflict()
+            throws IOException, JobException, NoSuchFieldException, 
IllegalAccessException {
         expectedEx.expect(IllegalArgumentException.class);
-        expectedEx.expectMessage("Column name for computed column 
DEFAULT.TEST_KYLIN_FACT.DEAL_AMOUNT is already used in model 
ci_inner_join_model, you should apply the same expression ' PRICE * ITEM_COUNT 
' here, or use a different column name.");
+        expectedEx.expectMessage(
+                "Column name for computed column 
DEFAULT.TEST_KYLIN_FACT.DEAL_AMOUNT is already used in model 
ci_inner_join_model, you should apply the same expression ' PRICE * ITEM_COUNT 
' here, or use a different column name.");
 
         List<DataModelDesc> dataModelDescs = 
modelService.listAllModels("ci_left_join_model", "default", true);
         Assert.assertTrue(dataModelDescs.size() == 1);
@@ -97,11 +101,12 @@ public class ModelServiceTest extends ServiceTestBase {
         DataModelDesc dataModelDesc = 
modelService.updateModelAndDesc(deserialize);
     }
 
-
     @Test
-    public void testFailureModelUpdateDueToComputedColumnConflict2() throws 
IOException, JobException, NoSuchFieldException, IllegalAccessException {
+    public void testFailureModelUpdateDueToComputedColumnConflict2()
+            throws IOException, JobException, NoSuchFieldException, 
IllegalAccessException {
         expectedEx.expect(IllegalArgumentException.class);
-        expectedEx.expectMessage("There is already a column named cal_dt on 
table DEFAULT.TEST_KYLIN_FACT, please change your computed column name");
+        expectedEx.expectMessage(
+                "There is already a column named cal_dt on table 
DEFAULT.TEST_KYLIN_FACT, please change your computed column name");
 
         List<DataModelDesc> dataModelDescs = 
modelService.listAllModels("ci_left_join_model", "default", true);
         Assert.assertTrue(dataModelDescs.size() == 1);
@@ -116,4 +121,81 @@ public class ModelServiceTest extends ServiceTestBase {
         field.set(deserialize.getComputedColumnDescs().get(0), "cal_dt");
         DataModelDesc dataModelDesc = 
modelService.updateModelAndDesc(deserialize);
     }
+
+    @Test
+    public void testRevisableModelInCaseOfDeleteMeasure() throws IOException {
+        List<DataModelDesc> dataModelDescs = 
modelService.listAllModels("ci_left_join_model", "default", true);
+        Assert.assertTrue(dataModelDescs.size() == 1);
+
+        DataModelDesc revisableModel = dataModelDescs.get(0);
+
+        String[] originM = revisableModel.getMetrics();
+        String[] reviseM = cutItems(originM, 1);
+
+        revisableModel.setMetrics(reviseM);
+
+        
expectedEx.expect(org.apache.kylin.rest.exception.BadRequestException.class);
+        expectedEx.expectMessage(
+                "Measure: TEST_KYLIN_FACT.DEAL_AMOUNT can't be removed, It is 
referred in Cubes: [ci_left_join_cube]");
+        modelService.updateModelToResourceStore(revisableModel, "default");
+    }
+
+    @Test
+    public void testRevisableModelInCaseOfDeleteDims() throws IOException {
+        List<DataModelDesc> dataModelDescs = 
modelService.listAllModels("ci_left_join_model", "default", true);
+        Assert.assertTrue(dataModelDescs.size() == 1);
+
+        DataModelDesc revisableModel = dataModelDescs.get(0);
+
+        List<ModelDimensionDesc> originDims = revisableModel.getDimensions();
+        String[] reviseDims = cutItems(originDims.get(0).getColumns(), 2);
+        originDims.get(0).setColumns(reviseDims);
+        revisableModel.setDimensions(originDims);
+
+        
expectedEx.expect(org.apache.kylin.rest.exception.BadRequestException.class);
+        expectedEx.expectMessage(
+                "Dimension: TEST_KYLIN_FACT.SELLER_ID_AND_COUNTRY_NAME can't 
be removed, It is referred in Cubes: [ci_left_join_cube]");
+        modelService.updateModelToResourceStore(revisableModel, "default");
+    }
+
+    @Test
+    public void testRevisableModelInCaseOfMoveMeasureToDim() throws 
IOException {
+        List<DataModelDesc> dataModelDescs = 
modelService.listAllModels("ci_left_join_model", "default", true);
+        Assert.assertTrue(dataModelDescs.size() == 1);
+
+        DataModelDesc revisableModel = dataModelDescs.get(0);
+
+        String[] originM = revisableModel.getMetrics();
+        String[] reviseM = cutItems(originM, 1);
+        revisableModel.setMetrics(reviseM);
+
+        String col = originM[originM.length - 1];
+        col = col.substring(col.indexOf('.') + 1);
+
+        List<ModelDimensionDesc> originDims = revisableModel.getDimensions();
+        String[] reviseDims = addItems(originDims.get(0).getColumns(), col);
+        originDims.get(0).setColumns(reviseDims);
+        revisableModel.setDimensions(originDims);
+        modelService.updateModelToResourceStore(revisableModel, "default");
+        //It should pass without any exceptions.
+    }
+
+    private String[] cutItems(String[] origin, int count) {
+        if (origin == null)
+            return null;
+
+        String[] ret = new String[origin.length - count];
+        for (int i = 0; i < ret.length; i++)
+            ret[i] = origin[i];
+        return ret;
+    }
+
+    private String[] addItems(String[] origin, String... toAddItems) {
+        if (origin == null)
+            return null;
+        String[] ret = new String[origin.length + toAddItems.length];
+        System.arraycopy(origin, 0, ret, 0, origin.length);
+        System.arraycopy(toAddItems, 0, ret, origin.length, toAddItems.length);
+        return ret;
+    }
 }

Reply via email to