Package com.foundationdb.server.service.restdml

Source Code of com.foundationdb.server.service.restdml.UpsertProcessor

/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.service.restdml;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import com.foundationdb.ais.model.AkibanInformationSchema;
import com.foundationdb.ais.model.CacheValueGenerator;
import com.foundationdb.ais.model.Column;
import com.foundationdb.ais.model.Index;
import com.foundationdb.ais.model.PrimaryKey;
import com.foundationdb.ais.model.TableName;
import com.foundationdb.qp.operator.API;
import com.foundationdb.qp.operator.Cursor;
import com.foundationdb.qp.operator.Operator;
import com.foundationdb.qp.row.Row;
import com.foundationdb.server.error.InvalidChildCollectionException;
import com.foundationdb.server.error.KeyColumnMissingException;
import com.foundationdb.server.error.NoSuchIndexException;
import com.foundationdb.server.types.FormatOptions;
import com.foundationdb.server.types.service.TypesRegistryService;
import com.foundationdb.server.service.externaldata.ExternalDataService;
import com.foundationdb.server.service.externaldata.ExternalDataServiceImpl;
import com.foundationdb.server.service.externaldata.JsonRowWriter;
import com.foundationdb.server.service.externaldata.PlanGenerator;
import com.foundationdb.server.service.externaldata.TableRowTracker;
import com.foundationdb.server.service.externaldata.JsonRowWriter.WriteCapturePKRow;
import com.foundationdb.server.service.session.Session;
import com.foundationdb.server.store.SchemaManager;
import com.foundationdb.server.store.Store;
import com.foundationdb.server.types.value.Value;
import com.foundationdb.util.AkibanAppender;
import com.fasterxml.jackson.databind.JsonNode;

public class UpsertProcessor extends DMLProcessor {

    private final InsertProcessor insertProcessor;
    private final ExternalDataService extDataService;
    private FormatOptions options;
   
    public UpsertProcessor(Store store, SchemaManager schemaManager,
            TypesRegistryService typesRegistryService,
            InsertProcessor insertProcessor,
            ExternalDataService extDataService,
            FormatOptions options) {
        super(store, schemaManager, typesRegistryService);
        this.insertProcessor = insertProcessor;
        this.extDataService = extDataService;
        this.options = options;
    }

   
    public String processUpsert(Session session, AkibanInformationSchema ais, TableName tableName, JsonNode node) {
        ProcessContext context = new ProcessContext (ais, session, tableName);
        StringBuilder builder = new StringBuilder();
        AkibanAppender appender = AkibanAppender.of(builder);

        processContainer (node, appender, context);
       
        return appender.toString();

    }
   
    private void processContainer (JsonNode node, AkibanAppender appender, ProcessContext context) {
        boolean first = true;
       
        if (node.isObject()) {
            processRow (node, appender, context);
        } else if (node.isArray()) {
            appender.append('[');
            for (JsonNode arrayElement : node) {
                if (first) {
                    first = false;
                } else {
                    appender.append(',');
                }
                if (arrayElement.isObject()) {
                    processRow (arrayElement, appender, context);
                    context.queryBindings.clear();
                    context.allValues.clear();
                }
                // else throw Bad Json Format Exception
            }
            appender.append(']');
        } // else throw Bad Json Format Exception
    }
   
    private void processRow(JsonNode node, AkibanAppender appender, ProcessContext context) {
       
        if (context.table.getPrimaryKey() == null) {
            throw new NoSuchIndexException(Index.PRIMARY);
        }
       
        PrimaryKey pkIndex = context.table.getPrimaryKey();
        int pkFields = 0;
        Iterator<Entry<String,JsonNode>> i = node.fields();
        while (i.hasNext()) {
            Entry<String,JsonNode> field = i.next();
            if (field.getValue().isContainerNode()) {
                throw new InvalidChildCollectionException(field.getKey());
            } else if (field.getValue().isValueNode()) {
                Column column = getColumn (context.table, field.getKey());
                if (field.getValue().isNull()) {
                    context.allValues.put(column, null);
                } else {
                    context.allValues.put (column, field.getValue().asText());
                }
                if (pkIndex.getColumns().contains(column)) {
                    pkFields++;
                    // NB: PATCH requires all PK columns to be specified. As there is no
                    // DEFAULT but they also can't actually be NULL, treat it as not present.
                    if (field.getValue().isNull()) {
                        context.allValues.remove(column);
                        i.remove();
                    }
                }
            }
        }
       
        if (pkIndex.getColumns().size() != pkFields) {
            throw new KeyColumnMissingException(pkIndex.getIndex().getIndexName().toString());
        }
        Row row = determineExistance (context);
        if (row != null) {
            runUpdate (appender, context, row);
        } else {
            appender.append(insertProcessor.processInsert(context.session, context.table.getAIS(), context.tableName, node));
        }
    }

    private Row determineExistance (ProcessContext context) {
        PlanGenerator generator = context.table.getAIS().getCachedValue(extDataService, ExternalDataServiceImpl.CACHED_PLAN_GENERATOR);
        Operator plan = generator.generateAncestorPlan(context.table);
        Cursor cursor = null;
        try {

            Value value = new Value(context.typesTranslator.typeForString());
            int i = 0;
            for (Column column : context.table.getPrimaryKey().getColumns()) {
                // bug 1169995 - a null value in the PK won't match anything,
                // return null to force the insert.
                if (context.allValues.get(column) == null) {
                    return null;
                }
                value.putString(context.allValues.get(column), null);
                context.queryBindings.setValue(i, value);
                i++;
            }
            cursor = API.cursor(plan, context.queryContext, context.queryBindings);
            cursor.openTopLevel();
            return cursor.next();
        } finally {
            if (cursor != null && !cursor.isClosed()) {
                cursor.close();
            }
        }
    }
   
    private void runUpdate (AkibanAppender appender, ProcessContext context, Row oldRow) {
       
        UpdateGenerator updateGenerator = getGenerator(CACHED_UPDATE_GENERATOR, context);

        List<Column> pkList = context.table.getPrimaryKey().getColumns();
        List<Column> upList = new ArrayList<>();
        Value value = new Value(context.typesTranslator.typeForString());
        int i = pkList.size();
        for (Column column : context.table.getColumns()) {
            if (!pkList.contains(column) && context.allValues.containsKey(column)) {
                if (context.allValues.get(column) == null) {
                    value.putNull();
                } else {
                    value.putString(context.allValues.get(column), null);
                }
                context.queryBindings.setValue(i, value);
                upList.add(column);
                i++;
            }
        }
       
        Operator update = updateGenerator.create(context.tableName,upList);
        Cursor cursor = API.cursor(update, context.queryContext, context.queryBindings);
        JsonRowWriter writer = new JsonRowWriter(new TableRowTracker(context.table, 0));
        WriteCapturePKRow rowWriter = new WriteCapturePKRow();
        writer.writeRows(cursor, appender, "\n", rowWriter, options);
    }
   
    private static final CacheValueGenerator<UpdateGenerator> CACHED_UPDATE_GENERATOR =
        new CacheValueGenerator<UpdateGenerator>() {
            @Override
            public UpdateGenerator valueFor(AkibanInformationSchema ais) {
                return new UpdateGenerator(ais);
            }
        };
}
TOP

Related Classes of com.foundationdb.server.service.restdml.UpsertProcessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.