fcf.module({
  name: "fcf:NFSQL/NDetails/ServerStorage.js",
  dependencies: ["fcf:NFSQL/Parser.js",
                 "fcf:NFSQL/Builder.js",
                 "fcf:NFSQL/NDetails/NConnections/MySQLConnection.js",
                 "fcf:NFSQL/NDetails/NConnections/MemoryConnection.js",
                 "fcf:NFSQL/NDetails/NConnections/FileConnection.js",
                 "fcf:NFSQL/NDetails/Errors.js",
                 "fcf:NFSQL/NDetails/Tools.js",
                 "fcf:NFSQL/NFunction/Function.js",
               ],
  module: function(Parser, Builder, MySQLConnection, MemoryConnection, FileConnection, Errors, Tools, Function) {
    var NDetails = fcf.prepareObject(fcf, "NFSQL.NDetails");
    var NFunction = fcf.prepareObject(fcf, "NFSQL.NFunction");

    NDetails.ServerStorage = function(a_options) {

      var self = this;
      this._parser      = new Parser();
      this._projections = a_options.projections;
      this._eventChannel = fcf.application.getEventChannel();

      this._connections = { };

      for(var name in a_options.connections) {
        if (a_options.connections[name].type == "mysql" || a_options.connections[name].type == "mariadb"){
          this._connections[name] = new MySQLConnection(a_options.connections[name]);
        } else if (a_options.connections[name].type == "memory"){
          var connection = fcf.append({projections: a_options.projections}, a_options.connections[name])
          this._connections[name] = new MemoryConnection(connection);
        } else if (a_options.connections[name].type == "file"){
          var connection = fcf.append({projections: a_options.projections}, a_options.connections[name])
          this._connections[name] = new FileConnection(connection);
        }
      }

      this.getConnection = function(a_name){
        a_name = !a_name ? "default" : a_name;
        return  this._connections[a_name];
      }

      this.getProjectionsByQuery = function(a_query){
        let resultMap = {};
        let queries;
        if (typeof a_query === "string")
          queries = this._parser.parse(a_query, []);
        if (!Array.isArray(queries))
          queries = [queries];
        fcf.each(queries, (a_key, a_query)=>{
          Tools.eachProjection({query: a_query}, (a_info)=>{
            resultMap[a_info.projection] = true;
          });
        });
        return fcf.array(resultMap, (k, v)=>{ return k});
      }

      this.query = function(a_options, a_cb) {
        let self = this;
        let results = [];
        let queries = [];


        // Преобразовываем в объектный тип если это необходимо
        if (typeof a_options.query == "string") {
          try {
            queries = this._parser.parse(a_options.query, a_options.args ? a_options.args : []);
          } catch(error) {
            if (a_cb)
              a_cb(error);
            return;
          }
        } else {
          queries = fcf.append(true, [], Array.isArray(a_options.query) ? a_options.query : [a_options.query]);
        }

        if (!a_options.generalInformation)
          a_options.generalInformation = {};

        return fcf.actions()
        .each(queries, function(a_key, a_query, a_res, a_act){
          a_query.type = a_query.type.toLowerCase();

          if (a_query.fullLog)
            a_options.fullLog = a_query.fullLog;

          try {
            if (a_query.type === "select"){
              var newFields = [];
              var fieldsMap = {};
              if (!Array.isArray(a_query.fields))
                throw new fcf.Exception("ERROR_NFSQL_FIELDS_NOT_SET");

              for(var i = 0; i < a_query.fields.length; ++i){
                if (a_query.fields[i].field != "*"){
                  var fieldAs = a_query.fields[i].as ?        a_query.fields[i].as :
                                a_query.fields[i].field ?     a_query.fields[i].field :
                                a_query.fields[i].function ?  a_query.fields[i].function :
                                                              "";
                  fieldsMap[fieldAs] = fieldAs;
                  newFields.push(a_query.fields[i]);
                } else {
                  var projection = a_query.fields[i].from ? a_query.fields[i].from : a_query.from;
                  var projectionData = self._projections.get(projection);
                  if (!projectionData)
                    throw new fcf.Exception("ERROR_NFSQL_INCORRECT_TYPE_QUERY", [projection]);
                  for(var j = 0; j < projectionData.fields.length; ++j){
                    var newField = {}
                    newField.field = projectionData.fields[j].alias;
                    var as         = projectionData.fields[j].alias;
                    var asRes      = projectionData.fields[j].alias;
                    var counter    = 1;
                    while(asRes in fieldsMap){
                      asRes += counter;
                      ++counter;
                    }
                    newField.as = asRes;
                    newField.from = projection;
                    if (a_query.fields[i].mode)
                      newField.mode = a_query.fields[i].mode;
                    newFields.push(newField);
                  }

                  var newField = {}
                  newField.function = "title"
                  var as      = "@title";
                  var asRes   = "@title";
                  var counter = 1;
                  while(asRes in fieldsMap){
                    asRes += counter;
                    ++counter;
                  }
                  newField.as  = asRes;
                  newField.from = projection;
                  newFields.push(newField);
                }
              }
              a_query.fields = newFields;
              self._select(a_query, a_options, results, a_act);
            } else if (a_query.type === "update"){
              self._update(a_query, a_options, results, a_act);
            } else if (a_query.type === "insert"){
              self._insert(a_query, a_options, results, a_act);
            } else if (a_query.type === "delete"){
              self._delete(a_query, a_options, results, a_act);
            }
          } catch(e) {
            a_act.error(e);
            return;
          }
        })
        .then(function(a_res, a_act){
          if (a_cb)
            a_cb(undefined, results);
          a_act.complete(results);
        })
        .catch((a_error)=>{
          if (fcf.find(a_options.hideErrors, a_error.code) !== undefined){
            var result = [];
            for(var i = 0; i < queries.length; ++i)
              result.push([]);
            a_cb(undefined, result);
          } else {
            if (a_cb)
              a_cb(a_error);
          }
        })
      }

      this._createTaskInfo = function(a_query, a_results, a_options, a_actQuery){
        let actions = fcf.actions().catch((a_error)=>{
          a_actQuery.error(a_error);
        });
        let postActions = fcf.actions({deferred: true}).catch((a_error)=>{
          a_actQuery.error(a_error);
        });

        return {
          actions:      actions,
          postActions:  postActions,
          query:        a_query,
          storage:      this,
          preJoinQueries: [],
          postJoinQueries: [],
          originQuery:  fcf.append(true, {}, a_query),
          options:      a_options,
          projections:  this._projections.getProjections(),
          projection:   this._projections.getProjections()[a_query.from],
          aliases:      {},
          _records:     undefined,
          getResult:    function(){
            return fcf.last(a_results);
          },
          getTableAlias:  function(a_alias){
            return a_alias in this.aliases ? a_alias :
                 this.projections[a_alias] ? this.projections[a_alias].table :
                                             a_alias;

          },
          getQueryTableByField:  function(a_field){
            var projection = this.getProjection(a_field.from);
            if (!projection)
              projection   = this.projection;
            var fieldDesc  = projection.fieldsMap[a_field.field];
            return   fieldDesc.realjoin ? this.getTableAlias(fieldDesc.realjoin.from) :
                     a_field.from ? this.getTableAlias(a_field.from) :
                                    projection.table;
          },
          getQueryField:  function(a_field){
            var projection = this.getProjection(a_field.from);
            var fieldDesc  = projection.fieldsMap[a_field.field];
            return fieldDesc.realjoin ? fieldDesc.realjoin.field
                                  : fieldDesc.field;
          },
          getProjection: function(a_alias) {
            return  !a_alias                ? this.projection :
                    a_alias in this.aliases ? this.projections[this.aliases[a_alias]] :
                                              this.projections[a_alias];
          },
          resetRecords: function(){
            self._records = undefined;
          },
          loadRecords: function(a_cb){
            let self = this;
            if (self._records){
              if (a_cb)
                a_cb(self._records);
              return fcf.actions().then(()=>{ return self._records; });
            }
            let query = {
              type: "select",
              fields: [],
              from: self.originQuery.from,
              where: self.originQuery.where,
            };
            if (self.query.type == "insert"){
              query.where = [{ type: "=", args: [ {field: self.projection.key}, {value: self.getResult()[0]["@key"]} ]}];
            }
            fcf.each(self.projection.fields, (a_key, a_field) => {
              query.fields.push({ field: a_field.alias, mode: "short"});
            });

            let ret = self.storage.query(
              fcf.append({}, self.options, { query: query, results: fcf.append([], a_results) }),
              (a_error, a_records) => {
                self._records = a_records ? a_records[0] : [];
                if (a_cb)
                  a_cb(self._records);
              }
            );

            return ret
            .then(()=>{
              return self._records;
            })
          },
          getFieldInfo: function(a_filed){
            a_filed = typeof a_filed === "string" ? {field: a_filed} : a_filed;

            let projectionAlias = a_filed.from ? a_filed.from : this.originQuery.from;
            let projection      = this.getProjection(projectionAlias);

            return projection.fieldsMap[a_filed.field];
          },
          getProjectionField: function(a_filed){
            a_filed = typeof a_filed === "string" ? {field: a_filed} : a_filed;

            let projectionAlias = a_filed.from ? a_filed.from : this.originQuery.from;
            return this.getProjection(projectionAlias);
          },
          getSourceProjectionField: function(a_filed){
            a_filed = typeof a_filed === "string" ? {field: a_filed} : a_filed;
            var fieldInfo = this.getFieldInfo(a_filed);
            if (fieldInfo.realjoin && fieldInfo.realjoin.from){
              return this.getProjection(fieldInfo.realjoin.from);
            } else {
              let projectionAlias = a_filed.from ? a_filed.from : this.originQuery.from;
              return this.getProjection(projectionAlias);
            }
          },
          getSourceFromField: function(a_filed){
            a_filed = typeof a_filed === "string" ? {field: a_filed} : a_filed;
            var fieldInfo = this.getFieldInfo(a_filed);
            if (fieldInfo.realjoin && fieldInfo.realjoin.from){
              return fieldInfo.realjoin.as ? fieldInfo.realjoin.as : fieldInfo.realjoin.from;
            } else {
              return projectionAlias = a_filed.from ? a_filed.from : this.originQuery.from;
            }
          },
          setAs: function(a_field, a_as, a_table){
            if (a_field.as)
              return a_field.as;
            if (!!a_table && a_table != this.query.from)
              a_as = this + "." + a_as;
            a_field.as = a_as;
            return a_field.as
          },
          setAutoAs: function(a_field, a_table){
            if (a_field.as)
              return a_field.as;
            let as = a_field.recommendedAs ? a_field.recommendedAs : a_field.field;
            if (!!a_table && a_table != this.query.from){
              let isSysJoin = fcf.each(this.projection.join, (a_key, a_join) =>{
                if (a_join.as == a_table  || (!a_join.as && a_join.from == a_table))
                  return true;
              });
              if (!isSysJoin)
                as = a_table + "." + as;
            }
            fcf.each(a_field.path, (a_key, a_subitem) =>{
              as += "->" + a_subitem;
            });
            a_field.as = as;
            return as;
          },
          getConnection: function(a_alias){
            let projection      = this.getProjection(a_alias) ? this.getProjection(a_alias) : this.getProjection(this.originQuery.from);
            return self._connections[projection.connectionGroup] ? self._connections[projection.connectionGroup]
                                                                 : self._connections["default"];
          }
        }
      }

      this._delete = function(a_query, a_options, a_results, a_actQuery){
        let taskInfo = this._createTaskInfo(a_query, a_results, a_options, a_actQuery);
        let records = [];
        let projection = undefined;

        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send("fsql_delete_before", { query: taskInfo.query, projection: taskInfo.query.from, options: taskInfo.options });
        });

        taskInfo.actions.then(()=>{
          // Минимальная проверка целостности запроса
          if (fcf.empty(taskInfo.projection))
            throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_QUERY", [taskInfo.query.from]);
          projection =  taskInfo.getProjection(taskInfo.query.from);
          if (!projection)
            throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_QUERY", [taskInfo.query.from]);

          // Добавление join-ов
          if (!Array.isArray(taskInfo.query.join))
            taskInfo.query.join = [];

          // Join-ы с проекций
          fcf.each(taskInfo.projection.join, (a_key, a_join) => {
            let joinOn = fcf.append(true, [], self.getWhereBlock(a_join.on));
            Tools.eachFieldBlockWhere(taskInfo, joinOn, (a_info) => {
              if (!a_info.field.from)
                a_info.field.from = taskInfo.query.from;
            });

            taskInfo.query.join.push({
              from: a_join.from,
              as:   a_join.as,
              on:   joinOn,
            });

            if (a_join.as)
              taskInfo.aliases[a_join.as] = a_join.from;
          });

          // Проставляем значения подстановки из предыдущих запроссов
          Tools.eachReplacement(taskInfo, taskInfo.query, (a_info) => {
            if (! (a_info.field.result in a_results)){
              return;
            }
            if (! (a_info.field.record in a_results[a_info.field.result])){
              return;
            }
            if (! (a_info.field.item in a_results[a_info.field.result][a_info.field.record])){
              return;
            }
            a_info.field.value = a_results[a_info.field.result][a_info.field.record][a_info.field.item];
          });
          Tools.eachReplacement(taskInfo, taskInfo.originQuery, (a_info) => {
            if (! (a_info.field.result in a_results)){
              return;
            }
            if (! (a_info.field.record in a_results[a_info.field.result])){
              return;
            }
            if (! (a_info.field.item in a_results[a_info.field.result][a_info.field.record])){
              return;
            }
            a_info.field.value = a_results[a_info.field.result][a_info.field.record][a_info.field.item];
          });


          // Подготовка запроса проставляем from для полей
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            if (!a_info.field.from)
              a_info.field.from = taskInfo.query.from;
          });


          // Проверка полей запроса
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            var projection =  taskInfo.getProjection(a_info.field.from);
            if (!projection){
              throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_FIELD", {projection: a_info.field.from, field: a_info.field.field});
            }
            if (!projection.fieldsMap[a_info.field.field])
              throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_FIELD_IN_QUERY", {projection: a_info.field.from, field: a_info.field.field});
          });

          // Проверка функций запроса блока WHERE
          Tools.eachFunctions(taskInfo, taskInfo.query, (a_info) => {
            var funcHandler = NFunction.getFunction(a_info.function.function);
            if (!funcHandler)
              throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_FUNCTION_IN_QUERY", {function: a_info.function.function });
          });

        });

        // Запрашиваем удаляемые поля
        taskInfo.actions.then(async () => {
          let mirrors = fcf.application.getProjections().getMirrors(projection.alias);
          if (!fcf.empty(taskInfo.projection.join) || (taskInfo.projection.translate && !a_options.ignoreTranslate) || !fcf.empty(mirrors)) {
            records = await taskInfo.loadRecords();
          }
        });

        // Удаляем записи переводов
        taskInfo.actions.then(() => {
          if (taskInfo.projection.translate && !fcf.empty(fcf.first(records)) && !a_options.ignoreTranslate){
            let query = {
              type: "delete",
              from: "___fcftranslate___" + projection.alias,
              where: [],
            };
            fcf.each(records, (a_key, a_record) => {
              query.where.push({ logic: "or", type: "=", args: [{field: taskInfo.projection.key}, {value: a_record["@key"]}] } );
            })
            return self.query(fcf.append({}, taskInfo.options, {query: query, ignoreTranslate: true}));
          }
        })

        taskInfo.actions.then((a_res) => {
          if (fcf.empty(taskInfo.projection.join))
            return;

          fcf.each(taskInfo.projection.join, (a_key, a_join) => {
            if (a_join.attach != "mirror" && a_join.attach != "extends")
              return;

            if (fcf.find(taskInfo.query.disableJoins, (k, as)=>{ return a_join.as == as; }) !== undefined)
              return;

            function clEachBlock(a_blocks, a_cb){
              fcf.each(a_blocks, (a_key, a_block)=>{
                if (a_block.type == "block")
                  clEachBlock(a_block.args, a_cb);
                else
                  a_cb(a_block);
              });
            }
            function clFillValues(a_selfProjection, a_joinProjection, a_join, a_block, a_record){
              function clFillArgs(a_arg1, a_arg2){
                let defaultProjectionAlias = a_selfProjection.alias;
                let arg1From = a_arg1.field && a_arg1.from ? a_arg1.from : defaultProjectionAlias;
                if (a_arg1.field && arg1From === a_join.as)
                  arg1From = a_join.from;
                let arg2From = a_arg2.field && a_arg2.from ? a_arg2.from : defaultProjectionAlias;
                if (a_arg2.field && arg2From === a_join.as)
                  arg2From = a_join.from;
                if (a_arg1.field && a_arg2.field &&
                    arg1From == a_selfProjection.alias &&
                    arg2From == a_joinProjection.alias) {
                  a_arg1.value = a_record[a_arg1.field];
                  delete a_arg1.field;
                  delete a_arg1.from;
                  delete a_arg2.from;
                  return true;
                }
              }
              if (!clFillArgs(a_block.args[0], a_block.args[1]))
                clFillArgs(a_block.args[1], a_block.args[0]);
            }

            let block = typeof a_join.on == "string" ? self._parser.parseWhere(a_join.on, []) : a_join.on;
            if (fcf.empty(block))
              return;

            let resultWhere = [];
            for(let i = 0; i < records.length; ++i){
              let curBlock = fcf.append(true, [], block);
              clEachBlock(curBlock, (a_block) => {
                if (a_block.type != "=")
                  return;
                clFillValues(taskInfo.projection, taskInfo.getProjection(a_join.from), a_join, a_block, records[i]);
              });
              resultWhere.push({logic: "or", type:"block", args: curBlock});
            }

            if (fcf.empty(resultWhere))
              return;

            let as = a_join.as ? a_join.as : a_join.from;
            let proj = taskInfo.projections[a_join.from];
            let record = fcf.first2(records, {});
            let query = {
              type:         "delete",
              from:         a_join.from,
              where:        resultWhere,
              disableJoins: [a_join.as],
            };

            fcf.each(taskInfo.query.disableJoins, (a_key, a_joinAlias)=>{
              if (fcf.find(query.disableJoins, a_joinAlias) !== undefined)
                return;
              query.disableJoins.push(a_joinAlias);
            })

            if (a_join.referencing)
              taskInfo.preJoinQueries.push({query: query, join: a_join, projection: proj});
            else
              taskInfo.postJoinQueries.push({query: query, join: a_join, projection: proj});
          });
        });

        // Обработка полей запроса
        taskInfo.actions.then(async (a_res) => {
          let actions = fcf.actions();

          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            let fieldInfo = taskInfo.getFieldInfo(a_info.field);
            let filter = fcf.getFilter(fieldInfo.type);
            let result = filter.processWhereField(taskInfo, a_info);
            if (result instanceof Promise || result instanceof fcf.Actions)
              actions.then(()=>{ return result; });
          });

          fcf.each(taskInfo.projection.fields, (a_key, a_field) =>{
            let info = {
              part:  "fields",
              field: { field: a_field.alias, }
            };
            let filter = fcf.getFilter(a_field.type);
            let result = filter.processDeleteField(taskInfo, info);
            if (result instanceof Promise || result instanceof fcf.Actions)
              actions.then(()=>{ return result; })
          });

          return actions;
        });

        // Обработка функций в запросе
        taskInfo.actions.then((a_res) => {
          Tools.eachFunctions(taskInfo, taskInfo.query, (a_info) => {
            var func = NFunction.getFunction(a_info.function.function);
            func.processFunction(taskInfo, a_info);
          });
        });

        // Меняем на реальные имена таблиц
        taskInfo.actions.then((a_res) => {
          Tools.eachProjection(taskInfo, (a_info) => {
            if (a_info.block.processed)
              return;
            a_info.block[a_info.key] = taskInfo.getProjection(a_info.projection).table;
          });
        });

        taskInfo.actions.then((a_res) => {
          return fcf.actions()
          .each(taskInfo.preJoinQueries, (a_key, a_info, a_res, a_act) => {
            let results = [];
            self._delete(a_info.query, a_options, results, a_act);
          })
        });

        // Выполняем сам запрос
        taskInfo.actions.then((a_res, a_act) => {
          let connection = taskInfo.getConnection();
          connection.query(taskInfo.query, taskInfo.options, function(a_error, a_rows){
            if (a_error){
              a_act.error(a_error);
              return;
            }
            a_results.push(a_rows);
            a_act.complete();
          });
        });

        taskInfo.actions.then((a_res) => {
          return fcf.actions()
          .each(taskInfo.postJoinQueries, (a_key, a_info, a_res, a_act) => {
            let results = [];
            self._delete(a_info.query, a_options, results, a_act);
          });
        });

        taskInfo.actions.then(() => {
          return taskInfo.postActions.startup();
        });

        // Заполянем дочерние join таблицы режима зеркала
        taskInfo.actions.then(async ()=>{
          let mirrors = fcf.application.getProjections().getMirrors(projection.alias);
          if (fcf.empty(mirrors))
            return;
          function clEachBlock(a_blocks, a_cb){
            fcf.each(a_blocks, (a_key, a_block)=>{
              if (a_block.type == "block")
                clEachBlock(a_block.args, a_cb);
              else
                a_cb(a_block);
            });
          }
          function clFillValues(a_selfProjection, a_joinProjection, a_join, a_block, a_record){
            function clFillArgs(a_arg1, a_arg2){
              let defaultProjectionAlias = a_joinProjection.alias;
              let arg1From = a_arg1.field && a_arg1.from ? a_arg1.from : defaultProjectionAlias;
              if (a_arg1.field && a_join.as && arg1From === a_join.as)
                arg1From = a_join.from;
              let arg2From = a_arg2.field && a_arg2.from ? a_arg2.from : defaultProjectionAlias;
              if (a_arg2.field && a_join.as && arg2From === a_join.as)
                arg2From = a_join.from;
              if (a_arg1.field && a_arg2.field &&
                  arg1From == a_selfProjection.alias &&
                  arg2From == a_joinProjection.alias) {
                a_arg1.value = a_record[a_arg1.field];
                delete a_arg1.field;
                delete a_arg1.from;
                delete a_arg2.from;
                return true;
              }
            }
            if (!clFillArgs(a_block.args[0], a_block.args[1]))
              clFillArgs(a_block.args[1], a_block.args[0]);
          }
          for(let recordIndex = 0; recordIndex < records.length; ++recordIndex) {
            let record = records[recordIndex];
            for(let i = 0; i < mirrors.length; ++i){
              let mirrorProjection = fcf.application.getProjections().get(mirrors[i]);
              let mirrorProjectionJoins = Array.isArray(mirrorProjection.join) ? mirrorProjection.join : [];
              let insertValues = {};
              for(let joinIndex = 0; joinIndex < mirrorProjectionJoins.length; ++joinIndex) {
                let join = mirrorProjectionJoins[joinIndex];
                if (join.from != projection.alias)
                  continue;
                if (fcf.find(taskInfo.query.disableJoins, join.as) !== undefined)
                  continue;
                let where = typeof join.on === "string" ? self._parser.parseWhere(join.on, []) : join.on;
                if (fcf.empty(where))
                  continue;
                clEachBlock(where, (a_block)=>{
                  if (a_block.type != "=")
                    return;
                  clFillValues(projection, mirrorProjection, join, a_block, record);
                });
                let mirrorDeleteQuery = {
                  type: "delete",
                  from: mirrorProjection.alias,
                  where: where,
                  disableJoins: [join.as],
                }
                for(let key in insertValues)
                  mirrorDeleteQuery.values.push({field: key, value: insertValues[key]});

                fcf.each(taskInfo.query.disableJoins, (a_key, a_joinAlias)=>{
                  if (fcf.find(mirrorDeleteQuery.disableJoins, a_joinAlias) !== undefined)
                    return;
                  mirrorDeleteQuery.disableJoins.push(a_joinAlias);
                });

                let options = fcf.append({}, taskInfo.options, {query: mirrorDeleteQuery, results: []});
                await self.query(options);
              }
            }
          }
        });

        // Send 'fsql_delete_after:[PROJECTION]' message
        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send(`fsql_delete_after:${taskInfo.originQuery.from}`, { query: taskInfo.originQuery, projection: taskInfo.originQuery.from, options: taskInfo.options });
        });

        // Send 'fsql_delete_after' message
        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send('fsql_delete_after', { query: taskInfo.originQuery, projection: taskInfo.originQuery.from, options: taskInfo.options });
        });

        taskInfo.actions.then(() => {
          a_actQuery.complete();
        });

        taskInfo.actions.catch((a_error)=>{
          a_actQuery.error(a_error);
        })
      }

      let counter = 0;

      this._insert = function(a_query, a_options, a_results, a_actQuery){
        let projection = undefined;
        let record = {};
        let translateQueries = [];
        let taskInfo = this._createTaskInfo(a_query, a_results, a_options, a_actQuery);

        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send("fsql_insert_before", { query: taskInfo.query, projection: taskInfo.query.from, options: taskInfo.options });
        });

        taskInfo.actions.then(()=>{
          // Минимальная проверка целостности запроса
          if (fcf.empty(taskInfo.projection))
            throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_QUERY", [taskInfo.query.from]);
          projection =  taskInfo.getProjection(taskInfo.query.from);
          if (!projection)
            throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_QUERY", [taskInfo.query.from]);

          // Проставляем значения подстановки из предыдущих запроссов
          Tools.eachReplacement(taskInfo, taskInfo.query, (a_info) => {
            if (! (a_info.field.result in a_results)){
              return;
            }
            if (! (a_info.field.record in a_results[a_info.field.result])){
              return;
            }
            if (! (a_info.field.item in a_results[a_info.field.result][a_info.field.record])){
              return;
            }
            a_info.field.value = a_results[a_info.field.result][a_info.field.record][a_info.field.item];
          });

          // Составляем запрос на вставку записи переводов
          if (!a_options.ignoreTranslate && taskInfo.projection.translate) {
            fcf.each(fcf.application.getSystemVariable("fcf:languages"), (a_lang)=>{
              if (fcf.application.getSystemVariable("fcf:defaultLanguage") == a_lang)
                return;
              if (a_lang == a_query.language) {
                let translateQuery = {
                  type: "insert",
                  from: "___fcftranslate___" + a_query.from,
                  values: [ ],
                  fullLog: taskInfo.query.fullLog,
                };
                fcf.each(taskInfo.query.values, (a_key, a_field)=>{
                  if (!projection.fieldsMap[a_field.field].translate)
                    return;
                  if (projection.key == a_field.field)
                    return;
                  translateQuery.values.push(fcf.clone(a_field));
                })
                translateQuery.values.push({field: "___fcftranslate___language", value: a_query.language})
                translateQueries.push(translateQuery);
              } else {
                translateQueries.push({
                  type: "insert",
                  from: "___fcftranslate___" + a_query.from,
                  values: [ {field: "___fcftranslate___language", value: a_lang}],
                  fullLog: taskInfo.query.fullLog,
                });
              }
            })
          }

          // Подготовка запроса проставляем from для полей
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            if (a_info.part == "where" || a_info.part == "fields") {
              if (!a_info.field.from)
                a_info.field.from = taskInfo.query.from;
            }
          });

          // Проставляем значения default
          fcf.each(taskInfo.query.values, (a_key, a_info) => { record[a_info.field] = a_info.value });
          fcf.each(taskInfo.projection.fields, (a_key, a_field) => {
            if (a_field.alias in record)
              return;
            if (fcf.empty(a_field.default))
              return;
            let defaultValue = fcf.pattern(a_field.default);
            taskInfo.query.values.push({
              field: a_field.alias,
              value: defaultValue,
              from:  taskInfo.projection.alias,
            })
          });

          // Добавление join-ов
          if (!Array.isArray(taskInfo.query.join))
            taskInfo.query.join = [];
          fcf.each(taskInfo.projection.join, (a_key, a_join) => {
            if (a_join.as)
              taskInfo.aliases[a_join.as] = a_join.from;
          });

          // Проверка полей запроса
          // И обрабатываем поля emptyAsNull
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            var projection =  taskInfo.getProjection(a_info.field.from);
            if (!projection)
              throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_FIELD", {projection: a_info.field.from, field: a_info.field.field});
            if (!projection.fieldsMap[a_info.field.field])
              throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_FIELD_IN_QUERY", {projection: a_info.field.from, field: a_info.field.field});

            if (a_info.part == "fields") {
              if (projection.fieldsMap[a_info.field.field].emptyAsNull && fcf.empty(a_info.field.value))
                a_info.field.value = null
            }
          });

          // Выносим JOIN поля
          let joinValues = {};
          let newValues = [];
          fcf.each(taskInfo.query.values, (a_key, a_value) => {
            let fieldInfo = taskInfo.getFieldInfo(a_value);
            if (fieldInfo.join)
              joinValues[fieldInfo.alias] = a_value;
            else
              newValues.push(a_value);
          });
          taskInfo.query.values = newValues;

          // Формируем JOIN запросы
          fcf.each(taskInfo.projection.join, (a_key, a_join) => {
            if (a_join.attach != "mirror" && a_join.attach != "extends")
              return;
            if (fcf.find(a_query.disableJoins, (k, as)=>{ return a_join.as == as; }) !== undefined)
              return;
            let query = {
              type: "insert",
              from: a_join.from,
              values: [],
              disableJoins: [a_join.as],
            }
            fcf.each(taskInfo.query.disableJoins, (a_key, a_joinAlias)=>{
              if (fcf.find(query.disableJoins, a_joinAlias) !== undefined)
                return;
              query.disableJoins.push(a_joinAlias);
            })
            fcf.each(joinValues, (a_alias, a_value) => {
              let fieldInfo = taskInfo.projection.fieldsMap[a_alias];
              if ((a_join.as && fieldInfo.join.from == a_join.as) || fieldInfo.join.from == a_join.from) {
                let joinFieldInfo = taskInfo.projections[query.from].fieldsMap[fieldInfo.join.field];
                query.values.push({
                  field: fieldInfo.join.field,
                  value: a_value.value
                });
              }
            });
            if (a_join.referencing)
              taskInfo.postJoinQueries.push({query: query, join: a_join, projection: taskInfo.projections[query.from]});
            else
              taskInfo.preJoinQueries.push({query: query, join: a_join, projection: taskInfo.projections[query.from]});
          });
        });

        // Выполняаем пре запросы join
        taskInfo.actions.then((a_res, a_subact) => {
          if (fcf.empty(taskInfo.preJoinQueries)){
            a_subact.complete();
            return;
          }

          let results = [];
          fcf.actions()
          .catch((a_error)=>{
            a_subact.error(a_error);
          })
          .each(taskInfo.preJoinQueries, (a_key, a_queryInfo, a_res, a_act) => {
            // Определяем нужно ли выполнять создание записи, по заполнености ссылочного поля
            var refs = {};
            function clFillRefs(a_block){
              fcf.each(a_block, (a_key, a_item)=>{
                if (a_item.type == "="){
                  let joinAlais  = a_queryInfo.join.as ? a_queryInfo.join.as : a_queryInfo.join.from;
                  let selfField  = !a_item.args[0].from || a_item.args[0].from == taskInfo.projection.alias ? a_item.args[0].field :
                                    !a_item.args[1].from || a_item.args[1].from == taskInfo.projection.alias ? a_item.args[1].field :
                                                                                                              undefined;
                  let joinField = a_item.args[0].from == joinAlais ? a_item.args[0].field :
                                  a_item.args[1].from == joinAlais ? a_item.args[1].field :
                                                                    undefined;
                  if (!selfField || !joinField)
                    return;
                  refs[selfField] = joinField;
                } else if (a_item.type == "block"){
                  clFillRefs(a_item.args);
                }
              });
            }

            clFillRefs(self.getWhereBlock(a_queryInfo.join.on));
            let skipQuery = false;
            fcf.each(taskInfo.values, (a_field, a_value) => {
              if (a_field in refs){
                skipQuery = true;
                return 1;
              }
            });
            if (skipQuery){
              a_act.complete();
              return;
            }

            // Выполняем вставку join записи
            var options = fcf.append({}, a_options, {query: a_queryInfo.query});
            self.query(
              options,
              function(a_error, a_result){
                if (a_error){
                  a_act.error(a_error);
                  return;
                }

                // Получаем значения вставленной записи и дополняем ими основной запрос
                let query = {
                  type: "select",
                  from: a_queryInfo.projection.alias,
                  fields: [],
                  where: [{type: "=", args: [{field: a_queryInfo.projection.key}, {value: fcf.first2(a_result)["@key"] }] }]
                }
                fcf.each(refs, (a_selfField, a_joinField) => { query.fields.push({"field": a_joinField, as: a_selfField}) })
                let options = fcf.append({}, a_options, { query: query });
                self.query(options, (a_error, a_result) =>{
                  if (a_error){
                    a_act.error(a_error);
                    return;
                  }
                  fcf.each(fcf.first2(a_result), (a_field, a_value) => {
                    if (a_field[0] == "@")
                      return;
                    taskInfo.query.values.push({field: a_field, value: a_value, from: taskInfo.query.from});
                  });
                  a_act.complete();
                });

              }
            );
          })
          .then((a_res, a_act) => {
            a_act.complete();
            a_subact.complete();
          });
        });

        // Проверка полей запроса после выноса JOIN полей
        taskInfo.actions.then(()=>{
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            if (a_info.part == "fields"){
              var fieldInfo = projection.fieldsMap[a_info.field.field];
              var filter = fcf.getFilter(fieldInfo.type);
              var errors = [];
              filter.validate({data: a_info.field.value, field: fieldInfo}, errors);
              if (!fcf.empty(errors))
                throw fcf.first(errors);
            }
          });
        });

        // Обработка полей запроса
        taskInfo.actions.then((a_res) => {
          let actions = fcf.actions();
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            var fieldInfo = taskInfo.getFieldInfo(a_info.field);
            var filter = fcf.getFilter(fieldInfo.type);
            let result = filter.processInsertField(taskInfo, a_info);
            if (result instanceof Promise || result instanceof fcf.Actions)
              actions.then(()=>{ return result; });
          });
          return actions;
        });

        // Меняем на реальные имена таблиц
        taskInfo.actions.then((a_res, a_act) => {
          Tools.eachProjection(taskInfo, (a_info) => {
            if (a_info.block.processed)
              return;
            a_info.block[a_info.key] = taskInfo.getProjection(a_info.projection).table;
          });
          a_act.complete();
        });

        // Выполняем сам запрос
        taskInfo.actions.then((a_res, a_act) => {
          //Устанавливаем параметр опций insertKey если вставляется ключ
          let keyRealField = projection.fieldsMap[projection.key].field;
          for(let i = 0; i < taskInfo.query.values.length; ++i) {
            if (taskInfo.query.values[i].field === keyRealField){
              taskInfo.options.insertKey = taskInfo.query.values[i].value;
              break;
            }
          }

          let connection = taskInfo.getConnection();

          connection.query(taskInfo.query, taskInfo.options, function(a_error, a_rows){
            if (a_error){
              a_act.error(a_error);
              return;
            }
            a_results.push(a_rows);
            a_act.complete();
          });
        });


        // Выполняем вставку переводов
        taskInfo.actions.then(()=>{
          if (!fcf.empty(translateQueries) && projection.translate){
            fcf.each(translateQueries, (a_key, a_query)=>{
              a_query.values.push({field: projection.key, value: fcf.last2(a_results)["@key"]})
            })
            let options = fcf.append({}, taskInfo.options,{query: translateQueries, ignoreTranslate: true});
            return self.query(options);
          }
        });

        // Заполянем дочерние join таблицы режима зеркала
        taskInfo.actions.then(async ()=>{
          let mirrors = fcf.application.getProjections().getMirrors(projection.alias);
          if (fcf.empty(mirrors))
            return;
          function clEachBlock(a_blocks, a_cb){
            fcf.each(a_blocks, (a_key, a_block)=>{
              if (a_block.type == "block")
                clEachBlock(a_block.args, a_cb);
              else
                a_cb(a_block);
            });
          }
          function clFillValues(a_selfProjection, a_joinProjection, a_join, a_block, a_record, a_dstValues){
            function clFillArgs(a_arg1, a_arg2){
              let defaultProjectionAlias = a_joinProjection.alias;
              let arg1From = a_arg1.field && a_arg1.from ? a_arg1.from : defaultProjectionAlias;
              if (a_arg1.field && a_join.as && arg1From === a_join.as)
                arg1From = a_join.from;
              let arg2From = a_arg2.field && a_arg2.from ? a_arg2.from : defaultProjectionAlias;
              if (a_arg2.field && a_join.as && arg2From === a_join.as)
                arg2From = a_join.from;
              if (a_arg1.field && a_arg2.field &&
                  arg1From == a_selfProjection.alias &&
                  arg2From == a_joinProjection.alias) {
                a_dstValues[a_arg2.field] = a_record[a_arg1.field];
              } else if ("value" in a_arg1 && a_arg2.field &&  arg2From == a_joinProjection.alias) {
                a_dstValues[a_arg2.field] = a_arg1.value;
              }
            }
            clFillArgs(a_block.args[0], a_block.args[1]);
            clFillArgs(a_block.args[1], a_block.args[0]);
          }
          let record = await taskInfo.loadRecords();
              record = record[0];
          for(let i = 0; i < mirrors.length; ++i){
            let mirrorProjection = fcf.application.getProjections().get(mirrors[i]);
            let mirrorProjectionJoins = Array.isArray(mirrorProjection.join) ? mirrorProjection.join : [];
            let insertValues = {};
            for(let joinIndex = 0; joinIndex < mirrorProjectionJoins.length; ++joinIndex) {
              let join = mirrorProjectionJoins[joinIndex];
              if (join.from != projection.alias)
                continue;
              if (fcf.find(taskInfo.query.disableJoins, join.as) !== undefined)
                continue;
              clEachBlock(typeof join.on === "string" ? self._parser.parseWhere(join.on, []) : join.on, (a_block)=>{
                if (a_block.type != "=")
                  return;
                clFillValues(projection, mirrorProjection, join, a_block, record, insertValues);
              });
              if (fcf.empty(insertValues))
                continue;
              let mirrorInsertQuery = {
                type: "insert",
                from: mirrorProjection.alias,
                values: [],
                disableJoins: [join.as],
              }
              for(let key in insertValues)
                mirrorInsertQuery.values.push({field: key, value: insertValues[key]});

              fcf.each(taskInfo.query.disableJoins, (a_key, a_joinAlias)=>{
                if (fcf.find(mirrorInsertQuery.disableJoins, a_joinAlias) !== undefined)
                  return;
                mirrorInsertQuery.disableJoins.push(a_joinAlias);
              })

              let options = fcf.append({}, taskInfo.options, {query: mirrorInsertQuery, results: []});
              await self.query(options);
            }
          }
        });

        // Выполняем пост запросы join
        taskInfo.actions.then((a_res, a_subact) => {
          if (fcf.empty(taskInfo.postJoinQueries)){
            a_subact.complete();
            return;
          }

          let mainRecord = {};
          fcf.each(taskInfo.query.values, (a_key, a_value) => { mainRecord[a_value.field] = a_value.value; });
          mainRecord[taskInfo.projection.key] = fcf.last2(a_results)["@key"];

          fcf.actions()
          .catch((a_error)=>{
            a_subact.error(a_error);
          })
          .each(taskInfo.postJoinQueries, (a_key, a_queryInfo, a_res, a_act) => {
            let values = {};
            function clFillValues(a_block){
              fcf.each(a_block, (a_key, a_item)=>{
                if (a_item.type == "="){
                  let joinAlais  = a_queryInfo.join.as ? a_queryInfo.join.as : a_queryInfo.join.from;
                  let selfField  = !a_item.args[0].from || a_item.args[0].from == taskInfo.projection.alias ? a_item.args[0].field :
                                    !a_item.args[1].from || a_item.args[1].from == taskInfo.projection.alias ? a_item.args[1].field :
                                                                                                              undefined;
                  let joinField = a_item.args[0].from == joinAlais ? a_item.args[0].field :
                                  a_item.args[1].from == joinAlais ? a_item.args[1].field :
                                                                    undefined;
                  if (!selfField || !joinField)
                    return;
                  values[joinField] = mainRecord[selfField];
                } else if (a_item.type == "block"){
                  clFillValues(a_item.args);
                }
              });
            }
            clFillValues(self.getWhereBlock(a_queryInfo.join.on))
            fcf.each(values, (a_alias, a_value) => {
              a_queryInfo.query.values.push({ field: a_alias, value: a_value });
            });

            // Выполняем сам запрос
            let results = [];
            self._insert(a_queryInfo.query, a_options, results, a_act);
          })
          .then((a_res, a_act) => {
            a_act.complete();
            a_subact.complete();
          });
        });

        taskInfo.actions.then(() => {
          return taskInfo.postActions.startup();
        });

        // Send 'fsql_insert_after:[PROJECTION]' message
        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send(`fsql_insert_after:${taskInfo.originQuery.from}`, { query: taskInfo.originQuery, projection: taskInfo.originQuery.from, options: taskInfo.options, result: taskInfo.result });
        });

        // Send 'fsql_insert_after' message
        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send("fsql_insert_after", { query: taskInfo.originQuery, projection: taskInfo.originQuery.from, options: taskInfo.options, result: taskInfo.result });
        });

        taskInfo.actions.then(() => {
          a_actQuery.complete();
        });

        taskInfo.actions.catch((a_error)=>{
          a_actQuery.error(a_error);
        })
      }

      this._update = function(a_query, a_options, a_results, a_actQuery){
        let result = undefined;
        let originWhere = undefined;
        let translateQuery = undefined;
        let resultIndex = 0;
        let projection = undefined;
        let taskInfo = this._createTaskInfo(a_query, a_results, a_options, a_actQuery);


        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send("fsql_update_before", { query: taskInfo.query, projection: taskInfo.query.from, options: taskInfo.options });
        });

        taskInfo.actions.then(()=>{
          // Минимальная проверка целостности запроса
          if (fcf.empty(taskInfo.projection))
            throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_QUERY", [taskInfo.query.from]);
          projection =  taskInfo.getProjection(taskInfo.query.from);
          if (!projection)
            throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_QUERY", [taskInfo.query.from]);
          originWhere = fcf.append(true, [], a_query.where);

          // Проставляем значения подстановки из предыдущих запроссов
          Tools.eachReplacement(taskInfo, taskInfo.query, (a_info) => {
            if (! (a_info.field.result in a_results)){
              return;
            }
            if (! (a_info.field.record in a_results[a_info.field.result])){
              return;
            }
            if (! (a_info.field.item in a_results[a_info.field.result][a_info.field.record])){
              return;
            }
            a_info.field.value = a_results[a_info.field.result][a_info.field.record][a_info.field.item];
          });
          Tools.eachReplacement(taskInfo, taskInfo.originQuery, (a_info) => {
            if (! (a_info.field.result in a_results)){
              return;
            }
            if (! (a_info.field.record in a_results[a_info.field.result])){
              return;
            }
            if (! (a_info.field.item in a_results[a_info.field.result][a_info.field.record])){
              return;
            }
            a_info.field.value = a_results[a_info.field.result][a_info.field.record][a_info.field.item];
          });

          // Подготовка запроса проставляем from для полей
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            if (a_info.part == "where" || a_info.part == "fields") {
              if (!a_info.field.from)
                a_info.field.from = taskInfo.query.from;
            }
          });


          // Добавление join-ов
          taskInfo.query.join = [];
          fcf.each(taskInfo.projection.join, (a_key, a_join) => {
            let joinOn = fcf.append(true, [], self.getWhereBlock(a_join.on));
            Tools.eachFieldBlockWhere(taskInfo, joinOn, (a_info) => {
              if (!a_info.field.from)
                a_info.field.from = taskInfo.query.from;
            });

            taskInfo.query.join.push({
              from: a_join.from,
              as:   a_join.as,
              on:   joinOn,
            });

            if (a_join.as)
              taskInfo.aliases[a_join.as] = a_join.from;
          });

          // Составляем запрос на вставку записи переводов
          if (a_query.language && fcf.application.getSystemVariable("fcf:defaultLanguage") != a_query.language && fcf.application.getSystemVariable("fcf:languages")[a_query.language]) {
            var isEmpty = true;
            translateQuery = {
              type:     "update",
              from:     "___fcftranslate___" + a_query.from,
              values:   [ ],
              fullLog:  taskInfo.query.fullLog,
            };
            let newValues = [];
            fcf.each(taskInfo.query.values, (a_key, a_field)=>{
              let projectionName = !a_field.from                  ? projection.alias :
                                   taskInfo.aliases[a_field.from] ? taskInfo.aliases[a_field.from] :
                                                                    a_field.from;
              let prjectionField = taskInfo.getProjection(projectionName);

              if (!prjectionField.fieldsMap[a_field.field].translate){
                newValues.push(a_field);
                return;
              }
              if (prjectionField.key == a_field.field){
                newValues.push(a_field);
                return;
              }
              isEmpty = false;
              let field = fcf.clone(a_field);

              field.from = "___fcftranslate___" + prjectionField.alias;
              translateQuery.values.push(field);
            });
            if (isEmpty){
              translateQuery = undefined;
            } else {
              translateQuery.where = [ {logic: "and", type: "=", args: [{field: "___fcftranslate___language"}, {value: a_query.language}]}];
              taskInfo.query.values = newValues;
            }
          }


          // Проверка полей запроса
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            if (a_info.part == "where" || a_info.part == "fields") {
              var projection =  taskInfo.getProjection(a_info.field.from);
              if (!projection)
                throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_FIELD", {projection: a_info.field.from, field: a_info.field.field});
              if (!projection.fieldsMap[a_info.field.field])
                throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_FIELD_IN_QUERY", {projection: a_info.field.from, field: a_info.field.field});
            }

            if (a_info.part == "fields"){
              var fieldInfo = projection.fieldsMap[a_info.field.field];

              // И обрабатываем поля emptyAsNull
              if (fieldInfo.emptyAsNull && fcf.empty(a_info.field.value))
                a_info.field.value = null

              var filter = fcf.getFilter(fieldInfo.type);
              var errors = [];
              filter.validate({data: a_info.field.value, field: fieldInfo}, errors);
              if (!fcf.empty(errors))
                throw fcf.first(errors);
            }
          });

          // Проверка функции запроса блока WHERE
          Tools.eachFunctions(taskInfo, taskInfo.query, (a_info) => {
            if (a_info.part != "where")
              return;
            var funcHandler = NFunction.getFunction(a_info.function.function);
            if (!funcHandler)
              throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_FUNCTION_IN_QUERY", {function: a_info.function.function });
          });
        });


        // Обработка полей запроса
        taskInfo.actions.then((a_res) => {
          let actions = fcf.actions();
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            var fieldInfo = taskInfo.getFieldInfo(a_info.field);
            var filter = fcf.getFilter(fieldInfo.type);
            if (a_info.part == "fields") {
              let result = filter.processUpdateField(taskInfo, a_info);
              if (result instanceof Promise || result instanceof fcf.Actions)
                actions.then(()=>{ return result; });
            } else if (a_info.part == "where") {
              let result = filter.processWhereField(taskInfo, a_info);
              if (result instanceof Promise || result instanceof fcf.Actions)
                actions.then(()=>{ return result; });
            }
          });
          return actions;
        });

        // Обработка функций в запросе
        taskInfo.actions.then((a_res, a_act) => {
          Tools.eachFunctions(taskInfo, taskInfo.query, (a_info) => {
            var func = NFunction.getFunction(a_info.function.function);
            func.processFunction(taskInfo, a_info);
          });
          a_act.complete();
        });

        // Меняем на реальные имена таблиц
        taskInfo.actions.then((a_res, a_act) => {
          Tools.eachProjection(taskInfo, (a_info) => {
            if (a_info.block.processed)
              return;
            a_info.block[a_info.key] = taskInfo.getProjection(a_info.projection).table;
          });
          a_act.complete();
        });

        // Выполняем сам запрос
        taskInfo.actions.then((a_res, a_act) => {
          let connection = taskInfo.getConnection();
          if (fcf.empty(taskInfo.query.values)){
            let stub = [];
            a_results.push(stub);
            a_act.complete(stub);
            return;
          }

          connection.query(taskInfo.query, taskInfo.options, function(a_error, a_rows){
            if (a_error){
              a_act.error(a_error);
              return;
            }
            resultIndex = a_results.length;
            a_results.push(a_rows);
            a_act.complete(a_rows);
          });
        });

        taskInfo.actions.then(() => {
          if (!translateQuery)
            return;

          let projection =  taskInfo.projection;
          let query = {
            type:    "select",
            from:    projection.alias,
            fields:  [],
            where:   originWhere,
          };

          query.fields.push({function: "title", as: "@title"});
          let options = fcf.append({}, taskInfo.options, {query: query, results: fcf.append([], a_results)});
          return self.query(options)
          .then((a_res)=>{
            return [fcf.last(a_res)];
          });
        })

        taskInfo.actions.then(function(a_res){
          return fcf.actions().then(()=>{
            //Выполняем запрос на вставку записей переводов
            if (translateQuery && !fcf.empty(a_res[0])) {
              let blockArgs = [];
              fcf.each(a_res[0], (a_key, a_record)=>{
                blockArgs.push({logic: "or", type: "=", args: [{field: taskInfo.projection.key}, {value: a_record["@key"]} ]});
              })
              translateQuery.where.push({logic: "and", type: "block", args: blockArgs});
              let options = fcf.append({}, taskInfo.options, {query: translateQuery, ignoreTranslate: true});
              return self.query(options)
              .then(async ()=>{
                let tsq = {
                  type: "select",
                  fields:[],
                  from: translateQuery.from,
                  where: translateQuery.where,
                };
                tsq.fields.push({field: taskInfo.projection.key});
                let translationRecords = await self.query(fcf.append({}, taskInfo.options, {query: tsq, ignoreTranslate: true}))
                let diff            = [];
                let map             = {};
                let mapTranslations = {};
                for(let i = 0; i < a_res[0].length; ++i)
                  map[a_res[0][i]["@key"]] = i;

                for(let i = 0; i < translationRecords[0].length; ++i)
                  mapTranslations[translationRecords[0][i][taskInfo.projection.key]] = i;
                for (let key in map) {
                  if (!(key in mapTranslations)) {
                    let record = {};
                    if (a_res[0][map[key]]) {
                      record["@key"] = a_res[0][map[key]]["@key"];
                    }
                    for(let i = 0; i < translateQuery.values.length; ++i) {
                      record[translateQuery.values[i].field] = translateQuery.values[i].value;
                      if (translateQuery.values[i].field == taskInfo.projection.key)
                        record["@key"] = translateQuery.values[i].value;
                    }
                    diff.push(record);
                  }
                }

                if (fcf.empty(diff))
                  return;

                let insertQueryOptions = fcf.append({}, a_options, {
                  query: [],
                  ignoreTranslate: true,
                });
                for(let diffIndex = 0; diffIndex < diff.length; ++diffIndex) {
                  let query = {
                    type:   "insert",
                    from:   translateQuery.from,
                    values: [],
                  };
                  for(let fieldIndex = 0; fieldIndex < taskInfo.projection.fields.length; ++fieldIndex) {
                    if (!taskInfo.projection.fields[fieldIndex].translate)
                      continue;
                    query.values.push({field: taskInfo.projection.fields[fieldIndex].alias,
                                 value: diff[diffIndex][taskInfo.projection.fields[fieldIndex].alias]});
                  }
                  if(!fcf.empty(query.values)){
                    query.values.push({
                      field: "___fcftranslate___language",
                      value: a_query.language,
                    });
                    query.values.push({
                      field: taskInfo.projection.key,
                      value: diff[diffIndex]["@key"],
                    });
                    insertQueryOptions.query.push(query);
                  }
                }

                if (fcf.empty(insertQueryOptions.query))
                  return;

                insertQueryOptions = fcf.append({}, taskInfo.options, insertQueryOptions);
                return self.query(insertQueryOptions);
              })
            }
          })
          .then(()=>{
            a_results[resultIndex] = [];
            taskInfo.result = a_results[resultIndex];
          })
        })

        taskInfo.actions.then(() => {
          return taskInfo.postActions.startup();
        });

        // Send 'fsql_update_after:[PROJECTION]' message
        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send(`fsql_update_after:${taskInfo.originQuery.from}`, { query: taskInfo.originQuery, projection: taskInfo.originQuery.from, options: taskInfo.options, result: taskInfo.result });
        });

        // Send 'fsql_update_after' message
        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send("fsql_update_after", { query: taskInfo.originQuery, projection: taskInfo.originQuery.from, options: taskInfo.options, result: taskInfo.result });
        });

        taskInfo.actions.then(() => {
          a_actQuery.complete();
        });

        taskInfo.actions.catch(function(a_error){
          a_actQuery.error(a_error);
        })

      }

      this._select = function(a_query, a_options, a_results, a_actQuery){
        var result = undefined;
        var taskInfo = this._createTaskInfo(a_query, a_results, a_options, a_actQuery);
        let onlyAggregate = false;

        // Send 'fsql_select_before' message
        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send("fsql_select_before", { query: taskInfo.query, options: taskInfo.options });
        });

        taskInfo.actions.then(()=>{
          // Минимальная проверка целостности запроса
          if (fcf.empty(taskInfo.projection))
            throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_QUERY", [taskInfo.query.from]);
          let projection =  taskInfo.getProjection(taskInfo.query.from);
          if (!projection)
            throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_QUERY", [taskInfo.query.from]);

          // Проставляем значения подстановки из предыдущих запросов
          Tools.eachReplacement(taskInfo, taskInfo.query, (a_info) => {
            if (! (a_info.field.result in a_results)){
              return;
            }
            if (! (a_info.field.record in a_results[a_info.field.result])){
              return;
            }
            if (! (a_info.field.item in a_results[a_info.field.result][a_info.field.record])){
              return;
            }
            a_info.field.value = a_results[a_info.field.result][a_info.field.record][a_info.field.item];
          });
          Tools.eachReplacement(taskInfo, taskInfo.originQuery, (a_info) => {
            if (! (a_info.field.result in a_results)){
              return;
            }
            if (! (a_info.field.record in a_results[a_info.field.result])){
              return;
            }
            if (! (a_info.field.item in a_results[a_info.field.result][a_info.field.record])){
              return;
            }
            a_info.field.value = a_results[a_info.field.result][a_info.field.record][a_info.field.item];
          });

          // Добавляем @@key для группировок
          fcf.each(taskInfo.query.fields, (a_key, a_field)=>{
            if (!a_field.function){
              onlyAggregate = false;
              return false;
            }
            let funcHandler = NFunction.getFunction(a_field.function);
            if (!funcHandler)
              return;
            if (funcHandler.aggregate){
              onlyAggregate = true;
            } else {
              onlyAggregate = false;
              return false;
            }
          })

          if (!onlyAggregate)
            taskInfo.query.fields.push({field: projection.key, as: "@key"});


          // Подготовка запроса проставляем from и as для полей и устанавливаем режимы
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            if (a_info.part == "where" || a_info.part == "fields") {
              if (!a_info.field.from)
                a_info.field.from = taskInfo.query.from;
            }
            if (a_info.part == "fields") {
              if (!a_info.field.as)
                a_info.field.recommendedAs = a_info.field.field;
            }
          });

          // Добавление order по умолчанию
          if (fcf.empty(taskInfo.query.order) && !fcf.empty(taskInfo.projection.order)){
            taskInfo.query.order = [];
            fcf.each(taskInfo.projection.order, (a_key, a_value) => {
              if (fcf.empty(a_value.field))
                return;

              let field = taskInfo.projection.fieldsMap[a_value.field].field;
              let table = taskInfo.projection.table;
              if (taskInfo.projection.fieldsMap[a_value.field].realjoin && taskInfo.projection.fieldsMap[a_value.field].realjoin.from){
                let from = taskInfo.projection.fieldsMap[a_value.field].realjoin.from;
                from = taskInfo.projection.join[fcf.find(taskInfo.projection.join, (k,v)=>{ return v.as == from;})].from;
                table = taskInfo.projections[from].table;
              }
              taskInfo.query.order.push({
                field: field,
                from:  table,
                order: a_value.order === "desc" ? a_value.order : "asc",
              });

            })
          }

          // Добавление join-ов
          if (!Array.isArray(taskInfo.query.join))
            taskInfo.query.join = [];

          // Join-ы с запроса
          fcf.each(taskInfo.query.join, (a_key, a_join) => {
            if (a_join.as)
              taskInfo.aliases[a_join.as] = a_join.from;
          });

          // Join-ы с проекций
          fcf.each(taskInfo.projection.join, (a_key, a_join) => {
            let joinOn = fcf.append(true, [], self.getWhereBlock(a_join.on));
            Tools.eachFieldBlockWhere(taskInfo, joinOn, (a_info) => {
              if (!a_info.field.from)
                a_info.field.from = taskInfo.query.from;
            });

            taskInfo.query.join.push({
              from: a_join.from,
              as:   a_join.as,
              on:   joinOn,
            });

            if (a_join.as)
              taskInfo.aliases[a_join.as] = a_join.from;
          });

          // Подготовка запроса проставляем режимы полей
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            if (a_info.part == "fields") {
              let fieldInfo = taskInfo.getFieldInfo(a_info.field);
              if (!a_info.field.mode && !taskInfo.options.mode && fieldInfo.selectionMode){
                a_info.field.mode = fieldInfo.selectionMode;
              } else if (!a_info.field.mode) {
                a_info.field.mode = taskInfo.options.mode;
              }
            }
          });

          // Проставляем выборку по языку
          if (a_query.language && fcf.application.getSystemVariable("fcf:defaultLanguage") != a_query.language && fcf.application.getSystemVariable("fcf:languages")[a_query.language]){
            let joinTranslateMap = {};
            Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
              let from = a_info.field.from ? a_info.field.from : taskInfo.query.from;
              let projectionName = taskInfo.aliases[from] ? taskInfo.aliases[from] : from;
              let joinTranslate = "___fcftranslate___" + projectionName;
              let fieldProj = taskInfo.getProjection(projectionName);

              if (!fieldProj.fieldsMap[a_info.field.field].translate)
                return;

              if (!joinTranslateMap[joinTranslate]){
                joinTranslateMap[joinTranslate] = true;
                taskInfo.query.join.push({
                  join: "left",
                  from: joinTranslate,
                  on:   [{logic: "and", type: "=", args: [{field: fieldProj.key, from: from}, {field: fieldProj.key, from: joinTranslate}]},
                         {logic: "and", type: "=", args: [{field: "___fcftranslate___language", from: joinTranslate}, {value: a_query.language}]}
                         ]
                })
              }

              if (a_query.defaultLanguage){
                let fieldName = a_info.field.field;
                let fieldFrom = a_info.field.from;
                delete a_info.field.field;
                a_info.field.function = "coalesce";
                a_info.field.args = [
                  {field: fieldName, from: joinTranslate},
                  {field: fieldName, from: fieldFrom},
                ];
                if (fcf.empty(a_info.field.as) && !fcf.empty(a_info.field.recommendedAs))
                  a_info.field.as = a_info.field.recommendedAs;
              } else {
                a_info.field.from = joinTranslate;
              }


            });
          }

          // Добавление limit по умолчанию
          if (!a_options.disableLimit) {
            if (!fcf.empty(taskInfo.projection.limit)){
              if (typeof taskInfo.projection.limit === "number") {
                if (taskInfo.projection.limit !== 0 || taskInfo.projection.limit !== -1) {
                  if (taskInfo.query.limit <= 0)
                    taskInfo.query.limit = undefined;
                  taskInfo.query.limit = fcf.min(taskInfo.query.limit, taskInfo.projection.limit);
                  if (taskInfo.query.limit <= 0)
                    taskInfo.query.limit = undefined;
                }
              } else if (typeof taskInfo.projection.limit === "object") {
                let roles = {};
                fcf.each(taskInfo.options.roles, (k, role)=>{ roles[role]=role;});
                fcf.each(fcf.getContext().session.user.roles, (role)=>{ roles[role]=role;});
                let maxLimit = undefined;
                fcf.each(taskInfo.projection.limit, (a_role, a_limit)=>{
                  if (a_role in roles || a_role == "*"){
                    if (maxLimit <= 0)
                      return;
                    if (a_limit <= 0)
                      maxLimit = a_limit;
                    else
                      maxLimit = fcf.max(maxLimit, a_limit);
                  }
                })
                if (maxLimit <= 0)
                  maxLimit = undefined;
                taskInfo.query.limit = fcf.min(taskInfo.query.limit, maxLimit);
                if (taskInfo.query.limit <= 0)
                  taskInfo.query.limit = undefined;
              }
            }
          }

          // Проставляет offset если задан лимит
          if ((taskInfo.query.offset === undefined || taskInfo.query.offset === null) &&
              (taskInfo.query.limit !== undefined && taskInfo.query.limit !== null)){
            taskInfo.query.offset = 0;
          }

          // Проверка запроса
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            if (a_info.part == "where" || a_info.part == "fields") {
              var projection =  taskInfo.getProjection(a_info.field.from);
              if (!projection)
                throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_PROJECTION_IN_FIELD", {projection: a_info.field.from, field: a_info.field.field});
              if (!projection.fieldsMap[a_info.field.field])
                throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_FIELD_IN_QUERY", {projection: a_info.field.from, field: a_info.field.field});
            }
          });

          Tools.eachFunctions(taskInfo, taskInfo.query, (a_info) => {
              var funcHandler = NFunction.getFunction(a_info.function.function);
              if (!funcHandler)
                throw new fcf.Exception("ERROR_NFSQL_UNKNOWN_FUNCTION_IN_QUERY", {function: a_info.function.function });
          });

        });

        // Send 'fsql_select_check_projection' message
        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send("fsql_select_check_projection", { query: taskInfo.originQuery, projection: taskInfo.originQuery.from, options: taskInfo.options});
        });
        taskInfo.actions.each(taskInfo.originQuery.join, (a_key, a_join)=>{
          return fcf.application.getEventChannel().send("fsql_select_check_projection", { query: taskInfo.originQuery, projection: a_join.from, options: taskInfo.options});
        })

        // Обработка полей запроса
        taskInfo.actions.then((a_res) => {
          let actions = fcf.actions();
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            var fieldInfo = taskInfo.getFieldInfo(a_info.field);
            var filter = fcf.getFilter(fieldInfo.type);
            if (a_info.part == "fields") {
              let result = filter.processOutputField(taskInfo, a_info);
              if (result instanceof Promise || result instanceof fcf.Actions)
                actions.then(()=>{ return result; });
            } else if (a_info.part == "where") {
              let result = filter.processWhereField(taskInfo, a_info);
              if (result instanceof Promise || result instanceof fcf.Actions)
                actions.then(()=>{ return result; });
            }
          });
          return actions;
        });

        // Обработка функций в запросе
        taskInfo.actions.then((a_res, a_act) => {
          Tools.eachFunctions(taskInfo, taskInfo.query, (a_info) => {
            var func = NFunction.getFunction(a_info.function.function);
            func.processFunction(taskInfo, a_info);
          });
          a_act.complete();
        });

        // Меняем на реальные имена таблиц
        taskInfo.actions.then((a_res, a_act) => {
          Tools.eachProjection(taskInfo, (a_info) => {
            if (a_info.block.processed)
              return;
            a_info.block[a_info.key] = taskInfo.getProjection(a_info.projection).table;
          });
          a_act.complete();
        });

        // Проставляем AS для полей и устанавливаем группировку
        taskInfo.actions.then((a_res, a_act) => {
          Tools.eachField(taskInfo, taskInfo.query, (a_info) => {
            if (!a_info.field.as)
              taskInfo.setAutoAs(a_info.field, a_info.field.from);
            if (!onlyAggregate) {
              if (a_info.part == "fields") {
                if (!taskInfo.query.group)
                  taskInfo.query.group = [];
                if (a_info.field.field != "*" && a_info.object == taskInfo.query.fields)
                  taskInfo.query.group.push(a_info.field.as);
              }
            }
          });
          a_act.complete();
        });

        // Проставляем AS для функций и устанавливаем группировку
        taskInfo.actions.then((a_res, a_act) => {
          Tools.eachFunctions(taskInfo, taskInfo.query, (a_info) => {
            if (!a_info.function.as)
              taskInfo.setAs(a_info.function, a_info.function.recommendedAs, a_info.function.from);
            if (!onlyAggregate) {
              if (a_info.part == "fields") {
                if (!taskInfo.query.group)
                  taskInfo.query.group = [];
                taskInfo.query.group.push(a_info.function.as);
              }
            }
          });
          a_act.complete();
        });

        // Выполняем сам запрос
        taskInfo.actions.then((a_res, a_act) => {
          let connection = taskInfo.getConnection();
          connection.query(taskInfo.query, taskInfo.options, function(a_error, a_rows){
            if (a_error){
              a_act.error(a_error);
              return;
            }
            taskInfo.result = a_rows;
            a_results.push(a_rows);
            a_act.complete(a_rows);
          });
        });

        taskInfo.actions.then((a_res) => {
          let actions = fcf.actions();
          Tools.eachField(taskInfo, taskInfo.originQuery, (a_info) => {
            var fieldInfo = taskInfo.getFieldInfo(a_info.field);
            var filter = fcf.getFilter(fieldInfo.type);
            if (a_info.part == "fields"){
              let result = filter.postProcessOutputField(taskInfo, a_info);
              if (result instanceof Promise || result instanceof fcf.Actions)
                actions.then(()=>{ return result; });
            }
          });
          return actions;
        });

        // Send 'fsql_select_after' message
        taskInfo.actions.then(()=>{
          return fcf.application.getEventChannel().send("fsql_select_after", { query: taskInfo.originQuery, projection: taskInfo.originQuery.from, result: taskInfo.result, options: taskInfo.options });
        });

        taskInfo.actions.then(() => {
          return taskInfo.postActions.startup();
        });

        taskInfo.actions.then(() => {
          a_actQuery.complete();
        });

        taskInfo.actions.catch((a_error)=>{
          a_actQuery.error(a_error);
        })


        return taskInfo.actions;
      }

      this.rawQuery = function(a_query, a_args, a_cb) {
        self._connections.default.rawQuery(a_query, a_args, a_cb);
      }

      this.destroy = function() {
        for(var k in self._connections) {
          var connection = self._connections[k];
          connection.destroy();
        }
      }

      this.getWhereBlock = function(a_whereBlock) {
        return typeof a_whereBlock !== "string" ? a_whereBlock
                                         : this._parser.parseWhere(a_whereBlock, []);
      }

      this._foreachFields = function(a_query, a_cb){

      }

    };

    return NDetails.ServerStorage;
  }
});
