/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  Licensed under the Apache License, Version 2.0 (the "License");
 *  *  you may not use this file except in compliance with the License.
 *  *  You may obtain a copy of the License at
 *  *
 *  *       http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *  Unless required by applicable law or agreed to in writing, software
 *  *  distributed under the License is distributed on an "AS IS" BASIS,
 *  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *  See the License for the specific language governing permissions and
 *  *  limitations under the License.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.orient.core.sql;

import com.orientechnologies.common.collection.OMultiValue;
import com.orientechnologies.common.io.OIOUtils;
import com.orientechnologies.common.parser.OBaseParser;
import com.orientechnologies.common.util.OPair;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.metadata.schema.OImmutableClass;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentHelper;
import com.orientechnologies.orient.core.record.impl.ODocumentInternal;
import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper;
import com.orientechnologies.orient.core.serialization.serializer.record.string.ORecordSerializerCSVAbstract;
import com.orientechnologies.orient.core.sql.filter.*;
import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime;

import java.math.BigDecimal;
import java.util.*;

/**
 * SQL Helper class
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 *
 */
public class OSQLHelper {
  public static final String NAME = "sql";

  public static final String VALUE_NOT_PARSED = "_NOT_PARSED_";
  public static final String NOT_NULL         = "_NOT_NULL_";
  public static final String DEFINED          = "_DEFINED_";

  private static ClassLoader orientClassLoader = OSQLFilterItemAbstract.class.getClassLoader();

  public static Object parseDefaultValue(ODocument iRecord, final String iWord) {
    final Object v = OSQLHelper.parseValue(iWord, null);

    if (v != VALUE_NOT_PARSED) {
      return v;
    }

    // TRY TO PARSE AS FUNCTION
    final OSQLFunctionRuntime func = OSQLHelper.getFunction(null, iWord);
    if (func != null) {
      return func.execute(iRecord, iRecord, null, null);
    }

    // PARSE AS FIELD
    return iWord;
  }

  /**
   * Convert fields from text to real value. Supports: String, RID, Boolean, Float, Integer and NULL.
   *
   * @param iValue Value to convert.
   *
   * @return The value converted if recognized, otherwise VALUE_NOT_PARSED
   */
  public static Object parseValue(String iValue, final OCommandContext iContext) {
    return parseValue(iValue, iContext, false);
  }

  public static Object parseValue(String iValue, final OCommandContext iContext, boolean resolveContextVariables) {

    if (iValue == null)
      return null;

    iValue = iValue.trim();

    Object fieldValue = VALUE_NOT_PARSED;

    if (iValue.length() == 0) {
      return iValue;
    }
    if (iValue.startsWith("'") && iValue.endsWith("'") || iValue.startsWith("\"") && iValue.endsWith("\""))
      // STRING
      fieldValue = OIOUtils.getStringContent(iValue);
    else if (iValue.charAt(0) == OStringSerializerHelper.LIST_BEGIN
        && iValue.charAt(iValue.length() - 1) == OStringSerializerHelper.LIST_END) {
      // COLLECTION/ARRAY
      final List<String> items = OStringSerializerHelper
          .smartSplit(iValue.substring(1, iValue.length() - 1), OStringSerializerHelper.RECORD_SEPARATOR);

      final List<Object> coll = new ArrayList<Object>();
      for (String item : items) {
        coll.add(parseValue(item, iContext, resolveContextVariables));
      }
      fieldValue = coll;

    } else if (iValue.charAt(0) == OStringSerializerHelper.MAP_BEGIN
        && iValue.charAt(iValue.length() - 1) == OStringSerializerHelper.MAP_END) {
      // MAP
      final List<String> items = OStringSerializerHelper
          .smartSplit(iValue.substring(1, iValue.length() - 1), OStringSerializerHelper.RECORD_SEPARATOR);

      final Map<Object, Object> map = new HashMap<Object, Object>();
      for (String item : items) {
        final List<String> parts = OStringSerializerHelper.smartSplit(item, OStringSerializerHelper.ENTRY_SEPARATOR);

        if (parts == null || parts.size() != 2)
          throw new OCommandSQLParsingException("Map found but entries are not defined as <key>:<value>");

        Object key = OStringSerializerHelper.decode(parseValue(parts.get(0), iContext).toString());
        Object value = parseValue(parts.get(1), iContext);
        if (VALUE_NOT_PARSED == value) {
          value = new OSQLPredicate(parts.get(1)).evaluate(iContext);
        }
        if(value instanceof String){
          value = OStringSerializerHelper.decode(value.toString());
        }
        map.put(key, value);
      }

      if (map.containsKey(ODocumentHelper.ATTRIBUTE_TYPE))
        // IT'S A DOCUMENT
        // TODO: IMPROVE THIS CASE AVOIDING DOUBLE PARSING
        fieldValue = new ODocument().fromJSON(iValue);
      else
        fieldValue = map;

    } else if (iValue.charAt(0) == OStringSerializerHelper.EMBEDDED_BEGIN
        && iValue.charAt(iValue.length() - 1) == OStringSerializerHelper.EMBEDDED_END) {
      // SUB-COMMAND
      fieldValue = new OCommandSQL(iValue.substring(1, iValue.length() - 1));
      ((OCommandSQL) fieldValue).getContext().setParent(iContext);

    } else if (ORecordId.isA(iValue))
      // RID
      fieldValue = new ORecordId(iValue.trim());
    else {

      if (iValue.equalsIgnoreCase("null"))
        // NULL
        fieldValue = null;
      else if (iValue.equalsIgnoreCase("not null"))
        // NULL
        fieldValue = NOT_NULL;
      else if (iValue.equalsIgnoreCase("defined"))
        // NULL
        fieldValue = DEFINED;
      else if (iValue.equalsIgnoreCase("true"))
        // BOOLEAN, TRUE
        fieldValue = Boolean.TRUE;
      else if (iValue.equalsIgnoreCase("false"))
        // BOOLEAN, FALSE
        fieldValue = Boolean.FALSE;
      else if (iValue.startsWith("date(")) {
        final OSQLFunctionRuntime func = OSQLHelper.getFunction(null, iValue);
        if (func != null) {
          fieldValue = func.execute(null, null, null, iContext);
        }
      } else if (resolveContextVariables && iValue.startsWith("$") && iContext != null) {
        fieldValue = iContext.getVariable(iValue);
      } else {
        final Object v = parseStringNumber(iValue);
        if (v != null)
          fieldValue = v;
      }
    }

    return fieldValue;
  }

  public static Object parseStringNumber(final String iValue) {
    final OType t = ORecordSerializerCSVAbstract.getType(iValue);

    if (t == OType.INTEGER)
      return Integer.parseInt(iValue);
    else if (t == OType.LONG)
      return Long.parseLong(iValue);
    else if (t == OType.FLOAT)
      return Float.parseFloat(iValue);
    else if (t == OType.SHORT)
      return Short.parseShort(iValue);
    else if (t == OType.BYTE)
      return Byte.parseByte(iValue);
    else if (t == OType.DOUBLE)
      return Double.parseDouble(iValue);
    else if (t == OType.DECIMAL)
      return new BigDecimal(iValue);
    else if (t == OType.DATE || t == OType.DATETIME)
      return new Date(Long.parseLong(iValue));

    return null;
  }

  public static Object parseValue(final OSQLPredicate iSQLFilter, final OBaseParser iCommand, final String iWord,
      final OCommandContext iContext) {
    if (iWord.charAt(0) == OStringSerializerHelper.PARAMETER_POSITIONAL
        || iWord.charAt(0) == OStringSerializerHelper.PARAMETER_NAMED) {
      if (iSQLFilter != null)
        return iSQLFilter.addParameter(iWord);
      else
        return new OSQLFilterItemParameter(iWord);
    } else
      return parseValue(iCommand, iWord, iContext);
  }

  public static Object parseValue(final OBaseParser iCommand, final String iWord, final OCommandContext iContext) {
    return parseValue(iCommand, iWord, iContext, false);
  }

  public static Object parseValue(final OBaseParser iCommand, final String iWord, final OCommandContext iContext,
      boolean resolveContextVariables) {
    if (iWord.equals("*"))
      return "*";

    // TRY TO PARSE AS RAW VALUE
    final Object v = parseValue(iWord, iContext, resolveContextVariables);
    if (v != VALUE_NOT_PARSED)
      return v;

    if (!iWord.equalsIgnoreCase("any()") && !iWord.equalsIgnoreCase("all()")) {
      // TRY TO PARSE AS FUNCTION
      final Object func = OSQLHelper.getFunction(iCommand, iWord);
      if (func != null)
        return func;
    }

    if (iWord.startsWith("$"))
      // CONTEXT VARIABLE
      return new OSQLFilterItemVariable(iCommand, iWord);

    // PARSE AS FIELD
    return new OSQLFilterItemField(iCommand, iWord, null);
  }

  public static OSQLFunctionRuntime getFunction(final OBaseParser iCommand, final String iWord) {
    final int separator = iWord.indexOf('.');
    final int beginParenthesis = iWord.indexOf(OStringSerializerHelper.EMBEDDED_BEGIN);
    if (beginParenthesis > -1 && (separator == -1 || separator > beginParenthesis)) {
      final int endParenthesis = iWord.indexOf(OStringSerializerHelper.EMBEDDED_END, beginParenthesis);

      final char firstChar = iWord.charAt(0);
      if (endParenthesis > -1 && (firstChar == '_' || Character.isLetter(firstChar)))
        // FUNCTION: CREATE A RUN-TIME CONTAINER FOR IT TO SAVE THE PARAMETERS
        return new OSQLFunctionRuntime(iCommand, iWord);
    }

    return null;
  }

