Skip to content
This repository has been archived by the owner on Jun 29, 2021. It is now read-only.

METAMODEL-1154: Added model for doing deletes and updates on table data #4

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -27,10 +27,15 @@
@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();
}
}
Expand Up @@ -30,17 +30,17 @@
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_";

Expand Down
Expand Up @@ -28,14 +28,28 @@
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;
Expand All @@ -46,6 +60,7 @@
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
Expand Down Expand Up @@ -80,13 +95,13 @@ public QueryResponse get(@PathVariable("tenant") String tenantId,

@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);

Expand All @@ -95,19 +110,47 @@ public InsertionResponse post(@PathVariable("tenant") String tenantId,
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));
Expand All @@ -119,4 +162,43 @@ public void run(UpdateCallback callback) {

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());
}
}
}
85 changes: 74 additions & 11 deletions core/src/main/resources/swagger.yaml
Expand Up @@ -76,7 +76,7 @@ paths:
200:
description: Tenant deleted
schema:
ref: "#/definitions/deleteTenantResponse"
$ref: "#/definitions/deleteTenantResponse"
404:
description: Tenant not found
schema:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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: janedoe@gmail.com
favourite_java_library: null
- fullname: John Doe
email_address: johndoe@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
Expand Down
Expand Up @@ -162,7 +162,7 @@ public void testScenario() throws Exception {
// 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();

Expand All @@ -172,7 +172,7 @@ public void testScenario() throws Exception {
}
{
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();

Expand Down