Package org.neo4j.community.console

Source Code of org.neo4j.community.console.CypherQueryExecutor$CypherResult

package org.neo4j.community.console;

import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.cypher.javacompat.PlanDescription;
import org.neo4j.cypher.javacompat.QueryStatistics;
import org.neo4j.cypher.javacompat.internal.ServerExecutionEngine;
import org.neo4j.graphdb.*;
import org.neo4j.graphdb.Transaction;
import org.neo4j.helpers.collection.IteratorUtil;
import org.neo4j.helpers.collection.MapUtil;
import org.neo4j.kernel.GraphDatabaseAPI;
import org.neo4j.kernel.impl.util.StringLogger;
import scala.NotImplementedError;

import javax.transaction.*;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* @author mh
* @since 21.04.12
*/
public class CypherQueryExecutor {
    private static final Pattern PROPERTY_PATTERN = Pattern.compile("((\\w+)\\s*:|\\w+\\.(\\w+)\\s*=)",Pattern.MULTILINE|Pattern.DOTALL);
    private static final Pattern INDEX_PATTERN = Pattern.compile("(node|relationship)\\s*:\\s*(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}+|`[^`]+`|)\\s*\\(",Pattern.MULTILINE);
    public static final Pattern CANNOT_PROFILE_PATTERN = Pattern.compile("\\b(UNION|OPTIONAL|LOAD)\\b|(\\bMERGE\\b.+){2,}", Pattern.CASE_INSENSITIVE|Pattern.MULTILINE|Pattern.DOTALL);
    private final TransactionManager transactionManager;
    private ServerExecutionEngine executionEngine;
    private final Index index;
  private final GraphDatabaseService gdb;
    public static final int CYPHER_LENGTH = "CYPHER".length();

    public CypherQueryExecutor(GraphDatabaseService gdb, Index index) {
      this.gdb = gdb;
        transactionManager = ((GraphDatabaseAPI) gdb).getDependencyResolver().resolveDependency(TransactionManager.class);
        this.index = index;
        executionEngine = new ServerExecutionEngine(gdb, StringLogger.SYSTEM);
    }

    public boolean isMutatingQuery(String query) {
        return query.matches("(?is).*\\b(create|relate|merge|delete|set)\\b.*");
    }
    public boolean isIndexQuery(String query) {
        return query.matches("(?is).*\\bcreate (index|constraint)\\b.*");
    }
    public boolean isCypherQuery(String query) {
        return query.matches("(?is).*\\b(drop|start|merge|match|return|where|skip|limit|create|relate|delete|set)\\b.*");
    }

    public static class CypherResult implements Iterable<Map<String, Object>> {
        private final List<String> columns;
        private final String query;
        private final String text;
        private final Collection<Map<String, Object>> rows;
        private final List<Map<String, Object>> json;
        private QueryStatistics queryStatistics;
        private final PlanDescription plan;
        private final long time;

        public CypherResult(List<String> columns, Collection<Map<String, Object>> rows, QueryStatistics queryStatistics, long time, PlanDescription plan, String query) {
            this.query = query;
            this.columns = new ArrayList<>(columns);
            this.queryStatistics = queryStatistics;
            this.time = time;
            this.rows = rows;
            this.plan = plan;
            this.text = generateText();
            this.json = createJson();
        }

        public List<String> getColumns() {
            return columns;
        }

        public String getQuery() {
            return query;
        }

        public int getRowCount() {
            return rows.size();
        }
       
        public Map getQueryStatistics() {
            final Map<String, Object> stats = MapUtil.map(
                    "rows", getRowCount(),
                    "time", getTime()
            );
            if (queryStatistics!=null && queryStatistics.containsUpdates()) {
                stats.put("containsUpdates", queryStatistics.containsUpdates());
                stats.put("nodesDeleted", queryStatistics.getDeletedNodes());
                stats.put("relationshipsDeleted", queryStatistics.getDeletedRelationships());
                stats.put("nodesCreated", queryStatistics.getNodesCreated());
                stats.put("relationshipsCreated", queryStatistics.getRelationshipsCreated());
                stats.put("propertiesSet", queryStatistics.getPropertiesSet());
                stats.put("text", queryStatistics.toString());
            }
            return stats;
        }
       
        public String getText() {
            return text;
        }

        private String generateText() {
            return new ResultPrinter().generateText(columns, rows, time, queryStatistics);
        }

        public Collection<Map<String, Object>> getRows() {
            return rows;
        }

        public String getPlan() {
            return plan!=null ? plan.toString() : "No Query Plan";
        }

        @Override
        public String toString() {
            return getText();
        }

        @Override
        public Iterator<Map<String, Object>> iterator() {
            return rows.iterator();
        }

        public List<Map<String,Object>> getJson() {
            return json;
        }

        private List<Map<String, Object>> createJson() {
            final List<Map<String, Object>> rows = new ArrayList<>();
            for (Map<String, Object> row : this) {
                final LinkedHashMap<String, Object> newRow = new LinkedHashMap<>();
                for (String column : columns) {
                    final Object value = row.get(column);
                    newRow.put(column, toJsonCompatible(value));
                }
                rows.add(newRow);
            }
            return rows;
        }