  public static Object getValue(final Object iObject) {
    if (iObject == null)
      return null;

    if (iObject instanceof OSQLFilterItem)
      return ((OSQLFilterItem) iObject).getValue(null, null, null);

    return iObject;
  }

  public static Object getValue(final Object iObject, final ORecord iRecord, final OCommandContext iContext) {
    if (iObject == null)
      return null;

    if (iObject instanceof OSQLFilterItem)
      return ((OSQLFilterItem) iObject).getValue(iRecord, null, iContext);
    else if (iObject instanceof String) {
      final String s = ((String) iObject).trim();
      if (iRecord != null & !s.isEmpty() && !OIOUtils.isStringContent(iObject) && !Character.isDigit(s.charAt(0)))
        // INTERPRETS IT
        return ODocumentHelper.getFieldValue(iRecord, s, iContext);
    }

    return iObject;
  }

  public static Object resolveFieldValue(final ODocument iDocument, final String iFieldName, final Object iFieldValue,
      final OCommandParameters iArguments, final OCommandContext iContext) {
    if (iFieldValue instanceof OSQLFilterItemField) {
      final OSQLFilterItemField f = (OSQLFilterItemField) iFieldValue;
      if (f.getRoot().equals("?"))
        // POSITIONAL PARAMETER
        return iArguments.getNext();
      else if (f.getRoot().startsWith(":"))
        // NAMED PARAMETER
        return iArguments.getByName(f.getRoot().substring(1));
    }

    if (iFieldValue instanceof ODocument && !((ODocument) iFieldValue).getIdentity().isValid())
      // EMBEDDED DOCUMENT
      ODocumentInternal.addOwner((ODocument) iFieldValue, iDocument);

    // can't use existing getValue with iContext
    if (iFieldValue == null)
      return null;
    if (iFieldValue instanceof OSQLFilterItem)
      return ((OSQLFilterItem) iFieldValue).getValue(iDocument, null, iContext);

    return iFieldValue;
  }

  public static ODocument bindParameters(final ODocument iDocument, final Map<String, Object> iFields,
      final OCommandParameters iArguments, final OCommandContext iContext) {
    if (iFields == null)
      return null;

    final List<OPair<String, Object>> fields = new ArrayList<OPair<String, Object>>(iFields.size());

    for (Map.Entry<String, Object> entry : iFields.entrySet())
      fields.add(new OPair<String, Object>(entry.getKey(), entry.getValue()));

    return bindParameters(iDocument, fields, iArguments, iContext);
  }

  public static ODocument bindParameters(final ODocument iDocument, final List<OPair<String, Object>> iFields,
      final OCommandParameters iArguments, final OCommandContext iContext) {
    if (iFields == null)
      return null;

    // BIND VALUES
    for (OPair<String, Object> field : iFields) {
      final String fieldName = field.getKey();
      Object fieldValue = field.getValue();

      if (fieldValue != null) {
        if (fieldValue instanceof OCommandSQL) {
          final OCommandSQL cmd = (OCommandSQL) fieldValue;
          cmd.getContext().setParent(iContext);
          fieldValue = ODatabaseRecordThreadLocal.instance().get().command(cmd).execute();

          // CHECK FOR CONVERSIONS
          OImmutableClass immutableClass = ODocumentInternal.getImmutableSchemaClass(iDocument);
          if (immutableClass != null) {
            final OProperty prop = immutableClass.getProperty(fieldName);
            if (prop != null) {
              if (prop.getType() == OType.LINK) {
                if (OMultiValue.isMultiValue(fieldValue)) {
                  final int size = OMultiValue.getSize(fieldValue);
                  if (size == 1)
                    // GET THE FIRST ITEM AS UNIQUE LINK
                    fieldValue = OMultiValue.getFirstValue(fieldValue);
                  else if (size == 0)
                    // NO ITEMS, SET IT AS NULL
                    fieldValue = null;
                }
              }
            } else if (immutableClass.isEdgeType() && ("out".equals(fieldName) || "in".equals(fieldName))
                && (fieldValue instanceof List)) {
              List lst = (List) fieldValue;
              if (lst.size() == 1) {
                fieldValue = lst.get(0);
              }
            }

          }

          if (OMultiValue.isMultiValue(fieldValue)) {
            final List<Object> tempColl = new ArrayList<Object>(OMultiValue.getSize(fieldValue));

            String singleFieldName = null;
            for (Object o : OMultiValue.getMultiValueIterable(fieldValue, false)) {
              if (o instanceof OIdentifiable && !((OIdentifiable) o).getIdentity().isPersistent()) {
                // TEMPORARY / EMBEDDED
                final ORecord rec = ((OIdentifiable) o).getRecord();
                if (rec != null && rec instanceof ODocument) {
                  // CHECK FOR ONE FIELD ONLY
                  final ODocument doc = (ODocument) rec;
                  if (doc.fields() == 1) {
                    singleFieldName = doc.fieldNames()[0];
                    tempColl.add(doc.field(singleFieldName));
                  } else {
                    // TRANSFORM IT IN EMBEDDED
                    doc.getIdentity().reset();
                    ODocumentInternal.addOwner(doc, iDocument);
                    ODocumentInternal.addOwner(doc, iDocument);
                    tempColl.add(doc);
                  }
                }
              } else
                tempColl.add(o);
            }

            fieldValue = tempColl;
          }
        }
      }

      iDocument.field(fieldName, resolveFieldValue(iDocument, fieldName, fieldValue, iArguments, iContext));
    }
    return iDocument;
  }
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  Licensed under the Apache License, Version 2.0 (the "License");
 *  *  you may not use this file except in compliance with the License.
 *  *  You may obtain a copy of the License at
 *  *
 *  *       http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *  Unless required by applicable law or agreed to in writing, software
 *  *  distributed under the License is distributed on an "AS IS" BASIS,
 *  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *  See the License for the specific language governing permissions and
 *  *  limitations under the License.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.orient.core.command;

import com.orientechnologies.common.listener.OProgressListener;
import com.orientechnologies.orient.core.command.OCommandContext.TIMEOUT_STRATEGY;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.replication.OAsyncReplicationError;
import com.orientechnologies.orient.core.replication.OAsyncReplicationOk;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Text based Command Request abstract class.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
@SuppressWarnings("serial")
public abstract class OCommandRequestAbstract implements OCommandRequestInternal, ODistributedCommand {
  protected OCommandResultListener resultListener;
  protected OProgressListener      progressListener;
  protected int              limit           = -1;
  protected long             timeoutMs       = OGlobalConfiguration.COMMAND_TIMEOUT.getValueAsLong();
  protected TIMEOUT_STRATEGY timeoutStrategy = TIMEOUT_STRATEGY.EXCEPTION;
  protected Map<Object, Object> parameters;
  protected String  fetchPlan       = null;
  protected boolean useCache        = false;
  protected boolean cacheableResult = false;
  protected OCommandContext        context;
  protected OAsyncReplicationOk    onAsyncReplicationOk;
  protected OAsyncReplicationError onAsyncReplicationError;

  private final Set<String> nodesToExclude = new HashSet<String>();
  private boolean recordResultSet = true;

  protected OCommandRequestAbstract() {
  }

  public OCommandResultListener getResultListener() {
    return resultListener;
  }

  public void setResultListener(OCommandResultListener iListener) {
    resultListener = iListener;
  }

  public Map<Object, Object> getParameters() {
    return parameters;
  }

  protected void setParameters(final Object... iArgs) {
    if (iArgs != null && iArgs.length>0)
      parameters = convertToParameters(iArgs);
  }

  @SuppressWarnings("unchecked")
  protected Map<Object, Object> convertToParameters(Object... iArgs) {
    final Map<Object, Object> params;

    if (iArgs.length == 1 && iArgs[0] instanceof Map) {
      params = (Map<Object, Object>) iArgs[0];
    } else {
      if (iArgs.length == 1 && iArgs[0] != null && iArgs[0].getClass().isArray() && iArgs[0] instanceof Object[])
        iArgs = (Object[]) iArgs[0];

      params = new HashMap<Object, Object>(iArgs.length);
      for (int i = 0; i<iArgs.length; ++i) {
        Object par = iArgs[i];

        if (par instanceof OIdentifiable && ((OIdentifiable) par).getIdentity().isValid())
          // USE THE RID ONLY
          par = ((OIdentifiable) par).getIdentity();

        params.put(i, par);
      }
    }
    return params;
  }

  /**
   * Defines a callback to call in case of the asynchronous replication succeed.
   */
  @Override
  public OCommandRequestAbstract onAsyncReplicationOk(final OAsyncReplicationOk iCallback) {
    onAsyncReplicationOk = iCallback;
    return this;
  }

  /**
   * Defines a callback to call in case of error during the asynchronous replication.
   */
  @Override
  public OCommandRequestAbstract onAsyncReplicationError(final OAsyncReplicationError iCallback) {
    if (iCallback != null) {
      onAsyncReplicationError = new OAsyncReplicationError() {
        int retry = 0;

        @Override
        public ACTION onAsyncReplicationError(Throwable iException, final int iRetry) {
          switch (iCallback.onAsyncReplicationError(iException, ++retry)) {
          case RETRY:
            execute();
            break;

          case IGNORE:

          }

          return ACTION.IGNORE;
        }
      };
    } else
      onAsyncReplicationError = null;
    return this;
  }

  public OProgressListener getProgressListener() {
    return progressListener;
  }

  public OCommandRequestAbstract setProgressListener(OProgressListener progressListener) {
    this.progressListener = progressListener;
    return this;
  }

  public void reset() {
  }

  public int getLimit() {
    return limit;
  }

  public OCommandRequestAbstract setLimit(final int limit) {
    this.limit = limit;
    return this;
  }

