This is an automated email from the ASF dual-hosted git repository. xxyu pushed a commit to branch kylin5 in repository https://gitbox.apache.org/repos/asf/kylin.git
commit 4ab0273f45054ae7712689d456615f7658419cdd Author: Pengfei Zhan <pengfei.z...@kyligence.io> AuthorDate: Fri Oct 21 23:29:10 2022 +0800 KYLIN-5352 updating healthy model no need set id of the simplified measure --- .../kylin/rest/service/ModelSemanticHelper.java | 85 +++++++------ .../service/ModelServiceSemanticUpdateTest.java | 133 +++++++++++++++++---- .../kylin/rest/service/TableReloadServiceTest.java | 82 ++++++------- 3 files changed, 201 insertions(+), 99 deletions(-) diff --git a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelSemanticHelper.java b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelSemanticHelper.java index 9471316401..6e029b6b4e 100644 --- a/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelSemanticHelper.java +++ b/src/modeling-service/src/main/java/org/apache/kylin/rest/service/ModelSemanticHelper.java @@ -524,47 +524,12 @@ public class ModelSemanticHelper extends BasicService { unusedColumn.setStatus(NDataModel.ColumnStatus.TOMB); updateImpact.getRemovedOrUpdatedCCs().add(unusedColumn.getId()); }); - - Set<Integer> healthyExistedMeasures = Sets.newHashSet(); - List<String> illegalSimplifiedMeasures = Lists.newArrayList(); - - Map<String, Integer> nameToIdOfSimplified = Maps.newHashMap(); - Set<Integer> idOfSimplified = Sets.newHashSet(); - for (SimplifiedMeasure measure : request.getSimplifiedMeasures()) { - nameToIdOfSimplified.put(measure.getName(), measure.getId()); - if (measure.getId() != 0) { - idOfSimplified.add(measure.getId()); - } - } - - List<Measure> nonCountStarExistedMeasures = originModel.getAllMeasures().stream() - .filter(measure -> !measure.getName().equals("COUNT_ALL")).filter(measure -> !measure.isTomb()) - .collect(Collectors.toList()); - Map<String, Integer> nameToIdOfExistedModel = nonCountStarExistedMeasures.stream() - .collect(Collectors.toMap(MeasureDesc::getName, Measure::getId)); - nameToIdOfExistedModel.forEach((name, id) -> { - if (!nameToIdOfSimplified.containsKey(name)) { - if (idOfSimplified.contains(id)) { - healthyExistedMeasures.add(id); - } - } else if (nameToIdOfSimplified.get(name) == 0) { - illegalSimplifiedMeasures.add(name); - } else { - healthyExistedMeasures.add(id); - } - }); - - if (!illegalSimplifiedMeasures.isEmpty()) { - throw new KylinException(SIMPLIFIED_MEASURES_MISSING_ID, String.join(",", illegalSimplifiedMeasures)); + Set<String> allFunctions = originModel.getEffectiveMeasures().values().stream() + .map(measure -> measure.getFunction().toString()).collect(Collectors.toSet()); + if (allFunctions.size() != originModel.getEffectiveMeasures().size()) { + fixDupMeasureNames(originModel, request); } - nonCountStarExistedMeasures.stream() // - .filter(measure -> !healthyExistedMeasures.contains(measure.getId())) // - .forEach(measure -> { - log.warn("the measure({}) has been handled to tomb", measure.getName()); - measure.setTomb(true); - }); - // move deleted CC's measure to TOMB List<Measure> currentMeasures = originModel.getEffectiveMeasures().values().asList(); currentMeasures.stream().filter(measure -> { @@ -620,6 +585,48 @@ public class ModelSemanticHelper extends BasicService { return updateImpact; } + private void fixDupMeasureNames(NDataModel originModel, ModelRequest request) { + Set<Integer> healthyExistedMeasures = Sets.newHashSet(); + List<String> illegalSimplifiedMeasures = Lists.newArrayList(); + + Map<String, Integer> nameToIdOfSimplified = Maps.newHashMap(); + Set<Integer> idOfSimplified = Sets.newHashSet(); + for (SimplifiedMeasure measure : request.getSimplifiedMeasures()) { + nameToIdOfSimplified.put(measure.getName(), measure.getId()); + if (measure.getId() != 0) { + idOfSimplified.add(measure.getId()); + } + } + + List<Measure> nonCountStarExistedMeasures = originModel.getAllMeasures().stream() + .filter(measure -> !measure.getName().equals("COUNT_ALL")).filter(measure -> !measure.isTomb()) + .collect(Collectors.toList()); + Map<String, Integer> nameToIdOfExistedModel = nonCountStarExistedMeasures.stream() + .collect(Collectors.toMap(MeasureDesc::getName, Measure::getId)); + nameToIdOfExistedModel.forEach((name, id) -> { + if (!nameToIdOfSimplified.containsKey(name)) { + if (idOfSimplified.contains(id)) { + healthyExistedMeasures.add(id); + } + } else if (nameToIdOfSimplified.get(name) == 0) { + illegalSimplifiedMeasures.add(name); + } else { + healthyExistedMeasures.add(id); + } + }); + + if (!illegalSimplifiedMeasures.isEmpty()) { + throw new KylinException(SIMPLIFIED_MEASURES_MISSING_ID, String.join(",", illegalSimplifiedMeasures)); + } + + nonCountStarExistedMeasures.stream() // + .filter(measure -> !healthyExistedMeasures.contains(measure.getId())) // + .forEach(measure -> { + log.warn("the measure({}) has been handled to tomb", measure.getName()); + measure.setTomb(true); + }); + } + /** * one measure in expectedModel but not in originModel then add one * one in expectedModel, is also a TOMB one in originModel, set status to not TOMB diff --git a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceSemanticUpdateTest.java b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceSemanticUpdateTest.java index c39e2153ac..f0ddc80d02 100644 --- a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceSemanticUpdateTest.java +++ b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/ModelServiceSemanticUpdateTest.java @@ -20,13 +20,17 @@ package org.apache.kylin.rest.service; import static org.apache.kylin.common.exception.code.ErrorCodeServer.SIMPLIFIED_MEASURES_MISSING_ID; import static org.hamcrest.Matchers.is; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import org.apache.kylin.engine.spark.job.ExecutableAddCuboidHandler; -import org.apache.kylin.engine.spark.job.NSparkCubingJob; -import lombok.extern.slf4j.Slf4j; -import lombok.val; -import lombok.var; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.commons.collections.CollectionUtils; import org.apache.kylin.common.KylinConfig; @@ -36,6 +40,8 @@ import org.apache.kylin.common.util.JsonUtil; import org.apache.kylin.common.util.NLocalFileMetadataTestCase; import org.apache.kylin.cube.model.SelectRule; import org.apache.kylin.engine.spark.ExecutableUtils; +import org.apache.kylin.engine.spark.job.ExecutableAddCuboidHandler; +import org.apache.kylin.engine.spark.job.NSparkCubingJob; import org.apache.kylin.job.execution.AbstractExecutable; import org.apache.kylin.job.execution.ExecutableState; import org.apache.kylin.job.execution.NExecutableManager; @@ -50,6 +56,7 @@ import org.apache.kylin.metadata.cube.model.NDataflowUpdate; import org.apache.kylin.metadata.cube.model.NIndexPlanManager; import org.apache.kylin.metadata.cube.model.RuleBasedIndex; import org.apache.kylin.metadata.model.ComputedColumnDesc; +import org.apache.kylin.metadata.model.FunctionDesc; import org.apache.kylin.metadata.model.ManagementType; import org.apache.kylin.metadata.model.MeasureDesc; import org.apache.kylin.metadata.model.NDataModel; @@ -57,7 +64,9 @@ import org.apache.kylin.metadata.model.NDataModel.ColumnStatus; import org.apache.kylin.metadata.model.NDataModel.Measure; import org.apache.kylin.metadata.model.NDataModel.NamedColumn; import org.apache.kylin.metadata.model.NDataModelManager; +import org.apache.kylin.metadata.model.ParameterDesc; import org.apache.kylin.metadata.model.PartitionDesc; +import org.apache.kylin.metadata.project.EnhancedUnitOfWork; import org.apache.kylin.metadata.realization.RealizationStatusEnum; import org.apache.kylin.metadata.recommendation.candidate.JdbcRawRecStore; import org.apache.kylin.rest.constant.Constant; @@ -84,16 +93,14 @@ import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.util.ReflectionTestUtils; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; + +import lombok.val; +import lombok.var; +import lombok.extern.slf4j.Slf4j; @Slf4j public class ModelServiceSemanticUpdateTest extends NLocalFileMetadataTestCase { @@ -411,25 +418,111 @@ public class ModelServiceSemanticUpdateTest extends NLocalFileMetadataTestCase { @Test public void testRenameTableAliasUsedWithSimplifiedMeasure() throws IOException { - val modelManager = NDataModelManager.getInstance(getTestConfig(), getProject()); + String project = getProject(); + val modelManager = NDataModelManager.getInstance(getTestConfig(), project); modelManager.listAllModels().forEach(modelManager::dropModel); val request = JsonUtil.readValue( getClass().getResourceAsStream("/ut_request/model_update/model_with_measure.json"), ModelRequest.class); request.setAlias("model_with_measure"); - val newModel = modelService.createModel(request.getProject(), request); + val newModel = modelService.createModel(project, request); + EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> { + // prepare dirty model + NDataModelManager modelMgr = NDataModelManager.getInstance(getTestConfig(), project); + modelMgr.updateDataModel(newModel.getId(), copyForWrite -> { + List<Measure> allMeasures = copyForWrite.getAllMeasures(); + Measure measure = new Measure(); + measure.setId(100002); + measure.setType(NDataModel.MeasureType.NORMAL); + measure.setName("MAX2"); + FunctionDesc function = new FunctionDesc(); + function.setExpression("MAX"); + function.setReturnType("integer"); + function.setConfiguration(Maps.newLinkedHashMap()); + ParameterDesc parameter = new ParameterDesc(); + parameter.setType("column"); + parameter.setValue("TEST_ACCOUNT.ACCOUNT_SELLER_LEVEL"); + parameter.setColRef(allMeasures.get(0).getFunction().getParameters().get(0).getColRef()); + function.setParameters(ImmutableList.of(parameter)); + measure.setFunction(function); + allMeasures.add(measure); + }); + return null; + }, project); val updateRequest = JsonUtil.readValue( getClass().getResourceAsStream("/ut_request/model_update/model_with_measure_change_alias.json"), ModelRequest.class); updateRequest.setAlias("model_with_measure_change_alias"); updateRequest.setUuid(newModel.getUuid()); + List<SimplifiedMeasure> simplifiedMeasures = updateRequest.getSimplifiedMeasures(); + simplifiedMeasures.get(0).setId(100000); + simplifiedMeasures.get(1).setId(100001); + SimplifiedMeasure simplifiedMeasure = new SimplifiedMeasure(); + ParameterResponse param = new ParameterResponse(); + param.setType("column"); + param.setValue("TEST_ACCOUNT.ACCOUNT_SELLER_LEVEL"); + simplifiedMeasure.setParameterValue(ImmutableList.of(param)); + simplifiedMeasure.setExpression("MAX"); + simplifiedMeasure.setName("MAX2"); + simplifiedMeasure.setReturnType("integer"); + simplifiedMeasures.add(simplifiedMeasure); try { - modelService.updateDataModelSemantic(getProject(), updateRequest); + modelService.updateDataModelSemantic(project, updateRequest); Assert.fail(); } catch (KylinException e) { Assert.assertEquals(SIMPLIFIED_MEASURES_MISSING_ID.getErrorCode().getCode(), e.getErrorCodeString()); } } + @Test + public void testMockFixDirtyModelWhenSaving() throws IOException { + val modelManager = NDataModelManager.getInstance(getTestConfig(), getProject()); + modelManager.listAllModels().forEach(modelManager::dropModel); + val request = JsonUtil.readValue( + getClass().getResourceAsStream("/ut_request/model_update/model_with_measure.json"), ModelRequest.class); + request.setAlias("model_with_measure"); + val newModel = modelService.createModel(request.getProject(), request); + EnhancedUnitOfWork.doInTransactionWithCheckAndRetry(() -> { + // prepare dirty model + NDataModelManager modelMgr = NDataModelManager.getInstance(getTestConfig(), getProject()); + modelMgr.updateDataModel(newModel.getId(), copyForWrite -> { + List<Measure> allMeasures = copyForWrite.getAllMeasures(); + Measure measure = new Measure(); + measure.setId(100002); + measure.setType(NDataModel.MeasureType.NORMAL); + measure.setName("MAX2"); + FunctionDesc function = new FunctionDesc(); + function.setExpression("MAX"); + function.setReturnType("integer"); + function.setConfiguration(Maps.newLinkedHashMap()); + ParameterDesc parameter = new ParameterDesc(); + parameter.setType("column"); + parameter.setValue("TEST_ACCOUNT.ACCOUNT_SELLER_LEVEL"); + parameter.setColRef(allMeasures.get(0).getFunction().getParameters().get(0).getColRef()); + function.setParameters(ImmutableList.of(parameter)); + measure.setFunction(function); + allMeasures.add(measure); + }); + return null; + }, getProject()); + + // set max2 to tomb + val updateRequest = JsonUtil.readValue( + getClass().getResourceAsStream("/ut_request/model_update/model_with_measure_change_alias.json"), + ModelRequest.class); + updateRequest.setAlias("model_with_measure_change_alias"); + updateRequest.setUuid(newModel.getUuid()); + List<SimplifiedMeasure> simplifiedMeasures = updateRequest.getSimplifiedMeasures(); + simplifiedMeasures.get(0).setId(100000); + simplifiedMeasures.get(1).setId(100001); + modelService.updateDataModelSemantic(getProject(), updateRequest); + + NDataModel modifiedModel = modelManager.getDataModelDesc(newModel.getUuid()); + List<Measure> allMeasures = modifiedModel.getAllMeasures(); + Optional<Measure> max2 = allMeasures.stream().filter(measure -> measure.getName().equals("MAX2")).findFirst(); + Assert.assertTrue(max2.isPresent()); + Assert.assertTrue(max2.get().isTomb()); + } + @Test public void testRenameTableAliasUsedAsMeasure() throws Exception { val modelManager = NDataModelManager.getInstance(getTestConfig(), getProject()); diff --git a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableReloadServiceTest.java b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableReloadServiceTest.java index 7008996d7d..91e1b2a83e 100644 --- a/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableReloadServiceTest.java +++ b/src/modeling-service/src/test/java/org/apache/kylin/rest/service/TableReloadServiceTest.java @@ -17,16 +17,26 @@ */ package org.apache.kylin.rest.service; -import com.google.common.base.Joiner; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import io.kyligence.kap.clickhouse.MockSecondStorage; -import org.apache.kylin.engine.spark.job.NSparkCubingJob; -import org.apache.kylin.engine.spark.job.NTableSamplingJob; -import io.kyligence.kap.secondstorage.SecondStorageUtil; -import lombok.extern.slf4j.Slf4j; -import lombok.val; -import lombok.var; +import static org.apache.kylin.common.exception.code.ErrorCodeServer.TABLE_RELOAD_HAVING_NOT_FINAL_JOB; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.is; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.apache.commons.collections.CollectionUtils; import org.apache.kylin.common.KylinConfig; import org.apache.kylin.common.exception.KylinException; @@ -36,6 +46,8 @@ import org.apache.kylin.common.persistence.transaction.UnitOfWork; import org.apache.kylin.common.scheduler.EventBusFactory; import org.apache.kylin.common.util.JsonUtil; import org.apache.kylin.cube.model.SelectRule; +import org.apache.kylin.engine.spark.job.NSparkCubingJob; +import org.apache.kylin.engine.spark.job.NTableSamplingJob; import org.apache.kylin.job.execution.AbstractExecutable; import org.apache.kylin.job.execution.JobTypeEnum; import org.apache.kylin.job.execution.NExecutableManager; @@ -82,25 +94,15 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.test.util.ReflectionTestUtils; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; -import static org.apache.kylin.common.exception.code.ErrorCodeServer.TABLE_RELOAD_HAVING_NOT_FINAL_JOB; -import static org.awaitility.Awaitility.await; -import static org.hamcrest.CoreMatchers.is; +import io.kyligence.kap.clickhouse.MockSecondStorage; +import io.kyligence.kap.secondstorage.SecondStorageUtil; +import lombok.val; +import lombok.var; +import lombok.extern.slf4j.Slf4j; @Slf4j public class TableReloadServiceTest extends CSVSourceTestCase { @@ -628,7 +630,7 @@ public class TableReloadServiceTest extends CSVSourceTestCase { Assert.assertNotNull(reModel); Assert.assertFalse(reModel.isBroken()); Assert.assertEquals(9, reModel.getJoinTables().size()); - Assert.assertEquals(18, reModel.getAllMeasures().size()); + Assert.assertEquals(17, reModel.getAllMeasures().size()); Assert.assertEquals(198, reModel.getAllNamedColumns().size()); Assert.assertEquals("ORDER_ID", reModel.getAllNamedColumns().get(13).getName()); Assert.assertEquals(NDataModel.ColumnStatus.TOMB, reModel.getAllNamedColumns().get(13).getStatus()); @@ -690,7 +692,7 @@ public class TableReloadServiceTest extends CSVSourceTestCase { Assert.assertNotNull(reModel); Assert.assertFalse(reModel.isBroken()); Assert.assertEquals(9, reModel.getJoinTables().size()); - Assert.assertEquals(18, reModel.getAllMeasures().size()); + Assert.assertEquals(17, reModel.getAllMeasures().size()); Assert.assertEquals(198, reModel.getAllNamedColumns().size()); Assert.assertEquals("CAL_DT", reModel.getAllNamedColumns().get(2).getName()); Assert.assertEquals("DEAL_YEAR", reModel.getAllNamedColumns().get(28).getName()); @@ -740,7 +742,7 @@ public class TableReloadServiceTest extends CSVSourceTestCase { Assert.assertNotNull(reModel); Assert.assertFalse(reModel.isBroken()); Assert.assertEquals(9, reModel.getJoinTables().size()); - Assert.assertEquals(18, reModel.getAllMeasures().size()); + Assert.assertEquals(17, reModel.getAllMeasures().size()); Assert.assertEquals(198, reModel.getAllNamedColumns().size()); Assert.assertEquals("CAL_DT", reModel.getAllNamedColumns().get(2).getName()); Assert.assertEquals("DEAL_YEAR", reModel.getAllNamedColumns().get(28).getName()); @@ -1054,8 +1056,8 @@ public class TableReloadServiceTest extends CSVSourceTestCase { executableManager.addJob(job); removeColumn(tableIdentity, "TEST_TIME_ENC"); - OpenPreReloadTableResponse response = tableService.preProcessBeforeReloadWithoutFailFast(PROJECT, - tableIdentity, false); + OpenPreReloadTableResponse response = tableService.preProcessBeforeReloadWithoutFailFast(PROJECT, tableIdentity, + false); Assert.assertTrue(response.isHasEffectedJobs()); Assert.assertEquals(1, response.getEffectedJobs().size()); Assert.assertThrows(TABLE_RELOAD_HAVING_NOT_FINAL_JOB.getMsg(job.getId()), KylinException.class, @@ -1071,8 +1073,8 @@ public class TableReloadServiceTest extends CSVSourceTestCase { job.setJobType(JobTypeEnum.TABLE_SAMPLING); executableManager.addJob(job); addColumn(tableIdentity, true, new ColumnDesc("", "TEST_COL", "int", "", "", "", null)); - OpenPreReloadTableResponse response = tableService.preProcessBeforeReloadWithoutFailFast(PROJECT, - tableIdentity, false); + OpenPreReloadTableResponse response = tableService.preProcessBeforeReloadWithoutFailFast(PROJECT, tableIdentity, + false); Assert.assertTrue(response.isHasEffectedJobs()); Assert.assertEquals(1, response.getEffectedJobs().size()); tableService.preProcessBeforeReloadWithFailFast(PROJECT, tableIdentity); @@ -1091,8 +1093,8 @@ public class TableReloadServiceTest extends CSVSourceTestCase { put("SLR_SEGMENT_CD", "bigint"); } }, true); - OpenPreReloadTableResponse response = tableService.preProcessBeforeReloadWithoutFailFast(PROJECT, - tableIdentity, false); + OpenPreReloadTableResponse response = tableService.preProcessBeforeReloadWithoutFailFast(PROJECT, tableIdentity, + false); Assert.assertTrue(response.isHasEffectedJobs()); Assert.assertEquals(1, response.getEffectedJobs().size()); Assert.assertThrows(TABLE_RELOAD_HAVING_NOT_FINAL_JOB.getMsg(job.getId()), KylinException.class, @@ -1626,7 +1628,7 @@ public class TableReloadServiceTest extends CSVSourceTestCase { } @Test - public void testReloadAWSTableCompatibleCrossAccountNoSample(){ + public void testReloadAWSTableCompatibleCrossAccountNoSample() { S3TableExtInfo tableExtInfo = prepareTableExtInfo("DEFAULT.TEST_ORDER", "endpoint", "role"); prepareTableExt("DEFAULT.TEST_ORDER"); tableService.reloadAWSTableCompatibleCrossAccount(PROJECT, tableExtInfo, false, 10000, true, 3, null); @@ -1648,13 +1650,13 @@ public class TableReloadServiceTest extends CSVSourceTestCase { } @Test(expected = Exception.class) - public void testReloadAWSTableCompatibleCrossAccountNeedSample(){ + public void testReloadAWSTableCompatibleCrossAccountNeedSample() { S3TableExtInfo tableExtInfo = prepareTableExtInfo("DEFAULT.TEST_ORDER", "endpoint", "role"); prepareTableExt("DEFAULT.TEST_ORDER"); tableService.reloadAWSTableCompatibleCrossAccount(PROJECT, tableExtInfo, true, 10000, true, 3, null); } - private S3TableExtInfo prepareTableExtInfo(String dbTable, String endpoint, String role){ + private S3TableExtInfo prepareTableExtInfo(String dbTable, String endpoint, String role) { S3TableExtInfo tableExtInfo = new S3TableExtInfo(); tableExtInfo.setName(dbTable); tableExtInfo.setEndpoint(endpoint);