        private Object toJsonCompatible(Object value) {
            if (value instanceof Node) {
                final Node node = (Node) value;
                final Map<String, Object> result = SubGraph.toMap((PropertyContainer)node);
                result.put("_id",node.getId());

                final List<String> labelNames = SubGraph.getLabelNames(node);
                if (!labelNames.isEmpty()) result.put("_labels", labelNames);
                return result;
            }
            if (value instanceof Relationship) {
                final Relationship relationship = (Relationship) value;
                final Map<String, Object> result = SubGraph.toMap((PropertyContainer) relationship);
                result.put("_id",relationship.getId());
                result.put("_start",relationship.getStartNode().getId());
                result.put("_end",relationship.getEndNode().getId());
                result.put("_type",relationship.getType().name());
                return result;
            }
            if (value instanceof Map) {
                @SuppressWarnings("unchecked") Map<String,Object> map = (Map<String,Object>) value;
                final Map<String,Object> result = new LinkedHashMap<>(map.size());
                for (Map.Entry<String,Object> entry : map.entrySet()) {
                    result.put(entry.getKey(), toJsonCompatible(entry.getValue()));
                }
                return result;
            }
            if (value instanceof Iterable) {
                final List<Object> result = new ArrayList<>();
                for (Object inner : (Iterable)value) {
                    result.add(toJsonCompatible(inner));
                }
                return result;
            }
            return value;
        }

        public long getTime() {
            return time;
        }
    }

    public String prettify(String query) {
        return executionEngine.prettify(query).replaceAll("\n","\n ");
    }
    public CypherResult cypherQuery(String query, String version, Map<String, Object> params) {
        // query = replaceIndex(query);
        if (version==null || version.isEmpty() || startsWithCypher(query)) return cypherQuery(query,params);
        return cypherQuery("CYPHER "+version+" "+query, params);
    }
    public CypherResult cypherQuery(String query, String version) {
        return cypherQuery(query,version,null);
    }

    private boolean startsWithCypher(String query) {
        String q = query.trim();
        return q.length() > CYPHER_LENGTH && q.substring(0, CYPHER_LENGTH).equalsIgnoreCase("cypher");
    }

    private CypherResult cypherQuery(String query, Map<String, Object> params) {
        if (isMutatingQuery(query)) {
            registerProperties(query);
        }
        boolean canProfile = canProfileQuery(query);
        try {
            return doExecuteQuery(query, params, canProfile);
        } catch (NotImplementedError |AssertionError e) {
            return doExecuteQuery(query, params, false);
        }
    }

    private CypherResult doExecuteQuery(String query, Map<String, Object> params, boolean canProfile) {
        params = params == null ? Collections.<String,Object>emptyMap() : params;
        long time=System.currentTimeMillis();
        Transaction tx = gdb.beginTx();
        javax.transaction.Transaction resumeTx;
        try {
            resumeTx = suspendTx(query);
            ExecutionResult result = canProfile ? executionEngine.profile(query,params) : executionEngine.execute(query,params);
            final Collection<Map<String, Object>> data = IteratorUtil.asCollection(result);
            time = System.currentTimeMillis() - time;
            resumeTransaction(resumeTx);
            CypherResult cypherResult = new CypherResult(result.columns(), data, result.getQueryStatistics(), time, canProfile ? result.executionPlanDescription() : null, prettify(query));
            tx.success();
            return cypherResult;
        } finally {
            tx.close();
            awaitIndexOnline(query);
        }
    }

    private void awaitIndexOnline(String query) {
        if (!isIndexQuery(query)) return;
        try (Transaction tx = gdb.beginTx()) {
            gdb.schema().awaitIndexesOnline(5, TimeUnit.SECONDS);
            tx.success();
        }
    }

    private javax.transaction.Transaction suspendTx(String query) {
        if (!executionEngine.isPeriodicCommit(query)) return null;
        try {
            return transactionManager.suspend();
        } catch (SystemException e) {
            throw new RuntimeException("Error suspending Transaction",e);
        }
    }
    private void resumeTransaction(javax.transaction.Transaction tx) {
        if (tx == null) return;
        try {
            transactionManager.resume(tx);
        } catch (SystemException | InvalidTransactionException e) {
            throw new RuntimeException("Error resuming Transaction "+tx,e);
        }
    }

    boolean canProfileQuery(String query) {
//        return false;
        Matcher matcher = CANNOT_PROFILE_PATTERN.matcher(query);
        return !matcher.find();
    }

    private void registerProperties(String query) {
        Set<String> properties = extractProperties(query);
        index.registerProperty(properties);
    }
   
    String replaceIndex(String query) {
        Matcher matcher = INDEX_PATTERN.matcher(query);
        if (!matcher.find()) return query;
        StringBuffer sb=new StringBuffer();
        do  {
            matcher.appendReplacement(sb,"$1:$1_auto_index(");
        } while (matcher.find());
        matcher.appendTail(sb);
        return sb.toString();
    }

    // TODO should get metadata from the cypher query
    // does not take care of quoted, non-identifier properties
    Set<String> extractProperties(String query) {
        final Matcher matcher = PROPERTY_PATTERN.matcher(query);
        final Set<String> properties = new HashSet<>();
        while (matcher.find()) {
            if (matcher.group(2)!=null) properties.add(matcher.group(2));
            if (matcher.group(3)!=null) properties.add(matcher.group(3));
        }
        return properties;
    }
}
TOP

Related Classes of org.neo4j.community.console.CypherQueryExecutor$CypherResult

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.