  public String getFetchPlan() {
    return fetchPlan;
  }

  @SuppressWarnings("unchecked")
  public <RET extends OCommandRequest> RET setFetchPlan(String fetchPlan) {
    this.fetchPlan = fetchPlan;
    return (RET) this;
  }

  public boolean isUseCache() {
    return useCache;
  }

  public void setUseCache(boolean useCache) {
    this.useCache = useCache;
  }

  @Override
  public boolean isCacheableResult() {
    return cacheableResult;
  }

  @Override
  public void setCacheableResult(final boolean iValue) {
    cacheableResult = iValue;
  }

  @Override
  public OCommandContext getContext() {
    if (context == null)
      context = new OBasicCommandContext();
    return context;
  }

  public OCommandRequestAbstract setContext(final OCommandContext iContext) {
    context = iContext;
    return this;
  }

  public long getTimeoutTime() {
    return timeoutMs;
  }

  public void setTimeout(final long timeout, final TIMEOUT_STRATEGY strategy) {
    this.timeoutMs = timeout;
    this.timeoutStrategy = strategy;
  }

  public TIMEOUT_STRATEGY getTimeoutStrategy() {
    return timeoutStrategy;
  }

  @Override
  public Set<String> nodesToExclude() {
    return Collections.unmodifiableSet(nodesToExclude);
  }

  public void addExcludedNode(String node) {
    nodesToExclude.add(node);
  }

  public void removeExcludedNode(String node) {
    nodesToExclude.remove(node);
  }

  public OAsyncReplicationOk getOnAsyncReplicationOk() {
    return onAsyncReplicationOk;
  }

  public OAsyncReplicationError getOnAsyncReplicationError() {
    return onAsyncReplicationError;
  }

  @Override
  public void setRecordResultSet(boolean recordResultSet) {
    this.recordResultSet = recordResultSet;
  }

  public boolean isRecordResultSet() {
    return recordResultSet;
  }
}


/*
 *
 *  *  Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
 *  *
 *  *  Licensed under the Apache License, Version 2.0 (the "License");
 *  *  you may not use this file except in compliance with the License.
 *  *  You may obtain a copy of the License at
 *  *
 *  *       http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *  Unless required by applicable law or agreed to in writing, software
 *  *  distributed under the License is distributed on an "AS IS" BASIS,
 *  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *  See the License for the specific language governing permissions and
 *  *  limitations under the License.
 *  *
 *  * For more information: http://www.orientechnologies.com
 *
 */

package com.orientechnologies.orient.core.compression;

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.compression.impl.OGZIPCompression;
import com.orientechnologies.orient.core.compression.impl.OHighZIPCompression;
import com.orientechnologies.orient.core.compression.impl.OLowZIPCompression;
import com.orientechnologies.orient.core.compression.impl.ONothingCompression;
import com.orientechnologies.orient.core.exception.OSecurityException;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Factory of compression algorithms.
 *
 * @author Andrey Lomakin
 * @since 05.06.13
 */
public class OCompressionFactory {
  public static final OCompressionFactory INSTANCE = new OCompressionFactory();

  private final Map<String, OCompression>                  compressions       = new HashMap<String, OCompression>();
  private final Map<String, Class<? extends OCompression>> compressionClasses = new HashMap<String, Class<? extends OCompression>>();

  /**
   * Install default compression algorithms.
   */
  public OCompressionFactory() {
    register(new OHighZIPCompression());
    register(new OLowZIPCompression());
    register(new OGZIPCompression());
    register(new ONothingCompression());
  }

  public OCompression getCompression(final String name, final String iOptions) {
    if (name.length() == 0)
      return ONothingCompression.INSTANCE;

    OCompression compression = compressions.get(name);
    if (compression == null) {

      final Class<? extends OCompression> compressionClass;
      if (name == null)
        compressionClass = ONothingCompression.class;
      else
        compressionClass = compressionClasses.get(name);

      if (compressionClass != null) {
        try {
          compression = compressionClass.newInstance();
          compression.configure(iOptions);

        } catch (Exception e) {
          throw OException.wrapException(new OSecurityException("Cannot instantiate compression algorithm '" + name + "'"), e);
        }
      } else
        throw new OSecurityException("Compression with name '" + name + "' is absent");
    }
    return compression;
  }

  /**
   * Registers a stateful implementations, a new instance will be created for each storage.
   *
   * @param compression Compression instance
   */
  public void register(final OCompression compression) {
    try {
      final String name = compression.name();

      if (compressions.containsKey(name))
        throw new IllegalArgumentException("Compression with name '" + name + "' was already registered");

      if (compressionClasses.containsKey(name))
        throw new IllegalArgumentException("Compression with name '" + name + "' was already registered");

      compressions.put(name, compression);
    } catch (Exception e) {
      OLogManager.instance().error(this, "Cannot register storage compression algorithm '%s'", e, compression);
    }
  }

  /**
   * Registers a stateless implementations, the same instance will be shared on all the storages.
   *
   * @param compression Compression class
   */
  public void register(final Class<? extends OCompression> compression) {
    try {
      final OCompression tempInstance = compression.newInstance();

      final String name = tempInstance.name();

      if (compressions.containsKey(name))
        throw new IllegalArgumentException("Compression with name '" + name + "' was already registered");

      if (compressionClasses.containsKey(tempInstance.name()))
        throw new IllegalArgumentException("Compression with name '" + name + "' was already registered");

      compressionClasses.put(name, compression);
    } catch (Exception e) {
      OLogManager.instance().error(this, "Cannot register storage compression algorithm '%s'", e, compression);
    }
  }

  public Set<String> getCompressions() {
    return compressions.keySet();
  }
}


/*
 *
 *  *  Copyright 2010-2018 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  Licensed under the Apache License, Version 2.0 (the "License");
 *  *  you may not use this file except in compliance with the License.
 *  *  You may obtain a copy of the License at
 *  *
 *  *       http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *  Unless required by applicable law or agreed to in writing, software
 *  *  distributed under the License is distributed on an "AS IS" BASIS,
 *  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *  See the License for the specific language governing permissions and
 *  *  limitations under the License.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.common.jna;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.util.OMemory;
import com.sun.jna.LastErrorException;
import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.PointerByReference;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.ByteBuffer;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ONative {
  private static volatile OCLibrary C_LIBRARY;
  private static final    String    DEFAULT_MEMORY_CGROUP_PATH = "/sys/fs/memory";

  private static volatile ONative instance = null;
  private static final    Lock    initLock = new ReentrantLock();

  public static final int O_RDONLY = 00;
  public static final int O_WRONLY = 01;
  public static final int O_RDWR   = 02;
  public static final int O_CREAT  = 0100;
  public static final int O_EXCL   = 0200;
  public static final int O_APPEND = 02000;
  public static final int O_TRUNC  = 01000;
  public static final int O_DIRECT = 040000;
  public static final int O_SYNC   = 04000000;

  public static final int SEEK_SET = 0;
  public static final int SEEK_CUR = 1;
  public static final int SEEK_END = 2;

  public static final int MCL_CURRENT = 1;
  public static final int MCL_FUTURE  = 2;

  public static ONative instance() {
    if (instance != null)
      return instance;

    initLock.lock();
    try {
      if (instance != null)
        return instance;

      if (Platform.isLinux()) {
        C_LIBRARY = Native.loadLibrary("c", OCLibrary.class);
      } else {
        C_LIBRARY = null;
      }

      instance = new ONative();
    } finally {
      initLock.unlock();
    }

    return instance;
  }

  /**
   * Prevent initialization outside singleton
   */
  private ONative() {
  }

  /**
   * Detects limit of limit of open files.
   *
   * @param recommended recommended value of limit of open files.
   * @param defLimit    default value for limit of open files.
   *
   * @return limit of open files, available for the system.
   */
  public int getOpenFilesLimit(boolean verbose, int recommended, int defLimit) {
    if (Platform.isLinux()) {
      final OCLibrary.Rlimit rlimit = new OCLibrary.Rlimit();
      final int result = C_LIBRARY.getrlimit(OCLibrary.RLIMIT_NOFILE, rlimit);

      if (result == 0 && rlimit.rlim_cur > 0) {
        if (verbose) {
          OLogManager.instance().infoNoDb(this, "Detected limit of amount of simultaneously open files is %d, "
              + " limit of open files for disk cache will be set to %d", rlimit.rlim_cur, rlimit.rlim_cur / 2 - 512);
        }

        if (rlimit.rlim_cur < recommended) {
          OLogManager.instance()
              .warnNoDb(this, "Value of limit of simultaneously open files is too small, recommended value is %d", recommended);
        }

        return (int) rlimit.rlim_cur / 2 - 512;
      } else {
        if (verbose) {
          OLogManager.instance().infoNoDb(this, "Can not detect value of limit of open files.");
        }
      }
    } else if (Platform.isWindows()) {
      if (verbose) {
        OLogManager.instance()
            .infoNoDb(this, "Windows OS is detected, %d limit of open files will be set for the disk cache.", recommended);
      }

      return recommended;
    }

    if (verbose) {
      OLogManager.instance().infoNoDb(this, "Default limit of open files (%d) will be used.", defLimit);
    }

    return defLimit;
  }

