Repository: metamodel-membrane Updated Branches: refs/heads/master 5e7dcac0a -> a015e28cb
METAMODEL-1154: Added model for doing deletes and updates on table data Project: http://git-wip-us.apache.org/repos/asf/metamodel-membrane/repo Commit: http://git-wip-us.apache.org/repos/asf/metamodel-membrane/commit/aad039ce Tree: http://git-wip-us.apache.org/repos/asf/metamodel-membrane/tree/aad039ce Diff: http://git-wip-us.apache.org/repos/asf/metamodel-membrane/diff/aad039ce Branch: refs/heads/master Commit: aad039ce738d6309d1df58739c2ffac9ab722847 Parents: 5e7dcac Author: Kasper Sørensen <i.am.kasper.soren...@gmail.com> Authored: Thu Aug 10 23:16:14 2017 -0700 Committer: Kasper Sørensen <i.am.kasper.soren...@gmail.com> Committed: Thu Aug 10 23:18:38 2017 -0700 ---------------------------------------------------------------------- .../membrane/app/config/JacksonConfig.java | 11 +- .../file/FileBasedDataSourceRegistry.java | 4 +- .../controllers/TableDataController.java | 104 +++++++++++++++++-- core/src/main/resources/swagger.yaml | 85 +++++++++++++-- .../TenantInteractionScenarioTest.java | 4 +- 5 files changed, 179 insertions(+), 29 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/aad039ce/core/src/main/java/org/apache/metamodel/membrane/app/config/JacksonConfig.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/config/JacksonConfig.java b/core/src/main/java/org/apache/metamodel/membrane/app/config/JacksonConfig.java index a9ad0cd..d1c90da 100644 --- a/core/src/main/java/org/apache/metamodel/membrane/app/config/JacksonConfig.java +++ b/core/src/main/java/org/apache/metamodel/membrane/app/config/JacksonConfig.java @@ -27,10 +27,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; @Configuration public class JacksonConfig { + // use the JSON class from swagger-codegen + private static final ObjectMapper OBJECT_MAPPER = new JSON().getContext(Object.class);; + + public static ObjectMapper getObjectMapper() { + return OBJECT_MAPPER; + } + @Bean(name = "objectMapper") public ObjectMapper objectMapper() { - // use the JSON class from swagger-codegen - final JSON json = new JSON(); - return json.getContext(Object.class); + return getObjectMapper(); } } http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/aad039ce/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedDataSourceRegistry.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedDataSourceRegistry.java b/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedDataSourceRegistry.java index 6659292..74d1d3a 100644 --- a/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedDataSourceRegistry.java +++ b/core/src/main/java/org/apache/metamodel/membrane/app/registry/file/FileBasedDataSourceRegistry.java @@ -30,17 +30,17 @@ import org.apache.metamodel.DataContext; import org.apache.metamodel.factory.DataContextProperties; import org.apache.metamodel.membrane.app.DataContextSupplier; import org.apache.metamodel.membrane.app.DataSourceRegistry; +import org.apache.metamodel.membrane.app.config.JacksonConfig; import org.apache.metamodel.membrane.app.exceptions.DataSourceAlreadyExistException; import org.apache.metamodel.membrane.app.exceptions.NoSuchDataSourceException; import org.apache.metamodel.membrane.controllers.model.RestDataSourceDefinition; -import org.apache.metamodel.membrane.swagger.invoker.JSON; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Strings; public class FileBasedDataSourceRegistry implements DataSourceRegistry { - private static final ObjectMapper OBJECT_MAPPER = new JSON().getContext(Object.class); + private static final ObjectMapper OBJECT_MAPPER = JacksonConfig.getObjectMapper(); private static final String DATASOURCE_FILE_SUFFIX = ".json"; private static final String DATASOURCE_FILE_PREFIX = "ds_"; http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/aad039ce/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java b/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java index 91d2588..afbc37c 100644 --- a/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java +++ b/core/src/main/java/org/apache/metamodel/membrane/controllers/TableDataController.java @@ -28,14 +28,28 @@ import org.apache.metamodel.UpdateCallback; import org.apache.metamodel.UpdateScript; import org.apache.metamodel.UpdateSummary; import org.apache.metamodel.UpdateableDataContext; +import org.apache.metamodel.data.RowBuilder; +import org.apache.metamodel.data.WhereClauseBuilder; +import org.apache.metamodel.delete.RowDeletionBuilder; import org.apache.metamodel.insert.RowInsertionBuilder; import org.apache.metamodel.membrane.app.DataContextTraverser; import org.apache.metamodel.membrane.app.TenantContext; import org.apache.metamodel.membrane.app.TenantRegistry; -import org.apache.metamodel.membrane.swagger.model.InsertionResponse; +import org.apache.metamodel.membrane.app.config.JacksonConfig; +import org.apache.metamodel.membrane.swagger.model.Operator; +import org.apache.metamodel.membrane.swagger.model.PostDataRequest; +import org.apache.metamodel.membrane.swagger.model.PostDataRequestDelete; +import org.apache.metamodel.membrane.swagger.model.PostDataRequestUpdate; +import org.apache.metamodel.membrane.swagger.model.PostDataResponse; import org.apache.metamodel.membrane.swagger.model.QueryResponse; +import org.apache.metamodel.membrane.swagger.model.WhereCondition; +import org.apache.metamodel.query.FilterItem; +import org.apache.metamodel.query.OperatorType; import org.apache.metamodel.query.Query; +import org.apache.metamodel.query.SelectItem; +import org.apache.metamodel.schema.Column; import org.apache.metamodel.schema.Table; +import org.apache.metamodel.update.RowUpdationBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.PathVariable; @@ -46,6 +60,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; @RestController @@ -80,13 +95,13 @@ public class TableDataController { @RequestMapping(method = RequestMethod.POST) @ResponseBody - public InsertionResponse post(@PathVariable("tenant") String tenantId, + public PostDataResponse post(@PathVariable("tenant") String tenantId, @PathVariable("dataContext") String dataSourceName, @PathVariable("schema") String schemaId, - @PathVariable("table") String tableId, @RequestBody final List<Map<String, Object>> inputRecords) { + @PathVariable("table") String tableId, @RequestBody PostDataRequest postDataReq) { final TenantContext tenantContext = tenantRegistry.getTenantContext(tenantId); - final UpdateableDataContext dataContext = tenantContext.getDataSourceRegistry().openDataContextForUpdate( - dataSourceName); + final UpdateableDataContext dataContext = + tenantContext.getDataSourceRegistry().openDataContextForUpdate(dataSourceName); final DataContextTraverser traverser = new DataContextTraverser(dataContext); @@ -95,19 +110,47 @@ public class TableDataController { final UpdateSummary result = dataContext.executeUpdate(new UpdateScript() { @Override public void run(UpdateCallback callback) { - for (Map<String, Object> inputMap : inputRecords) { - final RowInsertionBuilder insert = callback.insertInto(table); - for (Entry<String, Object> entry : inputMap.entrySet()) { - insert.value(entry.getKey(), entry.getValue()); + final List<PostDataRequestUpdate> updateItems = postDataReq.getUpdate(); + if (updateItems != null) { + for (PostDataRequestUpdate updateItem : updateItems) { + final RowUpdationBuilder updateBuilder = callback.update(table); + setWhere(updateBuilder, table, updateItem.getWhere()); + setValues(updateBuilder, updateItem.getValues()); + updateBuilder.execute(); + } + } + + final List<PostDataRequestDelete> deleteItems = postDataReq.getDelete(); + if (deleteItems != null) { + for (PostDataRequestDelete deleteItem : deleteItems) { + final RowDeletionBuilder deleteBuilder = callback.deleteFrom(table); + setWhere(deleteBuilder, table, deleteItem.getWhere()); + deleteBuilder.execute(); + } + } + + final List<Object> insertItems = postDataReq.getInsert(); + if (insertItems != null) { + for (Object insertItem : insertItems) { + final RowInsertionBuilder insertBuild = callback.insertInto(table); + setValues(insertBuild, insertItem); + insertBuild.execute(); } - insert.execute(); } } }); - final InsertionResponse response = new InsertionResponse(); + final PostDataResponse response = new PostDataResponse(); response.status("ok"); + if (result.getDeletedRows().isPresent()) { + final Integer deletedRecords = result.getDeletedRows().get(); + response.deletedRows(new BigDecimal(deletedRecords)); + } + if (result.getUpdatedRows().isPresent()) { + final Integer updatedRecords = result.getUpdatedRows().get(); + response.updatedRows(new BigDecimal(updatedRecords)); + } if (result.getInsertedRows().isPresent()) { final Integer insertedRecords = result.getInsertedRows().get(); response.insertedRows(new BigDecimal(insertedRecords)); @@ -119,4 +162,43 @@ public class TableDataController { return response; } + + private void setWhere(WhereClauseBuilder<?> whereBuilder, Table table, List<WhereCondition> conditions) { + for (WhereCondition condition : conditions) { + final Column column = table.getColumnByName(condition.getColumn()); + if (column == null) { + throw new IllegalArgumentException("No such column: " + condition.getColumn()); + } + final OperatorType operator = toOperator(condition.getOperator()); + final FilterItem filterItem = new FilterItem(new SelectItem(column), operator, condition.getOperand()); + whereBuilder.where(filterItem); + } + } + + private OperatorType toOperator(Operator operator) { + switch (operator) { + case EQ: + return OperatorType.EQUALS_TO; + case NE: + return OperatorType.DIFFERENT_FROM; + case GT: + return OperatorType.GREATER_THAN; + case LT: + return OperatorType.LESS_THAN; + case LIKE: + return OperatorType.LIKE; + case NOT_LIKE: + return OperatorType.NOT_LIKE; + } + throw new UnsupportedOperationException("Unsupported operator: " + operator); + } + + protected void setValues(RowBuilder<?> rowBuilder, Object values) { + final ObjectMapper objectMapper = JacksonConfig.getObjectMapper(); + @SuppressWarnings("unchecked") final Map<String, ?> inputMap = objectMapper.convertValue(values, Map.class); + + for (Entry<String, ?> entry : inputMap.entrySet()) { + rowBuilder.value(entry.getKey(), entry.getValue()); + } + } } http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/aad039ce/core/src/main/resources/swagger.yaml ---------------------------------------------------------------------- diff --git a/core/src/main/resources/swagger.yaml b/core/src/main/resources/swagger.yaml index f183feb..1423ee7 100644 --- a/core/src/main/resources/swagger.yaml +++ b/core/src/main/resources/swagger.yaml @@ -76,7 +76,7 @@ paths: 200: description: Tenant deleted schema: - ref: "#/definitions/deleteTenantResponse" + $ref: "#/definitions/deleteTenantResponse" 404: description: Tenant not found schema: @@ -271,12 +271,12 @@ paths: description: The data to insert required: true schema: - $ref: "#/definitions/insertionRequest" + $ref: "#/definitions/postDataRequest" responses: 200: description: Data inserted schema: - $ref: "#/definitions/insertionResponse" + $ref: "#/definitions/postDataResponse" 404: description: Table not found schema: @@ -413,20 +413,83 @@ definitions: deleted: type: boolean description: A confirmation boolean to indicate that the deletion is effectuated. - insertionRequest: - type: array - items: - description: A record to insert where each key is expected to match a column name and each value is the value to put. - type: object - insertionResponse: - description: Represents the result of inserting records to a table + postDataRequest: + type: object + properties: + update: + type: array + items: + type: object + properties: + values: + type: object + description: Record values to update where each key is expected to match a column name and each value is the value to put. + example: + favorite_java_library: Apache MetaModel + where: + type: array + items: + $ref: "#/definitions/whereCondition" + delete: + type: array + items: + type: object + properties: + where: + type: array + items: + $ref: "#/definitions/whereCondition" + insert: + type: array + items: + description: A record to insert where each key is expected to match a column name and each value is the value to put. + type: object + example: + - fullname: Jane Doe + email_address: jane...@gmail.com + favourite_java_library: null + - fullname: John Doe + email_address: john...@gmail.com + favourite_java_library: "MetaModel" + whereCondition: + type: object + properties: + column: + type: string + operator: + $ref: "#/definitions/operator" + operand: + type: object + example: + column: fullname + operator: like + operand: "John%Doe" + operator: + type: string + enum: + - "eq" + - "ne" + - "gt" + - "lt" + - "like" + - "not_like" + postDataResponse: + description: Represents the result of posting a data update to the records to a table type: object required: - status properties: status: type: string - description: A confirmation 'ok' that the insertion went well. + description: A confirmation 'ok' that the data updates went well. + updated-rows: + type: number + format: int32 + description: The amount of updated records + deleted-rows: + type: number + format: int32 + description: The amount of deleted records inserted-rows: type: number format: int32 http://git-wip-us.apache.org/repos/asf/metamodel-membrane/blob/aad039ce/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java b/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java index eccff2c..b410e01 100644 --- a/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java +++ b/core/src/test/java/org/apache/metamodel/membrane/controllers/TenantInteractionScenarioTest.java @@ -162,7 +162,7 @@ public class TenantInteractionScenarioTest { // insert into table (x2) { final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post( - "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Howdy','who':'MetaModel'}]" + "/tenant1/mydata/s/mydata/t/hello_world/d").content("{'insert':[{'greeting':'Howdy','who':'MetaModel'}]}" .replace('\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect( MockMvcResultMatchers.status().isOk()).andReturn(); @@ -172,7 +172,7 @@ public class TenantInteractionScenarioTest { } { final MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post( - "/tenant1/mydata/s/mydata/t/hello_world/d").content("[{'greeting':'Hi','who':'Apache'}]".replace( + "/tenant1/mydata/s/mydata/t/hello_world/d").content("{'insert':[{'greeting':'Hi','who':'Apache'}]}".replace( '\'', '"')).contentType(MediaType.APPLICATION_JSON)).andExpect(MockMvcResultMatchers .status().isOk()).andReturn();