  /**
   * @param printSteps Print all steps of discovering of memory limit in the log with {@code INFO} level.
   *
   * @return Amount of memory which are allowed to be consumed by application, and detects whether OrientDB instance is running
   * inside container. If null is returned then it was impossible to detect amount of memory on machine.
   */
  public MemoryLimitResult getMemoryLimit(final boolean printSteps) {
    //Perform several steps here:
    //1. Fetch physical size available on machine
    //2. Fetch soft limit
    //3. Fetch cgroup soft limit
    //4. Fetch cgroup hard limit
    //5. Return the minimal value from the list of results

    long memoryLimit = getPhysicalMemorySize();
    boolean insideContainer = false;

    if (printSteps) {
      OLogManager.instance()
          .infoNoDb(this, "%d B/%d MB/%d GB of physical memory were detected on machine", memoryLimit, convertToMB(memoryLimit),
              convertToGB(memoryLimit));
    }

    if (Platform.isLinux()) {
      final OCLibrary.Rlimit rlimit = new OCLibrary.Rlimit();
      final int result = C_LIBRARY.getrlimit(OCLibrary.RLIMIT_AS, rlimit);

      //no errors during the call
      if (result == 0) {
        if (printSteps)
          OLogManager.instance().infoNoDb(this, "Soft memory limit for this process is set to %d B/%d MB/%d GB", rlimit.rlim_cur,
              convertToMB(rlimit.rlim_cur), convertToGB(rlimit.rlim_cur));

        memoryLimit = updateMemoryLimit(memoryLimit, rlimit.rlim_cur);

        if (printSteps)
          OLogManager.instance().infoNoDb(this, "Hard memory limit for this process is set to %d B/%d MB/%d GB", rlimit.rlim_max,
              convertToMB(rlimit.rlim_max), convertToGB(rlimit.rlim_max));

        memoryLimit = updateMemoryLimit(memoryLimit, rlimit.rlim_max);
      }

      final String memoryCGroupPath = findMemoryGCGroupPath();

      if (memoryCGroupPath != null) {
        if (printSteps)
          OLogManager.instance().infoNoDb(this, "Path to 'memory' cgroup is '%s'", memoryCGroupPath);

        final String memoryCGroupRoot = findMemoryGCRoot();

        if (printSteps)
          OLogManager.instance().infoNoDb(this, "Mounting path for memory cgroup controller is '%s'", memoryCGroupRoot);

        File memoryCGroup = new File(memoryCGroupRoot, memoryCGroupPath);
        if (!memoryCGroup.exists()) {
          if (printSteps)
            OLogManager.instance().infoNoDb(this, "Can not find '%s' path for memory cgroup, it is supposed that "
                + "process is running in container, will try to read root '%s' memory cgroup data", memoryCGroup, memoryCGroupRoot);

          memoryCGroup = new File(memoryCGroupRoot);
          insideContainer = true;
        }

        final long softMemoryLimit = fetchCGroupSoftMemoryLimit(memoryCGroup, printSteps);
        memoryLimit = updateMemoryLimit(memoryLimit, softMemoryLimit);

        final long hardMemoryLimit = fetchCGroupHardMemoryLimit(memoryCGroup, printSteps);
        memoryLimit = updateMemoryLimit(memoryLimit, hardMemoryLimit);
      }
    }

    if (printSteps) {
      if (memoryLimit > 0)
        OLogManager.instance()
            .infoNoDb(this, "Detected memory limit for current process is %d B/%d MB/%d GB", memoryLimit, convertToMB(memoryLimit),
                convertToGB(memoryLimit));
      else
        OLogManager.instance().infoNoDb(this, "Memory limit for current process is not set");
    }

    if (memoryLimit <= 0)
      return null;

    return new MemoryLimitResult(memoryLimit, insideContainer);
  }

  public int open(String path, int flags) throws LastErrorException {
    return C_LIBRARY.open(path, flags, 0000400 | 0000200);//rw mask
  }

  public int fallocate(int fd, long offset, long len) throws LastErrorException {
    return C_LIBRARY.fallocate(fd, 0, offset, len);
  }

  public long read(int fd, ByteBuffer buffer, int count) throws LastErrorException {
    return C_LIBRARY.read(fd, buffer, count);
  }

  public long write(int fd, ByteBuffer buffer, int count) throws LastErrorException {
    return C_LIBRARY.write(fd, buffer, count);
  }

  public void posix_memalign(PointerByReference memptr, NativeLong alignment, NativeLong size) throws LastErrorException {
    C_LIBRARY.posix_memalign(memptr, alignment, size);
  }

  public int getpagesize() throws LastErrorException {
    return C_LIBRARY.getpagesize();
  }

  public int pathconf(String path, int name) throws LastErrorException {
    return C_LIBRARY.pathconf(path, name);
  }

  public int fsync(int fd) throws IOException {
    try {
      return C_LIBRARY.fsync(fd);
    } catch (LastErrorException e) {
      throw new IOException("Can not fsync file", e);
    }
  }

  public long lseek(int fd, long offset, int whence) throws LastErrorException {
    return C_LIBRARY.lseek(fd, offset, whence);
  }

  public int close(int fd) throws LastErrorException {
    return C_LIBRARY.close(fd);
  }

  public int mlockall(int flags) throws LastErrorException {
    return C_LIBRARY.mlockall(flags);
  }

  public int mlock(Pointer pointer, long len) throws LastErrorException {
    return C_LIBRARY.mlock(pointer, len);
  }

  public int munlock(Pointer pointer, long len) throws LastErrorException {
    return C_LIBRARY.munlock(pointer, len);
  }

  public boolean isUnlimitedMemoryLocking() {
    final OCLibrary.Rlimit rlimit = new OCLibrary.Rlimit();
    final int result = C_LIBRARY.getrlimit(OCLibrary.RLIMIT_MEMLOCK, rlimit);

    if (result != 0) {
      return false;
    }

    return rlimit.rlim_cur == -1;
  }

  private long updateMemoryLimit(long memoryLimit, final long newMemoryLimit) {
    if (newMemoryLimit <= 0) {
      return memoryLimit;
    }

    if (memoryLimit <= 0) {
      memoryLimit = newMemoryLimit;
    }

    if (memoryLimit > newMemoryLimit) {
      memoryLimit = newMemoryLimit;
    }

    return memoryLimit;
  }

  private long fetchCGroupSoftMemoryLimit(final File memoryCGroup, final boolean printSteps) {
    final File softMemoryCGroupLimit = new File(memoryCGroup, "memory.soft_limit_in_bytes");
    if (softMemoryCGroupLimit.exists()) {
      try {
        final FileReader memoryLimitReader = new FileReader(softMemoryCGroupLimit);
        try (final BufferedReader bufferedMemoryLimitReader = new BufferedReader(memoryLimitReader)) {
          try {
            final String cgroupMemoryLimitValueStr = bufferedMemoryLimitReader.readLine();
            try {
              final long cgroupMemoryLimitValue = Long.parseLong(cgroupMemoryLimitValueStr);

              if (printSteps)
                OLogManager.instance().infoNoDb(this, "cgroup soft memory limit is %d B/%d MB/%d GB", cgroupMemoryLimitValue,
                    convertToMB(cgroupMemoryLimitValue), convertToGB(cgroupMemoryLimitValue));

              return cgroupMemoryLimitValue;
            } catch (final NumberFormatException nfe) {
              if (cgroupMemoryLimitValueStr.matches("\\d+")) {
                if (printSteps) {
                  OLogManager.instance().infoNoDb(this, "cgroup soft memory limit is not set");
                }
              } else {
                OLogManager.instance().errorNoDb(this, "Can not read memory soft limit for cgroup '%s'", nfe, memoryCGroup);
              }
            }
          } catch (final IOException ioe) {
            OLogManager.instance().errorNoDb(this, "Can not read memory soft limit for cgroup '%s'", ioe, memoryCGroup);
          }
        } catch (final IOException e) {
          OLogManager.instance().errorNoDb(this, "Error on closing the reader of soft memory limit", e);
        }
      } catch (final FileNotFoundException fnfe) {
        OLogManager.instance().errorNoDb(this, "Can not read memory soft limit for cgroup '%s'", fnfe, memoryCGroup);
      }
    } else {
      if (printSteps)
        OLogManager.instance().infoNoDb(this, "Can not read memory soft limit for cgroup '%s'", memoryCGroup);
    }

    return -1;
  }

  private long fetchCGroupHardMemoryLimit(final File memoryCGroup, final boolean printSteps) {
    final File hardMemoryCGroupLimit = new File(memoryCGroup, "memory.limit_in_bytes");
    if (hardMemoryCGroupLimit.exists()) {
      try {
        final FileReader memoryLimitReader = new FileReader(hardMemoryCGroupLimit);

        try (final BufferedReader bufferedMemoryLimitReader = new BufferedReader(memoryLimitReader)) {
          try {
            final String cgroupMemoryLimitValueStr = bufferedMemoryLimitReader.readLine();
            try {
              final long cgroupMemoryLimitValue = Long.parseLong(cgroupMemoryLimitValueStr);

              if (printSteps)
                OLogManager.instance().infoNoDb(this, "cgroup hard memory limit is %d B/%d MB/%d GB", cgroupMemoryLimitValue,
                    convertToMB(cgroupMemoryLimitValue), convertToGB(cgroupMemoryLimitValue));

              return cgroupMemoryLimitValue;
            } catch (final NumberFormatException nfe) {
              if (cgroupMemoryLimitValueStr.matches("\\d+")) {
                if (printSteps) {
                  OLogManager.instance().infoNoDb(this, "cgroup hard memory limit is not set");
                }
              } else {
                OLogManager.instance().errorNoDb(this, "Can not read memory hard limit for cgroup '%s'", nfe, memoryCGroup);
              }
            }
          } catch (final IOException ioe) {
            OLogManager.instance().errorNoDb(this, "Can not read memory hard limit for cgroup '%s'", ioe, memoryCGroup);
          }
        } catch (final IOException e) {
          OLogManager.instance().errorNoDb(this, "Error on closing the reader of hard memory limit", e);
        }
      } catch (final FileNotFoundException fnfe) {
        OLogManager.instance().errorNoDb(this, "Can not read memory hard limit for cgroup '%s'", fnfe, memoryCGroup);
      }
    } else {
      if (printSteps) {
        OLogManager.instance().infoNoDb(this, "Can not read memory hard limit for cgroup '%s'", memoryCGroup);
      }
    }

    return -1;
  }

  private String findMemoryGCGroupPath() {
    String memoryCGroupPath = null;

    //fetch list of cgroups to which given process belongs to
    final File cgroupList = new File("/proc/self/cgroup");
    if (cgroupList.exists()) {
      try {
        final FileReader cgroupListReader = new FileReader(cgroupList);

        try (final BufferedReader bufferedCGroupReader = new BufferedReader(cgroupListReader)) {
          String cgroupData;
          try {
            while ((cgroupData = bufferedCGroupReader.readLine()) != null) {
              final String[] cgroupParts = cgroupData.split(":");
              //we need only memory controller
              if (cgroupParts[1].equals("memory")) {
                memoryCGroupPath = cgroupParts[2];
              }
            }
          } catch (final IOException ioe) {
            OLogManager.instance().errorNoDb(this, "Error during reading of details of list of cgroups for the current process, "
                + "no restrictions applied by cgroups will be taken into account", ioe);
            memoryCGroupPath = null;
          }

        } catch (final IOException e) {
          OLogManager.instance()
              .errorNoDb(this, "Error during closing of reader which reads details of list of cgroups for the current process", e);
        }
      } catch (final FileNotFoundException fnfe) {
        OLogManager.instance().warnNoDb(this, "Can not retrieve list of cgroups to which process belongs, "
            + "no restrictions applied by cgroups will be taken into account");
      }
    }

    return memoryCGroupPath;
  }

  private String findMemoryGCRoot() {
    String memoryCGroupRoot = null;

    //fetch all mount points and find one to which cgroup memory controller is mounted
    final File procMounts = new File("/proc/mounts");
    if (procMounts.exists()) {
      final FileReader mountsReader;
      try {
        mountsReader = new FileReader(procMounts);
        try (BufferedReader bufferedMountsReader = new BufferedReader(mountsReader)) {
          String fileSystem;
          while ((fileSystem = bufferedMountsReader.readLine()) != null) {
            //file system type \s+ mount point \s+ etc.
            final String[] fsParts = fileSystem.split("\\s+");
            if (fsParts.length == 0) {
              continue;
            }

            final String fsType = fsParts[0];
            //all cgroup controllers have "cgroup" as file system type
            if (fsType.equals("cgroup")) {
              //get mounting path of cgroup
              final String fsMountingPath = fsParts[1];
              final String[] fsPathParts = fsMountingPath.split(File.separator);
              if (fsPathParts[fsPathParts.length - 1].equals("memory")) {
                memoryCGroupRoot = fsMountingPath;
              }
            }
          }
        } catch (final IOException e) {
          OLogManager.instance().errorNoDb(this, "Error during reading a list of mounted file systems", e);
          memoryCGroupRoot = DEFAULT_MEMORY_CGROUP_PATH;
        }

      } catch (final FileNotFoundException fnfe) {
        memoryCGroupRoot = DEFAULT_MEMORY_CGROUP_PATH;
      }
    }

    if (memoryCGroupRoot == null) {
      memoryCGroupRoot = DEFAULT_MEMORY_CGROUP_PATH;
    }

    return memoryCGroupRoot;
  }

  /**
   * Obtains the total size in bytes of the installed physical memory on this machine. Note that on some VMs it's impossible to
   * obtain the physical memory size, in this case the return value will {@code -1}.
   *
   * @return the total physical memory size in bytes or {@code <= 0} if the size can't be obtained.
   */

  private long getPhysicalMemorySize() {
    long osMemory = -1;

    try {
      final MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
      final Object attribute = mBeanServer
          .getAttribute(new ObjectName("java.lang", "type", "OperatingSystem"), "TotalPhysicalMemorySize");

      if (attribute != null) {
        if (attribute instanceof Long) {
          osMemory = (Long) attribute;
        } else {
          try {
            osMemory = Long.parseLong(attribute.toString());
          } catch (final NumberFormatException e) {
            if (!OLogManager.instance().isDebugEnabled())
              OLogManager.instance().warnNoDb(OMemory.class, "Unable to determine the amount of installed RAM.");
            else
              OLogManager.instance().debugNoDb(OMemory.class, "Unable to determine the amount of installed RAM.", e);
          }
        }
      } else {
        if (!OLogManager.instance().isDebugEnabled())
          OLogManager.instance().warnNoDb(OMemory.class, "Unable to determine the amount of installed RAM.");
      }
    } catch (MalformedObjectNameException | AttributeNotFoundException | InstanceNotFoundException | MBeanException | ReflectionException e) {
      if (!OLogManager.instance().isDebugEnabled())
        OLogManager.instance().warnNoDb(OMemory.class, "Unable to determine the amount of installed RAM.");
      else
        OLogManager.instance().debugNoDb(OMemory.class, "Unable to determine the amount of installed RAM.", e);
    } catch (final RuntimeException e) {
      OLogManager.instance().warnNoDb(OMemory.class, "Unable to determine the amount of installed RAM.", e);
    }

    return osMemory;
  }

  private static long convertToMB(final long bytes) {
    if (bytes < 0)
      return bytes;

    return bytes / (1024 * 1024);
  }

  private static long convertToGB(final long bytes) {
    if (bytes < 0)
      return bytes;

    return bytes / (1024 * 1024 * 1024);
  }

  public final class MemoryLimitResult {
    public final long    memoryLimit;
    public final boolean insideContainer;

    MemoryLimitResult(final long memoryLimit, final boolean insideContainer) {
      this.memoryLimit = memoryLimit;
      this.insideContainer = insideContainer;
    }
  }
}


/* Generated By:JJTree: Do not edit this line. OMathExpression.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package com.orientechnologies.orient.core.sql.parser;

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.orient.core.collate.OCollate;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.record.OElement;
import com.orientechnologies.orient.core.sql.executor.AggregationContext;
import com.orientechnologies.orient.core.sql.executor.OResult;
import com.orientechnologies.orient.core.sql.executor.OResultInternal;

import java.math.BigDecimal;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class OMathExpression extends SimpleNode {

  private static final Object NULL_VALUE = new Object();

  public OExpression getExpandContent() {
    throw new OCommandExecutionException("Invalid expand expression");
  }

  public boolean isDefinedFor(OResult currentRecord) {
    return true;
  }

  public boolean isDefinedFor(OElement currentRecord) {
    return true;
  }

  public boolean isIndexChain(OCommandContext ctx, OClass clazz) {
    return false;
  }

  public enum Operator {
    STAR(10) {
      @Override
      public Number apply(Integer left, Integer right) {
        return left * right;
      }

      @Override
      public Number apply(Long left, Long right) {
        return left * right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return left * right;
      }

      @Override
      public Number apply(Double left, Double right) {
        return left * right;
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return left.multiply(right);
      }

      @Override
      public Object apply(Object left, Object right) {
        if (left == null || right == null) {
          return null;
        }
        return super.apply(left, right);
      }
    }, SLASH(10) {
      @Override
      public Number apply(Integer left, Integer right) {
        if (left % right == 0) {
          return left / right;
        }
        return ((double) left) / right;
      }

      @Override
      public Number apply(Long left, Long right) {
        if (left % right == 0) {
          return left / right;
        }
        return ((double) left) / right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return left / right;
      }

      @Override
      public Number apply(Double left, Double right) {
        return left / right;
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return left.divide(right, BigDecimal.ROUND_HALF_UP);
      }

      @Override
      public Object apply(Object left, Object right) {
        if (left == null || right == null) {
          return null;
        }
        return super.apply(left, right);
      }

    }, REM(10) {
      @Override
      public Number apply(Integer left, Integer right) {
        return left % right;
      }

      @Override
      public Number apply(Long left, Long right) {
        return left % right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return left % right;
      }

      @Override
      public Number apply(Double left, Double right) {
        return left % right;
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return left.remainder(right);
      }

      @Override
      public Object apply(Object left, Object right) {
        if (left == null || right == null) {
          return null;
        }
        return super.apply(left, right);
      }

    }, PLUS(20) {
      @Override
      public Number apply(Integer left, Integer right) {
        final Integer sum = left + right;
        if (sum < 0 && left.intValue() > 0 && right.intValue() > 0)
          // SPECIAL CASE: UPGRADE TO LONG
          return left.longValue() + right;
        return sum;
      }

      @Override
      public Number apply(Long left, Long right) {
        return left + right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return left + right;
      }

      @Override
      public Number apply(Double left, Double right) {
        return left + right;
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return left.add(right);
      }

      @Override
      public Object apply(Object left, Object right) {
        if (left == null && right == null) {
          return null;
        }
        if (left == null) {
          return right;
        }
        if (right == null) {
          return left;
        }
        if (left instanceof Number && right instanceof Number) {
          return super.apply(left, right);
        }
        if (left instanceof Date || right instanceof Date) {
          Number result = apply(toLong(left), toLong(right));
          return new Date(result.longValue());
        }
        return String.valueOf(left) + String.valueOf(right);
      }
    }, MINUS(20) {
      @Override
      public Number apply(Integer left, Integer right) {
        int result = left - right;
        if (result > 0 && left.intValue() < 0 && right.intValue() > 0)
          // SPECIAL CASE: UPGRADE TO LONG
          return left.longValue() - right;

        return result;
      }

      @Override
      public Number apply(Long left, Long right) {
        return left - right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return left - right;
      }

      @Override
      public Number apply(Double left, Double right) {
        return left - right;
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return left.subtract(right);
      }

      @Override
      public Object apply(Object left, Object right) {
        Object result = null;
        if (left == null && right == null) {
          result = null;
        } else if (left instanceof Number && right == null) {
          result = left;
        } else if (right instanceof Number && left == null) {
          result = apply(0, this, (Number) right);
        } else if (left instanceof Number && right instanceof Number) {
          result = apply((Number) left, this, (Number) right);
        } else if (left instanceof Date || right instanceof Date) {
          Number r = apply(toLong(left), toLong(right));
          result = new Date(r.longValue());
        }

        return result;
      }

    }, LSHIFT(30) {
      @Override
      public Number apply(Integer left, Integer right) {
        return left << right;
      }

      @Override
      public Number apply(Long left, Long right) {
        return left << right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return null;
      }

      @Override
      public Number apply(Double left, Double right) {
        return null;
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return null;
      }

      @Override
      public Object apply(Object left, Object right) {
        if (left == null || right == null) {
          return null;
        }
        return super.apply(left, right);
      }
    }, RSHIFT(30) {
      @Override
      public Number apply(Integer left, Integer right) {
        return left >> right;
      }

      @Override
      public Number apply(Long left, Long right) {
        return left >> right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return null;
      }

      @Override
      public Number apply(Double left, Double right) {
        return null;
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return null;
      }

      @Override
      public Object apply(Object left, Object right) {
        if (left == null || right == null) {
          return null;
        }
        return super.apply(left, right);
      }

    }, RUNSIGNEDSHIFT(30) {
      @Override
      public Number apply(Integer left, Integer right) {
        return left >>> right;
      }

      @Override
      public Number apply(Long left, Long right) {
        return left >>> right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return null;
      }

      @Override
      public Number apply(Double left, Double right) {
        return null;
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return null;
      }

      @Override
      public Object apply(Object left, Object right) {
        if (left == null || right == null) {
          return null;
        }
        return super.apply(left, right);
      }

    }, BIT_AND(40) {
      @Override
      public Number apply(Integer left, Integer right) {
        return left & right;
      }

      @Override
      public Number apply(Long left, Long right) {
        return left & right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return apply(left.longValue(), right.longValue());
      }

      @Override
      public Number apply(Double left, Double right) {
        return apply(left.longValue(), right.longValue());
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return null;
      }

      public Object apply(Object left, Object right) {
        if (left == null || right == null) {
          return null;
        }
        return super.apply(left, right);
      }
    }, XOR(50) {
      @Override
      public Number apply(Integer left, Integer right) {
        return left ^ right;
      }

      @Override
      public Number apply(Long left, Long right) {
        return left ^ right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return null;
      }

      @Override
      public Number apply(Double left, Double right) {
        return null;
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return null;
      }

      @Override
      public Object apply(Object left, Object right) {
        if (left == null && right == null) {
          return null;
        }
        if (left instanceof Number && right == null) {
          return apply((Number) left, this, 0);
        }
        if (right instanceof Number && left == null) {
          return apply(0, this, (Number) right);
        }

        if (left instanceof Number && right instanceof Number) {
          return apply((Number) left, this, (Number) right);
        }

        return null;
      }

    }, BIT_OR(60) {
      @Override
      public Number apply(Integer left, Integer right) {
        return left | right;
      }

      @Override
      public Number apply(Long left, Long right) {
        return left | right;
      }

      @Override
      public Number apply(Float left, Float right) {
        return null;
      }

      @Override
      public Number apply(Double left, Double right) {
        return null;
      }

      @Override
      public Number apply(BigDecimal left, BigDecimal right) {
        return null;
      }

      public Object apply(Object left, Object right) {
        if (left == null && right == null) {
          return null;
        }
        return super.apply(left == null ? 0 : left, right == null ? 0 : right);
      }

    };

    private static Long toLong(Object left) {
      if (left instanceof Number) {
        return ((Number) left).longValue();
      }
      if (left instanceof Date) {
        return ((Date) left).getTime();
      }
      return null;
    }

    private final int priority;

    Operator(int priority) {
      this.priority = priority;
    }

    public abstract Number apply(Integer left, Integer right);

    public abstract Number apply(Long left, Long right);

    public abstract Number apply(Float left, Float right);

    public abstract Number apply(Double left, Double right);

    public abstract Number apply(BigDecimal left, BigDecimal right);

    public Object apply(Object left, Object right) {
      if (left == null) {
        return right;
      }
      if (right == null) {
        return left;
      }
      if (left instanceof Number && right instanceof Number) {
        return apply((Number) left, this, (Number) right);
      }

      return null;
    }

    public Number apply(final Number a, final Operator operation, final Number b) {
      if (a == null || b == null)
        throw new IllegalArgumentException("Cannot increment a null value");

      if (a instanceof Integer || a instanceof Short) {
        if (b instanceof Integer || b instanceof Short) {
          return operation.apply(a.intValue(), b.intValue());
        } else if (b instanceof Long) {
          return operation.apply(a.longValue(), b.longValue());
        } else if (b instanceof Float)
          return operation.apply(a.floatValue(), b.floatValue());
        else if (b instanceof Double)
          return operation.apply(a.doubleValue(), b.doubleValue());
        else if (b instanceof BigDecimal)
          return operation.apply(new BigDecimal((Integer) a), (BigDecimal) b);
      } else if (a instanceof Long) {
        if (b instanceof Integer || b instanceof Long || b instanceof Short)
          return operation.apply(a.longValue(), b.longValue());
        else if (b instanceof Float)
          return operation.apply(a.floatValue(), b.floatValue());
        else if (b instanceof Double)
          return operation.apply(a.doubleValue(), b.doubleValue());
        else if (b instanceof BigDecimal)
          return operation.apply(new BigDecimal((Long) a), (BigDecimal) b);
      } else if (a instanceof Float) {
        if (b instanceof Short || b instanceof Integer || b instanceof Long || b instanceof Float)
          return operation.apply(a.floatValue(), b.floatValue());
        else if (b instanceof Double)
          return operation.apply(a.doubleValue(), b.doubleValue());
        else if (b instanceof BigDecimal)
          return operation.apply(new BigDecimal((Float) a), (BigDecimal) b);

      } else if (a instanceof Double) {
        if (b instanceof Short || b instanceof Integer || b instanceof Long || b instanceof Float || b instanceof Double)
          return operation.apply(a.doubleValue(), b.doubleValue());
        else if (b instanceof BigDecimal)
          return operation.apply(new BigDecimal((Double) a), (BigDecimal) b);

      } else if (a instanceof BigDecimal) {
        if (b instanceof Integer)
          return operation.apply((BigDecimal) a, new BigDecimal((Integer) b));
        else if (b instanceof Long)
          return operation.apply((BigDecimal) a, new BigDecimal((Long) b));
        else if (b instanceof Short)
          return operation.apply((BigDecimal) a, new BigDecimal((Short) b));
        else if (b instanceof Float)
          return operation.apply((BigDecimal) a, new BigDecimal((Float) b));
        else if (b instanceof Double)
          return operation.apply((BigDecimal) a, new BigDecimal((Double) b));
        else if (b instanceof BigDecimal)
          return operation.apply((BigDecimal) a, (BigDecimal) b);
      }

      throw new IllegalArgumentException(
          "Cannot increment value '" + a + "' (" + a.getClass() + ") with '" + b + "' (" + b.getClass() + ")");

    }

    public int getPriority() {
      return priority;
    }
  }

  protected List<OMathExpression> childExpressions = new ArrayList<OMathExpression>();
  protected List<Operator>        operators        = new ArrayList<>();

  public OMathExpression(int id) {
    super(id);
  }

  public OMathExpression(OrientSql p, int id) {
    super(p, id);
  }

  public boolean isCacheable() {
    for (OMathExpression exp : childExpressions) {
      if (!exp.isCacheable()) {
        return false;
      }
    }
    return true;
  }

  public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) {
    if (childExpressions.size() == 0) {
      return null;
    }
    if (childExpressions.size() == 1) {
      return childExpressions.get(0).execute(iCurrentRecord, ctx);
    }

    if (childExpressions.size() == 2) {
      Object leftValue = childExpressions.get(0).execute(iCurrentRecord, ctx);
      Object rightValue = childExpressions.get(1).execute(iCurrentRecord, ctx);
      return operators.get(0).apply(leftValue, rightValue);
    }

    return calculateWithOpPriority(iCurrentRecord, ctx);
  }

  public Object execute(OResult iCurrentRecord, OCommandContext ctx) {
    if (childExpressions.size() == 0) {
      return null;
    }
    if (childExpressions.size() == 1) {
      return childExpressions.get(0).execute(iCurrentRecord, ctx);
    }

    if (childExpressions.size() == 2) {
      Object leftValue = childExpressions.get(0).execute(iCurrentRecord, ctx);
      Object rightValue = childExpressions.get(1).execute(iCurrentRecord, ctx);
      return operators.get(0).apply(leftValue, rightValue);
    }

    return calculateWithOpPriority(iCurrentRecord, ctx);
  }

  private Object calculateWithOpPriority(OResult iCurrentRecord, OCommandContext ctx) {
    Deque valuesStack = new ArrayDeque<>();
    Deque<Operator> operatorsStack = new ArrayDeque<Operator>();

    OMathExpression nextExpression = childExpressions.get(0);
    Object val = nextExpression.execute(iCurrentRecord, ctx);
    valuesStack.push(val == null ? NULL_VALUE : val);

    for (int i = 0; i < operators.size() && i + 1 < childExpressions.size(); i++) {
      Operator nextOperator = operators.get(i);
      Object rightValue = childExpressions.get(i + 1).execute(iCurrentRecord, ctx);

      if (!operatorsStack.isEmpty() && operatorsStack.peek().getPriority() <= nextOperator.getPriority()) {
        Object right = valuesStack.poll();
        right = right == NULL_VALUE ? null : right;
        Object left = valuesStack.poll();
        left = left == NULL_VALUE ? null : left;
        Object calculatedValue = operatorsStack.poll().apply(left, right);
        valuesStack.push(calculatedValue == null ? NULL_VALUE : calculatedValue);
      }
      operatorsStack.push(nextOperator);

      valuesStack.push(rightValue == null ? NULL_VALUE : rightValue);
    }

    return iterateOnPriorities(valuesStack, operatorsStack);
  }

  private Object calculateWithOpPriority(OIdentifiable iCurrentRecord, OCommandContext ctx) {
    Deque valuesStack = new ArrayDeque<>();
    Deque<Operator> operatorsStack = new ArrayDeque<Operator>();

    OMathExpression nextExpression = childExpressions.get(0);
    Object val = nextExpression.execute(iCurrentRecord, ctx);
    valuesStack.push(val == null ? NULL_VALUE : val);

    for (int i = 0; i < operators.size() && i + 1 < childExpressions.size(); i++) {
      Operator nextOperator = operators.get(i);
      Object rightValue = childExpressions.get(i + 1).execute(iCurrentRecord, ctx);

      if (!operatorsStack.isEmpty() && operatorsStack.peek().getPriority() <= nextOperator.getPriority()) {
        Object right = valuesStack.poll();
        right = right == NULL_VALUE ? null : right;
        Object left = valuesStack.poll();
        left = left == NULL_VALUE ? null : left;
        Object calculatedValue = operatorsStack.poll().apply(left, right);
        valuesStack.push(calculatedValue == null ? NULL_VALUE : calculatedValue);
      }
      operatorsStack.push(nextOperator);

      valuesStack.push(rightValue == null ? NULL_VALUE : rightValue);
    }

    return iterateOnPriorities(valuesStack, operatorsStack);
  }

  private Object iterateOnPriorities(Deque values, Deque<Operator> operators) {
    while (true) {
      if (values.size() == 0) {
        return null;
      }
      if (values.size() == 1) {
        return values.getFirst();
      }

      Deque valuesStack = new ArrayDeque<>();
      Deque<OMathExpression.Operator> operatorsStack = new ArrayDeque<OMathExpression.Operator>();

      valuesStack.push(values.removeLast());

      while (!operators.isEmpty()) {
        Operator nextOperator = operators.removeLast();
        Object rightValue = values.removeLast();

        if (!operatorsStack.isEmpty() && operatorsStack.peek().getPriority() <= nextOperator.getPriority()) {
          Object right = valuesStack.poll();
          right = right == NULL_VALUE ? null : right;
          Object left = valuesStack.poll();
          left = left == NULL_VALUE ? null : left;
          Object calculatedValue = operatorsStack.poll().apply(left, right);
          valuesStack.push(calculatedValue == null ? NULL_VALUE : calculatedValue);
        }
        operatorsStack.push(nextOperator);
        valuesStack.push(rightValue == null ? NULL_VALUE : rightValue);
      }
      if (!operatorsStack.isEmpty()) {
        Object right = valuesStack.poll();
        right = right == NULL_VALUE ? null : right;
        Object left = valuesStack.poll();
        left = left == NULL_VALUE ? null : left;
        Object val = operatorsStack.poll().apply(left, right);
        valuesStack.push(val == null ? NULL_VALUE : val);
      }

      values = valuesStack;
      operators = operatorsStack;
    }
  }

  /**
   * Accept the visitor.
   **/
  public Object jjtAccept(OrientSqlVisitor visitor, Object data) {
    return visitor.visit(this, data);
  }

  public List<OMathExpression> getChildExpressions() {
    return childExpressions;
  }

  public void setChildExpressions(List<OMathExpression> childExpressions) {
    this.childExpressions = childExpressions;
  }

  public List<Operator> getOperators() {
    return operators;
  }

  public void toString(Map<Object, Object> params, StringBuilder builder) {
    for (int i = 0; i < childExpressions.size(); i++) {
      if (i > 0) {
        builder.append(" ");
        switch (operators.get(i - 1)) {
        case PLUS:
          builder.append("+");
          break;
        case MINUS:
          builder.append("-");
          break;
        case STAR:
          builder.append("*");
          break;
        case SLASH:
          builder.append("/");
          break;
        case REM:
          builder.append("%");
          break;
        case LSHIFT:
          builder.append("<<");
          break;
        case RSHIFT:
          builder.append(">>");
          break;
        case RUNSIGNEDSHIFT:
          builder.append(">>>");
          break;
        case BIT_AND:
          builder.append("&");
          break;
        case BIT_OR:
          builder.append("|");
          break;
        case XOR:
          builder.append("^");
          break;
        }
        builder.append(" ");
      }
      childExpressions.get(i).toString(params, builder);
    }
  }

  protected boolean supportsBasicCalculation() {
    for (OMathExpression expr : this.childExpressions) {
      if (!expr.supportsBasicCalculation()) {
        return false;
      }
    }
    return true;
  }

  public boolean isIndexedFunctionCall() {
    if (this.childExpressions.size() != 1) {
      return false;
    }
    return this.childExpressions.get(0).isIndexedFunctionCall();
  }

  public long estimateIndexedFunction(OFromClause target, OCommandContext context, OBinaryCompareOperator operator, Object right) {
    if (this.childExpressions.size() != 1) {
      return -1;
    }
    return this.childExpressions.get(0).estimateIndexedFunction(target, context, operator, right);
  }

  public Iterable<OIdentifiable> executeIndexedFunction(OFromClause target, OCommandContext context,
      OBinaryCompareOperator operator, Object right) {
    if (this.childExpressions.size() != 1) {
      return null;
    }
    return this.childExpressions.get(0).executeIndexedFunction(target, context, operator, right);
  }

  /**
   * tests if current expression is an indexed funciton AND that function can also be executed without using the index
   *
   * @param target  the query target
   * @param context the execution context
   *
   * @return true if current expression is an indexed funciton AND that function can also be executed without using the index, false
   * otherwise
   */
  public boolean canExecuteIndexedFunctionWithoutIndex(OFromClause target, OCommandContext context, OBinaryCompareOperator operator,
      Object right) {
    if (this.childExpressions.size() != 1) {
      return false;
    }
    return this.childExpressions.get(0).canExecuteIndexedFunctionWithoutIndex(target, context, operator, right);
  }

  /**
   * tests if current expression is an indexed function AND that function can be used on this target
   *
   * @param target  the query target
   * @param context the execution context
   *
   * @return true if current expression is an indexed function AND that function can be used on this target, false otherwise
   */
  public boolean allowsIndexedFunctionExecutionOnTarget(OFromClause target, OCommandContext context,
      OBinaryCompareOperator operator, Object right) {
    if (this.childExpressions.size() != 1) {
      return false;
    }
    return this.childExpressions.get(0).allowsIndexedFunctionExecutionOnTarget(target, context, operator, right);
  }

  /**
   * tests if current expression is an indexed function AND the function has also to be executed after the index search. In some
   * cases, the index search is accurate, so this condition can be excluded from further evaluation. In other cases the result from
   * the index is a superset of the expected result, so the function has to be executed anyway for further filtering
   *
   * @param target  the query target
   * @param context the execution context
   *
   * @return true if current expression is an indexed function AND the function has also to be executed after the index search.
   */
  public boolean executeIndexedFunctionAfterIndexSearch(OFromClause target, OCommandContext context,
      OBinaryCompareOperator operator, Object right) {
    if (this.childExpressions.size() != 1) {
      return false;
    }
    return this.childExpressions.get(0).executeIndexedFunctionAfterIndexSearch(target, context, operator, right);
  }

  public boolean isFunctionAny() {
    if (this.childExpressions.size() != 1) {
      return false;
    }
    return this.childExpressions.get(0).isFunctionAny();
  }

  public boolean isBaseIdentifier() {
    if (childExpressions.size() == 1) {
      return childExpressions.get(0).isBaseIdentifier();
    }
    return false;
  }

  public OCollate getCollate(OResult currentRecord, OCommandContext ctx) {
    if (childExpressions.size() == 1)
      return childExpressions.get(0).getCollate(currentRecord, ctx);
    return null;
  }

  public boolean isEarlyCalculated(OCommandContext ctx) {
    for (OMathExpression exp : childExpressions) {
      if (!exp.isEarlyCalculated(ctx)) {
        return false;
      }
    }
    return true;
  }

  public boolean needsAliases(Set<String> aliases) {
    for (OMathExpression expr : childExpressions) {
      if (expr.needsAliases(aliases)) {
        return true;
      }
    }
    return false;
  }

  public boolean isExpand() {
    for (OMathExpression expr : this.childExpressions) {
      if (expr.isExpand()) {
        if (this.childExpressions.size() > 1) {
          throw new OCommandExecutionException("Cannot calculate expand() with other expressions");
        }
        return true;
      }
    }
    return false;
  }

  public boolean isAggregate() {
    for (OMathExpression expr : this.childExpressions) {
      if (expr.isAggregate()) {
        return true;
      }
    }
    return false;
  }

  public boolean isCount() {
    if (this.childExpressions.size() != 1) {
      return false;
    }
    return this.childExpressions.get(0).isCount();
  }

  public SimpleNode splitForAggregation(AggregateProjectionSplit aggregateProj, OCommandContext ctx) {
    if (isAggregate()) {
      OMathExpression result = new OMathExpression(-1);
      int i = 0;
      for (OMathExpression expr : this.childExpressions) {
        if (i > 0) {
          result.operators.add(operators.get(i - 1));
        }
        SimpleNode splitResult = expr.splitForAggregation(aggregateProj, ctx);
        if (splitResult instanceof OMathExpression) {
          OMathExpression res = (OMathExpression) splitResult;
          if (res.isEarlyCalculated(ctx) || res.isAggregate()) {
            result.childExpressions.add(res);
          } else {
            throw new OCommandExecutionException("Cannot mix aggregate and single record attribute values in the same projection");
          }
        } else if (splitResult instanceof OExpression) {
          result.childExpressions.add(((OExpression) splitResult).mathExpression);//this comes from a splitted aggregate function
        }
        i++;
      }
      return result;
    } else {
      return this;
    }
  }

  public AggregationContext getAggregationContext(OCommandContext ctx) {
    throw new UnsupportedOperationException("multiple math expressions do not allow plain aggregation");
  }

  public OMathExpression copy() {
    OMathExpression result = null;
    try {
      result = getClass().getConstructor(Integer.TYPE).newInstance(-1);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    result.childExpressions = childExpressions.stream().map(x -> x.copy()).collect(Collectors.toList());
    result.operators.addAll(operators);
    return result;
  }

  public void extractSubQueries(OIdentifier letAlias, SubQueryCollector collector) {
    for (OMathExpression expr : this.childExpressions) {
      expr.extractSubQueries(letAlias, collector);
    }
  }

  public void extractSubQueries(SubQueryCollector collector) {
    for (OMathExpression expr : this.childExpressions) {
      expr.extractSubQueries(collector);
    }
  }

  public boolean refersToParent() {
    for (OMathExpression expr : this.childExpressions) {
      if (expr.refersToParent()) {
        return true;
      }
    }
    return false;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o)
      return true;
    if (o == null || getClass() != o.getClass())
      return false;

    OMathExpression that = (OMathExpression) o;

    if (childExpressions != null ? !childExpressions.equals(that.childExpressions) : that.childExpressions != null)
      return false;
    if (operators != null ? !operators.equals(that.operators) : that.operators != null)
      return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = childExpressions != null ? childExpressions.hashCode() : 0;
    result = 31 * result + (operators != null ? operators.hashCode() : 0);
    return result;
  }

  public List<String> getMatchPatternInvolvedAliases() {
    List<String> result = new ArrayList<String>();
    for (OMathExpression exp : childExpressions) {
      List<String> x = exp.getMatchPatternInvolvedAliases();
      if (x != null) {
        result.addAll(x);
      }
    }
    if (result.size() == 0) {
      return null;
    }
    return result;
  }

  public void applyRemove(OResultInternal result, OCommandContext ctx) {
    if (childExpressions.size() != 1) {
      throw new OCommandExecutionException("cannot apply REMOVE " + toString());
    }
    childExpressions.get(0).applyRemove(result, ctx);
  }

  public static OMathExpression deserializeFromResult(OResult fromResult) {
    String className = fromResult.getProperty("__class");
    try {
      OMathExpression result = (OMathExpression) Class.forName(className).getConstructor(Integer.class).newInstance(-1);
      result.deserialize(fromResult);
      return result;
    } catch (Exception e) {
      throw OException.wrapException(new OCommandExecutionException(""), e);
    }

  }

  public OResult serialize() {
    OResultInternal result = new OResultInternal();
    result.setProperty("__class", getClass().getName());
    if (childExpressions != null) {
      result.setProperty("childExpressions", childExpressions.stream().map(x -> x.serialize()).collect(Collectors.toList()));
    }
    if (operators != null) {
      result.setProperty("operators", operators.stream().map(x -> serializeOperator(x)).collect(Collectors.toList()));
    }
    return result;
  }

  public void deserialize(OResult fromResult) {
    if (fromResult.getProperty("childExpressions") != null) {
      List<OResult> ser = fromResult.getProperty("childExpressions");
      childExpressions = ser.stream().map(x -> deserializeFromResult(x)).collect(Collectors.toList());

    }
    if (fromResult.getProperty("operators") != null) {
      List<String> ser = fromResult.getProperty("operators");
      operators = ser.stream().map(x -> deserializeOperator(x)).collect(Collectors.toList());

    }
  }

  private String serializeOperator(Operator x) {
    return x.toString();
  }

  private Operator deserializeOperator(String x) {
    return Operator.valueOf(x);
  }
}
/* JavaCC - OriginalChecksum=c255bea24e12493e1005ba2a4d1dbb9d (do not edit this line) */


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.com)
 *  *
 *  *  Licensed under the Apache License, Version 2.0 (the "License");
 *  *  you may not use this file except in compliance with the License.
 *  *  You may obtain a copy of the License at
 *  *
 *  *       http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *  Unless required by applicable law or agreed to in writing, software
 *  *  distributed under the License is distributed on an "AS IS" BASIS,
 *  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *  See the License for the specific language governing permissions and
 *  *  limitations under the License.
 *  *
 *  * For more information: http://orientdb.com
 *
 */
package com.orientechnologies.orient.graph.sql;

import com.orientechnologies.common.util.OPair;
import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest;
import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.metadata.OMetadataInternal;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.sql.OCommandExecutorSQLSetAware;
import com.orientechnologies.orient.core.sql.OCommandParameters;
import com.orientechnologies.orient.core.sql.OCommandSQLParsingException;
import com.orientechnologies.orient.core.sql.OSQLHelper;
import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime;
import com.tinkerpop.blueprints.impls.orient.OrientBaseGraph;
import com.tinkerpop.blueprints.impls.orient.OrientVertex;
import com.tinkerpop.blueprints.impls.orient.OrientVertexType;

import java.util.*;

/**
 * SQL CREATE VERTEX command.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OCommandExecutorSQLCreateVertex extends OCommandExecutorSQLSetAware implements OCommandDistributedReplicateRequest {
  public static final String          NAME = "CREATE VERTEX";
  private OClass                      clazz;
  private String                      clusterName;
  private List<OPair<String, Object>> fields;

  @SuppressWarnings("unchecked")
  public OCommandExecutorSQLCreateVertex parse(final OCommandRequest iRequest) {

    final OCommandRequestText textRequest = (OCommandRequestText) iRequest;

    String queryText = textRequest.getText();
    String originalQuery = queryText;
    try {
      queryText = preParse(queryText, iRequest);
      textRequest.setText(queryText);

      final ODatabaseDocument database = getDatabase();

      init((OCommandRequestText) iRequest);

      String className = null;

      parserRequiredKeyword("CREATE");
      parserRequiredKeyword("VERTEX");

      String temp = parseOptionalWord(true);

      while (temp != null) {
        if (temp.equals("CLUSTER")) {
          clusterName = parserRequiredWord(false);

        } else if (temp.equals(KEYWORD_SET)) {
          fields = new ArrayList<OPair<String, Object>>();
          parseSetFields(clazz, fields);

        } else if (temp.equals(KEYWORD_CONTENT)) {
          parseContent();

        } else if (className == null && temp.length() > 0) {
          className = temp;
          if (className == null)
            // ASSIGN DEFAULT CLASS
            className = OrientVertexType.CLASS_NAME;

          // GET/CHECK CLASS NAME
          clazz = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot().getClass(className);
          if (clazz == null)
            throw new OCommandSQLParsingException("Class '" + className + "' was not found");
        }

        temp = parserOptionalWord(true);
        if (parserIsEnded())
          break;
      }

      if (className == null) {
        // ASSIGN DEFAULT CLASS
        className = OrientVertexType.CLASS_NAME;

        // GET/CHECK CLASS NAME
        clazz = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot().getClass(className);
        if (clazz == null)
          throw new OCommandSQLParsingException("Class '" + className + "' was not found");
      }
    } finally {
      textRequest.setText(originalQuery);
    }
    return this;
  }

  /**
   * Execute the command and return the ODocument object created.
   */
  public Object execute(final Map<Object, Object> iArgs) {
    if (clazz == null)
      throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet");

    // CREATE VERTEX DOES NOT HAVE TO BE IN TX
    return OGraphCommandExecutorSQLFactory.runWithAnyGraph(new OGraphCommandExecutorSQLFactory.GraphCallBack<Object>() {
      @Override
      public Object call(final OrientBaseGraph graph) {
        final OrientVertex vertex = graph.addTemporaryVertex(clazz.getName());

        if (fields != null)
          // EVALUATE FIELDS
          for (final OPair<String, Object> f : fields) {
            if (f.getValue() instanceof OSQLFunctionRuntime)
              f.setValue(((OSQLFunctionRuntime) f.getValue()).getValue(vertex.getRecord(), null, context));
          }

        OSQLHelper.bindParameters(vertex.getRecord(), fields, new OCommandParameters(iArgs), context);

        if (content != null)
          vertex.getRecord().merge(content, true, false);

        if (clusterName != null)
          vertex.save(clusterName);
        else
          vertex.save();

        return vertex.getRecord();
      }
    });
  }

  @Override
  public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
    return DISTRIBUTED_EXECUTION_MODE.LOCAL;
  }

  @Override
  public QUORUM_TYPE getQuorumType() {
    return QUORUM_TYPE.WRITE;
  }

  @Override
  public Set<String> getInvolvedClusters() {
    if (clazz != null)
      return Collections.singleton(getDatabase().getClusterNameById(clazz.getClusterSelection().getCluster(clazz, null)));
    else if (clusterName != null)
      return getInvolvedClustersOfClusters(Collections.singleton(clusterName));

    return Collections.EMPTY_SET;
  }

  @Override
  public String getSyntax() {
    return "CREATE VERTEX [<class>] [CLUSTER <cluster>] [SET <field> = <expression>[,]*]";
  }
}