Skip to content
Snippets Groups Projects
process.js 69.94 KiB
import("system.db");
import("system.vars");
import("system.translate");
import("system.logging");
import("system.datetime");
import("system.fileIO");
import("system.swing");
import("system.util");
import("system.text");
import("ImporterCustomMappingFunctions_lib");
import("ImporterMappingFunctions_lib");

/*
┌─────────────────────────────────────────────────────────────┐
│ importer module constructor function                        │
│ DO NOT TOUCH - use lib_importerCustomMappingFunctions       │
└─────────────────────────────────────────────────────────────┘


 Frequently asked questions:
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Q:  Why does the importer have a own versionsystem, if there exists git?
A:  Because its a standalone module and there is no modularisation-system right now. 
    So you may want to update the IMPORTER-module and have to keep an eye on the verison.

Q:  Where can I see the version of the importer?
A:  There is a @version-tag in the documentation of the Importer-function (constructor-function for the object)
 */


/*
* the importer class, main object
* @param {String []} pConfig req ( Name, FunktionsArt, Details )
*
* @version 2.1
* 
*
* @return {void}
*/
function Importer(pConfig)
{
    var batchNum = 0;
    var batchStart = 0;
    var batchList;
    var mappingtimer = [];
    var exectimer = {
        insertData: 0,
        updateData: 0,
        outbuffer: 0,
        logging: 0,
        getdata: 0,
        updateDecisionTime: 0,
        insertUpdateActions: 0,
        dbDataModifiedTime: 0,
        dbInsertTime: 0,
        dbUpdateTime: 0,
        dataLoopTime: 0,
        mapPerRecordTime: 0,
        outputPerRecordTime: 0
    };
    var startTime = datetime.date();//this will be overwriten when starting the importing-process
    var stopTime;
    var logBuffer = [];
    this.insertArray = [];
    this.updateArray = [];

    this.LogLevels = {
        Minimal: 0,
        Error : 1,
        Warning : 2,
        Info : 3,
        Debug : 4,
        Preview : 5
    };

    this.Cases = {
        Lower: 0,
        Upper: 1,
        Unchanged: 2
    };

    this.Process = "";
    this.Config = pConfig;
    this.BatchSize = 50;
    this.MaxRows = 0;
    this.Preview = false;
    this.Debug = false;
    this.Log = "CONSOLE";
    this.fileInputCharset = "UTF8";
    this.LogLevel = this.LogLevels.Warning;
    this.enableLogBuffer = false;
    this.InputRecord = new Array();
    this.OutputRecord = new Object();
    this.ImportUser = "IMPORTER";
    this.FuncBuffer = new Object();
    this.CompleteUpdate = true;
    this.ErrorLog = "";
    this.DataType = null;
    this.KeyColumn = new Object();  // contains key info as in KeyColumn["tbl.col"] = "I" | "U" | "I+U"
    this.UseAOType = true; //default true, so that AOType will be uses (for older systems)
    this.UseUUID = true; //default false, so that db.getNewID() instead of util.getNewUUID() would be uses
    this.TableCase = this.Cases.Upper; //default is uppercase
    this.ColumnCase = this.Cases.Upper; //default is uppercase
    this.skipEmptyValue = true;

    this.AttributeCache = undefined;
    this.useAttributeCache = false;
    this.attributeCacheLoadedSuccessfully = false;
    this.loadAttributeCache = function(){
        this.writeLog(this.LogLevels.Info, "[LoadAttributeCache]loading Attibute cache");
        try
        {
            this.AttributeCache = {};
            let TMP = 0;
            var cD = {
                 attrNameL1: TMP
                ,attrNameL2: ++TMP
                ,attrNameL3: ++TMP
                ,attrIdL1: ++TMP
                ,attrIdL2: ++TMP
                ,attrIdL3: ++TMP
                ,attrObjectL1: ++TMP
                ,attrObjectL2: ++TMP
                ,attrCompL1: ++TMP
                ,attrCompL2: ++TMP
                ,attrSqlIdFieldL1: ++TMP
                ,attrSqlDisplayFieldL1: ++TMP
                ,attrSqlFromFieldL1: ++TMP
                ,attrSqlWhereFieldL1: ++TMP
                ,attrSqlIdFieldL2: ++TMP
                ,attrSqlDisplayFieldL2: ++TMP
                ,attrSqlFromFieldL2: ++TMP
                ,attrSqlWhereFieldL2: ++TMP
            };

            var sqlStr = "select "
            + this.getColumnCase( "A.ATTRNAME, B.ATTRNAME, C.ATTRNAME "
                + ",A.ATTRID, B.ATTRID, C.ATTRID "
                + ",AO_A.ATTROBJECT, AO_B.ATTROBJECT "
                + ",A.ATTRCOMPONENT, B.ATTRCOMPONENT "
                + ",A.SQLIDCOLUMN, A.SQLSHOWCOLUMN, A.SQLFROMDEF, A.SQLWHERE "
                + ",B.SQLIDCOLUMN, B.SQLSHOWCOLUMN, B.SQLFROMDEF, B.SQLWHERE "
                + "")
            + " from " + this.getTableCase("ATTR A")
            + " left join " + this.getTableCase("ATTR B") + " on " + this.getColumnCase("A.ATTRID") + " = " + this.getColumnCase("B.ATTR_ID")
            + " left join " + this.getTableCase("ATTR C") + " on " + this.getColumnCase("B.ATTRID") + " = " + this.getColumnCase("C.ATTR_ID")
            + " left join " + this.getTableCase("ATTROBJECT AO_A") + " on " + this.getColumnCase("AO_A.ATTR_ID") + " = " + this.getColumnCase("A.ATTRID")
            + " left join " + this.getTableCase("ATTROBJECT AO_B") + " on " + this.getColumnCase("AO_B.ATTR_ID") + " = " + this.getColumnCase("B.ATTRID")
            + " where " + this.getColumnCase("A.ATTR_ID") + " is null "//if it has an attr_id it's level 2 an with that ATTR B (or ATTR C)
            + "";
            this.writeLog(this.LogLevels.Info, "[LoadAttributeCache]executed select is: " + sqlStr);
            var data = db.table(sqlStr, this.Config.AliasTo);
            var attributeTree = {};
            for(var i = 0, j = data.length; i < j; i++)
            {
                let row = data[i];
                if (attributeTree[row[cD.attrNameL1]] == undefined)
                    attributeTree[row[cD.attrNameL1]] = {
                         id: row[cD.attrIdL1]
                        ,component: row[cD.attrCompL1]
                        ,usedInFrames: {}
                        ,children: {}
                    };

                if (row[cD.attrObjectL1] != "")
                    attributeTree[row[cD.attrNameL1]].usedInFrames[row[cD.attrObjectL1]] = true;
                else
                    this.writeLog(this.LogLevels.Warning, '[loadAttributeCache]attribute "' + row[cD.attrNameL1] + '" has no ATTROBJECT');
                //selectCombo, init for hybrid-cache (loaded once, when attribute is really needed):
                if (row[cD.attrCompL1] == "7"
                 && attributeTree[row[cD.attrNameL1]].loadedSelectCombo == undefined)
                {
                    attributeTree[row[cD.attrNameL1]].loadedSelectCombo = false;
                    attributeTree[row[cD.attrNameL1]].selectComboSql = "select " + row[cD.attrSqlIdFieldL1]
                        + " ," + row[cD.attrSqlDisplayFieldL1]
                        + " from " + row[cD.attrSqlFromFieldL1]
                        + (row[cD.attrSqlWhereFieldL1] != "" ? " where " : "") + row[cD.attrSqlWhereFieldL1];
                }


                if (row[cD.attrNameL2] != "")
                {
                    if (attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]] == undefined)
                        attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]] = {
                             id: row[cD.attrIdL2]
                            ,component: row[cD.attrCompL2]
                            ,usedInFrames: {}
                            ,children: {}
                        };

                    if (row[cD.attrObjectL2] != "")
                        attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].usedInFrames[row[cD.attrObjectL2]] = true;
                    else
                        this.writeLog(this.LogLevels.Warning, '[loadAttributeCache]attribute "' + row[cD.attrNameL1] + "|" + row[cD.attrNameL2] + '" has no ATTROBJECT');

                    //selectCombo, init for hybrid-cache:
                    if (row[cD.attrCompL2] == "7"
                        && attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].loadedSelectCombo == undefined)
                    {
                        attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].loadedSelectCombo = false;
                        attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].selectComboSql = "select " + row[cD.attrSqlIdFieldL2]
                        + " ," + row[cD.attrSqlDisplayFieldL2]
                        + " from " + row[cD.attrSqlFromFieldL2]
                        + (row[cD.attrSqlWhereFieldL2] != "" ? " where " : "") + row[cD.attrSqlWhereFieldL2];
                    }

                    //this nomally happens only if component is combo
                    if (row[cD.attrNameL3] != "")
                    {
                        if (attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].children[row[cD.attrNameL3]] == undefined)
                            attributeTree[row[cD.attrNameL1]].children[row[cD.attrNameL2]].children[row[cD.attrNameL3]] = {
                                 id: row[cD.attrIdL3]
                            };
                    }
                }
//                        //jetzt muss geprüft werden ob sich die ATTRIDs unterscheiden, wenn ja bedeutet dass, das ein Attributname mehrfach vergeben wurde
//                        //dann darf keiner der Daten in den Cache geladen werden, dass würde nur zu fehlerhaften daten führen
//                        var existingAttrid = this.AttributeCache[ attrname ].ATTRID;
//                        if( existingAttrid != attrid )
//                        {
//                            this.AttributeCache[ attrname ] = null; //cache sicherheitshalber erst freigeben
//                            this.AttributeCache[ attrname ] = undefined;
//                            this.writeLog(this.LogLevels.Error, "[LoadAttributeCache] Multiple Attributes were found for attribute: '" + attrname + "'."
//                                + "\r\nNo Attribute with attrname '" + attrname + "' will be loaded into the cache. ");
//                        }
            }
            this.AttributeCache = attributeTree;
            this.attributeCacheLoadedSuccessfully = true;
        }
        catch(ex)
        {
            this.attributeCacheLoadedSuccessfully = false;
            this.writeLog(this.LogLevels.Error, "[LoadAttributeCache]Error while loading Attributes:\r\n" + logging.toLogString(ex) );
        }
        if( this.attributeCacheLoadedSuccessfully )
            this.writeLog(this.LogLevels.Info, "[LoadAttributeCache]Loaded Attibute cache successfully");
        else
            this.writeLog(this.LogLevels.Info, "[LoadAttributeCache]Failed loading Attibute cache");
    }

    this.recordCounts = {//is initialized with numbers in the this.process-function
         total: null
        ,skip: null
        ,update: null
        ,insert: null
        ,unchanged: null
    };
    Object.defineProperty(this.recordCounts, 'unchanged', {
        //"this" is here the passed object
         get: function (){ return (this.total - this.insert - this.update - this.skip)}
        ,set: function (){throw new Error("importers recordCounts cannot be set; it is read-only")}
    });

    this.process = function()
    {
        this.mappingLength = this.Config.Mapping.length;
        var i;
        var t;
        var pkid;
        var c;
        var cond;
        var s;

        var pImporter = this;

        this.recordCounts.total = 0;
        this.recordCounts.skip = 0;
        this.recordCounts.insert = 0;
        this.recordCounts.update = 0;
        
        // Infoobject for Callbackfunction
        var recordStack = {
            sourceDataSet: [],
            insertUpdate: "",
            insertData: {
                successful: true, 
                array: []
            },
            updateData: {
                successful: true, 
                array: []
            },
            exception:  [],
            pId: ""
        };

        var daten;
        var skip;
        var headercount = 0;
        var batchlist = [];
        var idcolumn;
        var importtype = "S";        // F,S or M for "file", "single" or "multi" batch, default to single batch
        batchStart = 0;              // current batch start index
        var logstring = "";          // contains the log file, if not writing to server log
        var requiredTargets = new Object();   // stores the required target columns
        // prepare logging stuff
        if(this.Log != "" && this.Log != undefined && this.Log.split(".")[0] == "$global") vars.set(this.Log, "");
        if(this.Preview == true) this.LogLevel = this.LogLevels.Preview;

        if (this.enableLogBuffer)
            logBuffer = [];
        else
            logBuffer = ["logBuffer is not enabled; you cannot use getLogMessages(...)"];
        startTime = datetime.date();
        s = datetime.toDate(startTime, translate.text("yyyy-MM-dd HH:mm:ss"), "UTC");
        this.writeLog(this.LogLevels.Minimal, "Started at " + s + " (UTC)");
        logging.log("[IMPORT] Start at " + s + " UTC");
        if(this.Preview) this.writeLog(this.LogLevels.Info, "======== import running in preview mode ========");
        if (this.useAttributeCache)
            this.loadAttributeCache();
        this.writeLog(this.LogLevels.Debug, "Building mapping tables.");
        // get all column type from all tables of the target alias
        this.DataType = this.getDataTypes(this.Config.AliasTo);
        // build an object with the data types for each column and
        // check for key fields and collect them in parmObject.KeyRecord
        for(i=0; i < this.mappingLength; i++)
        {
            mappingtimer[i] = 0;  // init mapping timer

            var target = this.Config.Mapping[i][1]["Target"];
            if(target != undefined && target.substr(0,4).toLowerCase() != "var.")  // exclude var storage
            {
                // collect a required target, if we find one (these columns must not be empty)
                requiredTargets[target] = (this.Config.Mapping[i][1]["Required"] == true);
                // collect all key columns in an object array
                // KeyColumn[table ][column] = action1;action2...
                var coldata = target.split(".");

                if(this.Config.Mapping[i][1]["Key"] != undefined)  //2009-05-13  TR  changed, because on insertcommand "insert" or "update" there can be no Action
                {
                    if(this.KeyColumn[ this.getTableCase(coldata[0]) ] == undefined) this.KeyColumn[ this.getTableCase(coldata[0]) ] = new Object();
                    if(this.KeyColumn[ this.getTableCase(coldata[0]) ][ this.getColumnCase(coldata[1]) ] == undefined) this.KeyColumn[ this.getTableCase(coldata[0]) ][ this.getColumnCase(coldata[1]) ] = "key";

                    this.setDefaultAction(this.Config.Mapping[i][1]);

                    this.KeyColumn[ this.getTableCase(coldata[0]) ][ this.getColumnCase(coldata[1]) ] = this.KeyColumn[ this.getTableCase(coldata[0]) ][ this.getColumnCase(coldata[1]) ] + ";" + this.Config.Mapping[i][1]["Action"];
                }
            }
        }

        batchNum = 0;
        headercount = this.Config.HeaderLines;
        if(headercount == undefined) headercount = 0;
        this.writeLog(this.LogLevels.Warning, "Beginning data import.");
        // main loop
        while(daten = this.getNextBatch(this.Config), daten != null )
        {
            var dataLoopTimeStart = datetime.date();
            this.writeLog(this.LogLevels.Info, "Processing batch #" + batchNum);
            // import each row of the current batch
            for(var bi=0; bi < daten.length; bi++)
            {
                recordStack.exception ="";
                recordStack.sourceDataSet = daten[bi];
                recordStack.insertData  = {
                    successful: true, 
                    array: []
                }
                recordStack.updateData  = {
                    successful: true, 
                    array: []
                }

                // skip empty data rows
                if(daten[bi].length == 1 && daten[bi][0] == "")
                {
                    if(this.Debug == true) {
                        this.writeLog(this.LogLevels.Debug, "Skipping empty row " + bi);
                    }
                    continue;
                }

                this.recordCounts.total++;
                skip = false;

                // skip header rows (usually file import only)
                if(bi < headercount)
                {
                    if(this.Preview == true && this.Debug == true)
                    {
                        this.writeLog(this.LogLevels.Debug, "Skipping header row " + bi);
                    }
                    continue;
                }
                if (this.Debug)
                    this.writeLog(this.LogLevels.Debug, "Row " + bi);

                this.InputRecord = daten[bi];       // assign input fields for this record
                this.OutputRecord = new Object();   // clear output fields for this record

                var mapRecordMapTimeStart = datetime.date();
                // call mapping functions
                for(i=0, ml = this.mappingLength; i < ml; i++)
                {
                    try
                    {
                        var fname = this.Config.Mapping[i][0].name;//name is inherited function-property

                        if (this.Debug)
                            this.writeLog(this.LogLevels.Debug, "Calling mapping function " + fname);
                        // call mapping function and time it
                        t = datetime.date();
                        var mapresult = this.Config.Mapping[i][0].call(this, this.Config.Mapping[i][1]);
                        t = datetime.date() - t;
                        mappingtimer[i] += t;
                        // if the mapping did not succeed, log function and ID value
                        if(mapresult == false)
                        {
                            this.writeLog(this.LogLevels.Error, "Mapping function " + fname + " failed for row " + this.recordCounts.total);
                            recordStack.exception = "Fehler Zeile: " + this.recordCounts.total + " - Info:  Mapping function " + fname + " failed for row " + this.recordCounts.total + " Column: " + this.Config.Mapping[i][1]["Target"]; 
                            skip = true;
                        }
                    }
                    catch(ex)
                    {
                        logging.log(ex["rhinoException"] != undefined ? ex["rhinoException"] : ex)
                        this.writeLog(this.LogLevels.Error, "Exception in mapping function [" + fname + "] for input row " + this.recordCounts.total+ " - " + this.Config.Mapping[i][1]["Target"]);                       
                        recordStack.exception = logging.toLogString(ex["rhinoException"] != undefined ? ex["rhinoException"] : ex, true) +  " -  Column: " + this.Config.Mapping[i][1]["Target"];
                        skip = true;

                    }
                }
                exectimer.mapPerRecordTime += datetime.date() - mapRecordMapTimeStart;


                if(skip == false)  // do we continue with this record?
                {
                    // if a debug callback function has been defined, call it
                    if(this.DebugCallback != null && typeof(this.DebugCallback) == "function")
                    {
                        this.DebugCallback(this.OutputRecord);
                    }


                    var tt = datetime.date();

                    var outdata = new Object();

                    for(var tbl in this.OutputRecord)  // loop thru all output tables
                    {
                        for(var col in this.OutputRecord[ tbl ])  // loop thru all output columns
                        {
                            if(tbl.toLowerCase() != "var")   // exclude var storage
                            {
                                for(var action in this.OutputRecord[tbl][col])
                                {
                                    if(outdata[tbl] == undefined) outdata[tbl] = "";
                                    outdata[tbl] = outdata[tbl] + ";" + action;

                                    // check for required data mappings and set skip again, we check this later
                                    skip = (requiredTargets[tbl + "." + col] == true && (this.OutputRecord[tbl][col][action] == undefined || this.OutputRecord[tbl][col][action] == ""));
                                }
                            }
                        }
                    }

                    tt = datetime.date() - tt;
                    exectimer["outbuffer"] += tt;

                    var outputRecordTimeStart = datetime.date();
                    // loop thru all distinct tables, maybe we have more than one record to write
                    for(t in this.OutputRecord)
                    {
                        var spalten = [];
                        var typen = [];
                        var werte = [];

                        // we do insert-for-new and update-for-existing
                        if(skip == false && this.Config.ImportCommand == "insert+update")
                        {
                            var insertUpdateStart = datetime.date();
                            pkid = "0";  // assume we do not find an existing record

                            // if no key values have been specified, there is no need to check
                            // so we do an insert
                            if(this.KeyColumn[t] != undefined )
                            {
                                // check for existing data row based on key values
                                cond = this.generateKeyCondition(t);
                                var sql = "select count(*) from " + t + " where " + cond;
                                this.writeLog(this.LogLevels.Debug, "Insert/Update decision for table " + t + " based on: " + cond);
                                pkid = db.cell(sql, this.Config.AliasTo);
                            }
                            exectimer["updateDecisionTime"] += (datetime.date() - insertUpdateStart);

                            if(t.toLowerCase() != "var")
                            {
                                if(this.DataType[t] == undefined) this.writeLog(this.LogLevels.Error, "Table " + t + " not exist in database");
                                else
                                {
                                    for(c in this.OutputRecord[t])
                                    {
                                        if(this.OutputRecord[t][c]["I+U"] != undefined && (this.OutputRecord[t][c]["I+U"] != "" || !this.skipEmptyValue) && this.OutputRecord[t][c]["I+U"] != null)
                                        {
                                            if(this.DataType[t][c] != undefined)
                                            {
                                                spalten.push(c);
                                                typen.push(this.DataType[t][c]);
                                                werte.push(this.OutputRecord[t][c]["I+U"]);
                                            }
                                            else
                                                this.writeLog(this.LogLevels.Error, "Column " + c + " not exist in table " + t);
                                        }
                                    }
                                }
                            }
                            if(pkid != "0")  // exists
                            {
                                if(t.toLowerCase() != "var")
                                {
                                    if(this.DataType[t] == undefined) this.writeLog(this.LogLevels.Error, "Table " + t + " not exist in database");
                                    else
                                    {
                                        for(c in this.OutputRecord[t])
                                        {
                                            if(this.OutputRecord[t][c]["U"] != undefined && (this.OutputRecord[t][c]["U"] != "" || !this.skipEmptyValue) && this.OutputRecord[t][c]["U"] != null
                                                && this.OutputRecord[t][c]["I+U"] == undefined)
                                                {
                                                if(this.DataType[t][c] != undefined)
                                                {
                                                    spalten.push(c);
                                                    typen.push(this.DataType[t][c]);
                                                    werte.push(this.OutputRecord[t][c]["U"]);
                                                }
                                                else
                                                    this.writeLog(this.LogLevels.Error, "Column " + c + " not exist in table " + t);
                                            }
                                        }
                                    }
                                }

                                if(!this.updateData(t, spalten, typen, werte, cond, this.Config.AliasTo)) skip = true;
                            }
                            else
                            {
                                if(t.toLowerCase() != "var")
                                {
                                    if(this.DataType[t] == undefined) this.writeLog(this.LogLevels.Error, "Table " + t + " not exist in database");
                                    else
                                    {
                                        for(c in this.OutputRecord[t])
                                        {
                                            if(this.OutputRecord[t][c]["I"] != undefined && this.OutputRecord[t][c]["I"] != "" && this.OutputRecord[t][c]["I"] != null
                                                && this.OutputRecord[t][c]["I+U"] == undefined)
                                                {
                                                if(this.DataType[t][c] != undefined)
                                                {
                                                    spalten.push(c);
                                                    typen.push(this.DataType[t][c]);
                                                    werte.push(this.OutputRecord[t][c]["I"]);
                                                }
                                                else
                                                    this.writeLog(this.LogLevels.Error, "Column " + c + " not exist in table " + t);
                                            }
                                        }
                                    }
                                }

                                if(!this.insertData(t, spalten, typen, werte, this.Config.AliasTo)) skip = true;
                            }
                            exectimer["insertUpdateActions"] += (datetime.date() - insertUpdateStart);
                        }

                        // we do an insert
                        if(skip == false && this.Config.ImportCommand == "insert")
                        {
                            if(t.toLowerCase() != "var")
                            {
                                if(this.DataType[t] == undefined) this.writeLog(this.LogLevels.Error, "Table " + t + " not exist in database");
                                else
                                {
                                    for(c in this.OutputRecord[t])
                                    {
                                        if(this.DataType[t][c] != undefined)
                                        {
                                            spalten.push(c);
                                            typen.push(this.DataType[t][c]);
                                            werte.push(this.OutputRecord[t][c]["I"]);
                                        }
                                        else
                                            this.writeLog(this.LogLevels.Error, "Column " + c + " not exist in table " + t);
                                    }
                                }
                            }
                            if(!this.insertData(t, spalten, typen, werte, this.Config.AliasTo)) skip = true;
                        }

                        // we do an update?
                        if(skip == false && this.Config.ImportCommand == "update")
                        {
                            if(t.toLowerCase() != "var")
                            {
                                if(this.DataType[t] == undefined) this.writeLog(this.LogLevels.Error, "Table " + t + " not exist in database");
                                else
                                {
                                    for(c in this.OutputRecord[t])
                                    {
                                        if(this.DataType[t][c] != undefined)
                                        {
                                            spalten.push(c);
                                            typen.push(this.DataType[t][c]);
                                            werte.push(this.OutputRecord[t][c]["U"]);
                                        }
                                        else
                                            this.writeLog(this.LogLevels.Error, "Column " + c + " not exist in table " + t);
                                    }
                                }
                            }

                            // create condition by looking for key fields
                            cond = this.generateKeyCondition(t);
                            if(!this.updateData(t, spalten, typen, werte, cond, this.Config.AliasTo)) skip = true;
                        }
                    }
                    exectimer.outputPerRecordTime += datetime.date() - outputRecordTimeStart;
                }

                // this is *not* the else-branch for the if above, because we may change the value for "skip"
                // even it was false when comparing in the "if" above. so we check this here in a separate
                // if to get all occurrences of skipped import rows.
                if(skip == true) this.recordCounts.skip++;
                //Now insert/update all Columns
                if(!skip)
                {
                    var totalDBexecTime = datetime.date();
                    let insertSuccessfull = true;
                    let updateSuccessfull = true;
                    try
                    {
                        if(this.insertArray.length > 0)
                        {
                            insertSuccessfull = false;
                            var startInsert = datetime.date();
                            db.inserts(this.insertArray, this.Config.AliasTo);
                            exectimer.dbInsertTime += datetime.date() - startInsert;
                            this.recordCounts.insert++;
                            insertSuccessfull = true;
                             // insert
                            recordStack.insertUpdate = "insert";
                            recordStack.insertData  = {
                                successful: true, 
                                array: this.insertArray
                            };
                        }
                        if(this.updateArray.length > 0)
                        {
                            updateSuccessfull = false;
                            var startUpdate = datetime.date();
                            db.updates(this.updateArray, this.Config.AliasTo);
                            exectimer.dbUpdateTime += datetime.date() - startUpdate;
                            this.recordCounts.update++;
                            updateSuccessfull = true;
                            // update
                            recordStack.insertUpdate = "update";
                            recordStack.updateData  = {
                                successful: true, 
                                array: this.updateArray
                            };
                        }
                    }
                    catch(ex)
                    {
                        this.writeLog(this.LogLevels.Error, "Error at " + (insertSuccessfull ? "" : "Insert") + (updateSuccessfull ? "": "Update") + ":" + logging.toLogString(ex));
                        logging.log(ex["rhinoException"] != undefined ? ex["rhinoException"] : ex)
                        this.recordCounts.skip++;
                        recordStack.exception = logging.toLogString(ex["rhinoException"] != undefined ? ex["rhinoException"] : ex, true);
                        
                        if(this.insertArray.length > 0 && !insertSuccessfull) {
                            this.writeLog(this.LogLevels.Info, "Insert array: " + JSON.stringify(this.insertArray, null, " "));
                            recordStack.insertData  = {
                                successful: false, 
                                array: this.updateArray
                            };
                        }
                        if(this.updateArray.length > 0 && !updateSuccessfull) {
                            this.writeLog(this.LogLevels.Info, "Update array: " + JSON.stringify(this.updateArray, null, " "));
                            recordStack.updateData  = {
                                successful: false, 
                                array: this.updateArray
                            };
                        }
                    }

                    exectimer.dbDataModifiedTime += datetime.date() - totalDBexecTime;

                    //Clear Arrays
                    this.insertArray = [];
                    this.updateArray = [];
                }
                // if a progress callback function has been defined, call it
                if(this.ProgressCallback != null && typeof(this.ProgressCallback) == "function")
                {
                    this.ProgressCallback(skip, bi, recordStack, this.Config.AliasFrom, this.Config.Callback.TableFrom, this.Config.Callback.IDColumn, this.Config.Callback.IDColumnIndex);
                }

                if(this.MaxRows > 0 && this.recordCounts.total >= this.MaxRows)
                {
                    break;
                }
            }  // end for (row of current batch)
            if(this.MaxRows > 0 && this.recordCounts.total >= this.MaxRows)
            {
                break;
            }
            exectimer.dataLoopTime += datetime.date() - dataLoopTimeStart;
        }

        this.showCounts();
        stopTime = datetime.date();
        this.showTimings();
        s = datetime.toDate(stopTime, translate.text("yyyy-MM-dd HH:mm:ss"), "UTC");
        this.writeLog(this.LogLevels.Minimal, "End at " + s + " UTC");
        logging.log("[IMPORT] End at " + s + " UTC");

        return {
             totalCount: this.recordCounts.total
            ,skipCount: this.recordCounts.skip
            ,insertCount: this.recordCounts.insert
            ,updateCount: this.recordCounts.update
            ,unchangedCount: this.recordCounts.unchanged
        };
    }
    //	show timing information
    this.showTimings = function(pPrefix)
    {
        if (pPrefix == undefined)
            pPrefix = "";

        var mappingtotal = 0;
        for(var i=0; i < this.Config.Mapping.length; i++)
        {
            mappingtotal += mappingtimer[i];
        }
        if (stopTime == undefined)
            this.writeLog(this.LogLevels.Warning, pPrefix + "Total run time till now: " + ((datetime.date() - startTime) / 1000) + " seconds.");
        else
            this.writeLog(this.LogLevels.Warning, pPrefix + "Total run time: " + ((stopTime - startTime) / 1000) + " seconds.");

        for(var k in exectimer)
        {
            this.writeLog(this.LogLevels.Info, pPrefix + "Total " + k + " time: " + (exectimer[k] == "0" ? "<1" : exectimer[k]) + " ms ");
        }

        this.writeLog(this.LogLevels.Warning, pPrefix + "Total map time: " + (mappingtotal / 1000) + " seconds.");

        for(i=0; i < this.Config.Mapping.length; i++)
        {
            var fname =  this.Config.Mapping[i][0].name;//name is inherited function-property
            this.writeLog(this.LogLevels.Info, pPrefix + "Mapping #" + i + " " + fname + ": " + (mappingtimer[i] == "0" ? "<1" : mappingtimer[i]) + " ms ");
        }
    }

    this.showCounts = function(pPrefix)
    {
        if (pPrefix == undefined)
            pPrefix = "";

        this.writeLog(this.LogLevels.Minimal, pPrefix + "Total: " + this.recordCounts.total
                    + ", Skipped: " + this.recordCounts.skip
                    + ", Inserted: " + this.recordCounts.insert
                    + ", Updated: " + this.recordCounts.update
                    + ", Unchanged: " +  this.recordCounts.unchanged );
    }

    //		this function yields the next batch of data to import.
    this.getNextBatch = function(pConfig){
        var tt = datetime.date();  // exec timer
        var resultBatch;
        var bs;
        var daten;

        this.writeLog(this.LogLevels.Debug, "Executing getNextBatch()");
        batchNum++;

        var impmode;

        if(pConfig.DataFunction != undefined)
            impmode = "array";
        else if(pConfig.DataFile != undefined)
        {
            if(pConfig.DataFile.substr(pConfig.DataFile.length-3, pConfig.DataFile.length) == 'xml') //is the file an xml file? then use xml-mode
                impmode = "xml";
            else
                impmode = "file";
        }
        else if(pConfig.XMLObject != undefined)
            impmode = "xml";
        else
            impmode = "table";


        switch(impmode)
        {
            case "xml":
                if(batchNum > 1) return null;	// we read files in one sweep

                if(this.Debug == true) this.writeLog("Getting input xml " + pConfig.DataFile, false, 3);
                // file import, which is always a single batch operation
                importtype = "F";
                var strXML;

                // get xml-string directly or load from file?
                if(pConfig.DataFile != "")
                    strXML =	this.getFileContent(pConfig.DataFile, util.DATA_TEXT); //load from file
                else
                    strXML = pConfig.XMLObject; //get xml-string

                if(strXML != "" && strXML != undefined)
                {
                    var xmlData = new XML(strXML);
                    resultBatch = [];
                    var xmlArr = [];

                    //get every row-element
                    for each(xmlItem in xmlData.data.row) // TODO: replace "for each (.. in ..)" with for (.. of ..) when the designer supports it (the server already supports it). As it's deprecated in js https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Statements/for_each...in
                    {
                        xmlArr = [];

                        for each(xmlItem2 in xmlItem.column)
                            xmlArr.push(xmlItem2);

                        resultBatch.push(xmlArr);
                    }
                }
                else
                    this.writeLog("XML-Data is empty or undefined!", false, 0);

                return resultBatch;
                break;

            case "file":
                if(batchNum > 1) return null;	// we read files in one sweep
                if(this.Debug == true) this.writeLog("Reading input file " + pConfig.DataFile, false, 3);
                // file import, which is always a single batch operation
                importtype = "F";

                // otherwise, load the file
                var filestring = this.getFileContent(pConfig.DataFile, util.DATA_TEXT);
                var rs = pConfig.RowSeparator;
                if(rs == undefined) rs = "\n";
                var cs = pConfig.ColumnSeparator;
                if(cs == undefined) cs = ",";
                var sd = pConfig.StringDelimiter;
                if(sd == undefined) sd = "";

                resultBatch = text.parseCSV(filestring, rs, cs, sd);
                tt = datetime.date() - tt;
                exectimer["getdata"] += tt;
                return resultBatch;
                break;

            case "array":
                bs = this.BatchSize;
                if(bs == undefined) bs = 0;

                resultBatch = this.Config.DataFunction.call(this, batchNum, bs);
                tt = datetime.date() - tt;
                exectimer["getdata"] += tt;
                return resultBatch;
                break;

            case "table":
                // table import
                bs = this.BatchSize;
                if(bs == undefined)
                    bs = 0;

                //bs specified: read data in blocks to save memory
                if(bs > 0 )
                {
                    if(this.Config.IdQuery == undefined || this.Config.IdColumn == undefined)
                    {
                        //new method for batch-processsing (paging)
                        daten = db.tablePage(this.Config.DataQuery, this.Config.AliasFrom, batchStart, bs);
                        batchStart += bs;

                        //this happens when all-records % pBlockSize == 0
                        //we do not want to call the callback-fn
                        if (daten.length == 0)
                            daten = null;
                    }
                    else
                    {
                        if (this.idList == undefined)
                        {
                            this.writeLog(this.LogLevels.Warning, "using \"IdQuery\" and \"IdColumn\" is an obsolte method for getting sql-data in blocks");
                            this.idList = this.createIDList();
                        }

                        //legacy method for batch-processsing
                        if(batchStart < this.idList.length)  // as long as we got something to do
                        {
                            batchList = this.idList.slice(batchStart, batchStart + bs);
                            batchStart += bs;

                            // get data of the next batch
                            var sql = this.Config.DataQuery.replace(/\$\$id/i, idcolumn + " in ('" + batchList.join("','") + "')" );
                            daten = db.table(sql, this.Config.AliasFrom);
                        }
                        else
                        {
                            this.idList = undefined;
                            daten = null;
                        }
                    }
                }
                else
                {
                    // return complete result set
                    if(batchNum > 1)
                        daten = null;
                    else
                        daten = db.table(this.Config.DataQuery, this.Config.AliasFrom);
                }
                tt = datetime.date() - tt;
                exectimer["getdata"] += tt;
                return daten;
                break;
        }
    }
    this.createIDList = function()
    {
        var tt = datetime.date();   // exectimer
        var resIDList;

        if(pConfig.IdQuery != undefined && pConfig.DataQuery != undefined && pConfig.IdColumn != undefined)
        {
            // set the id column for the batch retrieval
            idcolumn = this.Config.IdColumn;

            // set multi-batch import
            importtype = "M";

            // get the list of primary keys
            resIDList = db.array(db.COLUMN, pConfig.IdQuery, pConfig.AliasFrom);
            tt = datetime.date() - tt;
            exectimer["getdata"] += tt;
        }
        return resIDList;
    }

    //	@param String pMessage -- die Meldung, die geloggt werden soll
    //	@param String pLevel
    this.writeLog = function(pLevel, pMessage)
    {
        var tt = datetime.date();

        // if logging has been "oursourced", just call the callback function and do nothing
        if(this.LogCallback != null && typeof(this.LogCallback) == "function")
        {
            this.LogCallback(pLevel, pMessage);
        }
        else if(pLevel <= this.LogLevel) 		// shall we output this message?
        {
            var logprefix = "[IMPORTER]@" + datetime.toDate(datetime.date(), translate.text("yyyy-MM-dd HH:mm:ss"), "UTC") + " UTC: ";
            var logline = logprefix + pMessage;
            if (this.enableLogBuffer)
                logBuffer.push(logline);

            if(this.Log == "LOGFILE")
            {
                logging.log(logline);
            }
            else
            {
                if(this.Log == "CONSOLE")
                {
                    logging.log(logline);
                }
                else if(this.Log != "" && this.Log != undefined)
                {
                    // log in globalvar
                    if(this.Log.split(".")[0].toLowerCase() == "$global")
                    {
                        var s = logline + "\r\n";
                        vars.set(this.Log, vars.getString(this.Log) + s);
                    }
                }
            }
        }

        tt = datetime.date() - tt;
        exectimer["logging"] += tt;
    }


    //	retrieve all log messages currently in log buffer
    this.getLogMessages = function(){
        return logBuffer;
    }
    // return true, if running in client context, false for a server context
    this.isClientProcess = function(){
        return vars.getString("$sys.isclient") == "true";
    }


    this.getFileContent = function(pFilename, pDataType)
    {
        var resContent;
        var cp;

        if(this.isClientProcess() == true)   // use doClientIntermediate
        {
            try
            {
                resContent = swing.doClientIntermediate(swing.CLIENTCMD_GETDATA, [pFilename, pDataType, this.fileInputCharset]);
            }
            catch(ex)
            {
                logging.show(ex)
                resContent = "";
            }
        }
        else   // use fileIO
        {
            try
            {
                resContent = fileIO.getData(pFilename, util.DATA_TEXT, this.fileInputCharset);
            }
            catch(ex)
            {
                logging.log( ex );
                resContent = "";
            }
        }
        return resContent;
    }


    this.dumpRecord = function(pTable, pColumns, pTypes, pValues, pCondition)
    {
        var PADDING = "................................";
        var s = "";
        if(pCondition != undefined) s += "update " + pTable + " where " + pCondition; else s += "insert " + pTable;
        s += "\n";
        for(var i=0; i < pColumns.length; i++)
        {
            s += "  " + pColumns[i] + PADDING.substr(0, 32- pColumns[i].length) + ": |" + pValues[i] + "|\n";
        }

        s += "\n";

        return s;
    }


    this.insertData = function(pTable, pColumns, pTypes, pValues, pAlias)
    {
        var tt = datetime.date();
        var resData = true;
        if(this.Preview == false)
        {
            //better safe than sorry ...
            try
            {
                if(pTable.toLowerCase() != "var")
                {
                    if(this.DataType[pTable] == undefined) this.writeLog(this.LogLevels.Error, "Table " + pTable + " not exist in database");
                    else
                    {
                        this.writeLog(this.LogLevels.Debug, "INSERT for [" + pAlias + "].[" + pTable + "]");
                        this.writeLog(this.LogLevels.Preview, this.dumpRecord(pTable, pColumns, pTypes, pValues));
                        //exist already "USER_NEW" and/or "DATE_NEW" in the columns resultset?
                        var uNew = false;
                        var dNew = false;
                        for(var i = 0; i < pColumns.length; i++)
                        {
                            if(uNew == false && pColumns[i].toUpperCase() == "USER_NEW")
                                uNew = true;

                            if(dNew == false && pColumns[i].toUpperCase() == "DATE_NEW")
                                dNew = true;
                        }
                        // process audit columns automagically
                        if(uNew == false && this.DataType[this.getTableCase(pTable)][this.getColumnCase("USER_NEW")] != undefined)
                        {
                            pColumns.push(this.getColumnCase("USER_NEW"));
                            if (pTypes != null) pTypes.push(this.DataType[this.getTableCase(pTable)][this.getColumnCase("USER_NEW")]);
                            pValues.push(this.ImportUser);
                        }
                        if(dNew == false && this.DataType[pTable][this.getColumnCase("DATE_NEW")] != undefined)
                        {
                            pColumns.push(this.getColumnCase("DATE_NEW"));
                            if(pTypes != null) pTypes.push(this.DataType[this.getTableCase(pTable)][this.getColumnCase("DATE_NEW")]);
                            pValues.push(datetime.date());
                        }

                        this.insertArray.push([this.getTableCase(pTable), pColumns, pTypes, pValues, pAlias]);
                    }
                }
            }
            catch(ex)
            {
                this.writeLog(this.LogLevels.Error, "Exception at insertData for record: " + getRecordString(pColumns, pValues));
                logging.log(ex);
                resData = false;
            }
        }
        else
        {
            this.writeLog(this.LogLevels.Preview, "Insert into table " + pTable);
            this.writeLog(this.LogLevels.Preview, this.dumpRecord(pTable, pColumns, pTypes, pValues));
        }

        tt = datetime.date() - tt;
        exectimer["insertData"] += tt;
        return resData;
    }

    this.updateData = function(pTable, pColumns, pTypes, pValues, pCondition, pAlias)
    {
        var tt = datetime.date();
        var theCols;
        var theTypes;
        var theValues;

        var resultData = true;
        if(this.Preview == false)
        {
            try
            {
                if(pTable.toLowerCase() != "var")
                {
                    if(this.DataType[pTable] == undefined) this.writeLog("Table " + pTable + " not exist in database");
                    else
                    {
                        this.writeLog(this.LogLevels.Debug, "UPDATE for alias [" + pAlias + "].[" + pTable + "]");
                        this.writeLog(this.LogLevels.Preview, this.dumpRecord(pTable, pColumns, pTypes, pValues, pCondition));

                        if (this.CompleteUpdate == false)   // check for changed database values and use only changed columns for update
                        {
                            var uColumns = new Array();
                            var uValues = new Array();
                            var uTypes = new Array();
                            var oldValues = db.array(db.ROW, "select " + pColumns.join(", ") + " from " + pTable + " where " + pCondition, pAlias);
                            for (var dsi = 0; dsi < oldValues.length; dsi++)
                            {
                                if (oldValues[dsi] != pValues[dsi])
                                {
                                    //get the values from the validate target
                                    uColumns.push(pColumns[dsi]);
                                    uValues.push(pValues[dsi]);
                                    if (pTypes != null) uTypes.push(pTypes[dsi]);
                                }
                            }
                            theCols = uColumns;
                            theTypes = uTypes;
                            theValues = uValues;
                        }
                        else   // update all columns, so use default column set
                        {
                            theCols = pColumns;
                            theTypes = pTypes;
                            theValues = pValues;
                        }

                        if(theCols.length > 0)
                        {
                            //show the old and the new data only if anything changed
                            this.writeLog(this.LogLevels.Preview, "New Data: " + pValues.join("|"));
                            if (oldValues != undefined)
                                this.writeLog(this.LogLevels.Preview, "Old Data: " + oldValues.join("|"));
                        }

                        var minchanges = 0;
                        var dEdit = false;
                        var uEdit = false;

                        for(var i = 0; i < theCols.length; i++)
                        {
                            if(uEdit == false && theCols[i].toUpperCase() == "USER_EDIT")
                                uEdit = true;

                            if(dEdit == false && theCols[i].toUpperCase() == "DATE_EDIT")
                                dEdit = true;
                        }


                        // process audit columns automagically
                        if(uEdit == false && this.DataType[this.getTableCase(pTable)][this.getColumnCase("USER_EDIT")] != undefined)
                        {
                            theCols.push(this.getColumnCase("USER_EDIT"));
                            if(pTypes != null) theTypes.push(this.DataType[this.getTableCase(pTable)][this.getColumnCase("USER_EDIT")]);
                            theValues.push(this.ImportUser);
                            minchanges++;
                        }
                        if(dEdit == false && this.DataType[this.getTableCase(pTable)][this.getColumnCase("DATE_EDIT")] != undefined)
                        {
                            theCols.push(this.getColumnCase("DATE_EDIT"));
                            if(pTypes != null) theTypes.push(this.DataType[this.getTableCase(pTable)][this.getColumnCase("DATE_EDIT")]);
                            theValues.push(datetime.date());
                            minchanges++;
                        }
                        if(this.CompleteUpdate == false)
                        {
                            if(theCols.length > minchanges)
                            {
                                this.updateArray.push([this.getTableCase(pTable), theCols, theTypes, theValues, pCondition, pAlias]);
                            }
                        }
                        else
                        {
                            this.updateArray.push([this.getTableCase(pTable), theCols, theTypes, theValues, pCondition, pAlias]);
                        }
                    }
                }
            }
            catch(ex)
            {
                resultData = false;
                this.writeLog(this.LogLevels.Error, "Exception at updateData for record: " + getRecordString(pColumns, pValues));
                logging.log(ex);
            }
        }
        else
        {
            this.writeLog(this.LogLevels.Preview, "Update table " + pTable);
            this.writeLog(this.LogLevels.Preview, this.dumpRecord(pTable, pColumns, pTypes, pValues, pCondition));
        }
        tt = datetime.date() - tt;
        exectimer["updateData"] += tt;
        return resultData;
    }

    //		set default action for a mapping call, if action has not been specified
    this.setDefaultAction = function(pObject)
    {
        if(pObject.Action == undefined)  // set reasonable defaults for Action, if not specified
        {
            if(this.Config.ImportCommand == "insert") pObject.Action = "I";
            else
            if(this.Config.ImportCommand == "update") pObject.Action = "U";
            else
            if(this.Config.ImportCommand == "insert+update") pObject.Action = "I+U";
        }
    }

    //		ATTENTION!! This is the *new* version and not the same as resolveSymbols!!
    //
    //		resolve symbol to get import data
    //		may contain literals string and {#} and {tbl.col} symbols
    //		if undefined or empty expression is provided, return an empty string
    this.resolveSymbol = function(pObject, pExpression, pEvalScript)
    {
        var expr;

        if((pExpression != undefined) && (pExpression != ""))
        {
            var inp = this.InputRecord;
            var out = this.OutputRecord;
            var cCase = this.ColumnCase;
            var tCase = this.TableCase;
            var uCase = this.Cases.Upper;

            this.setDefaultAction(pObject);
            var obj = pObject;

            let self = this;
            expr = pExpression.toString();
            expr = expr.replace(/\{([._a-zA-Z0-9]+)\}/ig,
                function(pMatch, pNumber)
                {
                    if(isNaN(Number(pNumber)))
                    {
                        var varname = pNumber.split(".");
                        var res;

                        // action verwenden, wenn keine da => importcommand auslesen
                        var action = obj.Action;
                        if(out[ self.getTableCase(varname[0]) ] != undefined && out[ self.getTableCase(varname[0]) ][ self.getColumnCase(varname[1]) ] != undefined)
                        {
                            switch(obj.Action)
                            {
                                case "I":
                                    res = out[ self.getTableCase(varname[0]) ][ self.getColumnCase(varname[1]) ]["I"];
                                    break;
                                case "U":
                                    res = out[ self.getTableCase(varname[0]) ][ self.getColumnCase(varname[1]) ]["U"];
                                    break;
                                case "I+U":

                                    res = out[ self.getTableCase(varname[0]) ][ self.getColumnCase(varname[1]) ]["U"];																					//2009-06-16  TR
                                    if(res == undefined || res == "") res = out[ self.getTableCase(varname[0])][ self.getColumnCase(varname[1]) ]["I"];  //2009-06-16  TR

                                    break;
                            }
                        }
                        else  // varname does not exist as a property of out
                        {
                            res = undefined;
                        }
                        if(res == undefined) res = "";  // blank out NULL values

                        //transform ' to \\u0027 which is in eval a \u0027; transform " to \\u0022 which is in eval a \u0022
                        //needed for eval like: "'3'2' != ''"
                        //also escape LF and CR to prevent unterminated string errors
                        //if the transform of LF and CR cause problems:
                        //- remove the 2 replaces
                        //- document in the comment why it should not be used and when it will cause other problems
                        //- find another solution to prevent the unterminated string errors
                        if(pEvalScript)
                            return res.replace("'", "\\\\u0027", "g").replace("\"", "\\\\u0022", "g").replace("\r", "\\\\u000D", "g").replace("\n", "\\\\u000A", "g");
                        else
                            return res;
                    }
                    else
                    {
                        //transform ' to \\u0027 which is in eval a \u0027; transform " to \\u0022 which is in eval a \u0022
                        //needed for eval like: "'3'2' != ''"
                        //also escape LF and CR to prevent unterminated string errors
                        //if the transform of LF and CR cause problems:
                        //- remove the 2 replaces
                        //- document in the comment why it should not be used and when it will cause other problems
                        //- find another solution to prevent the unterminated string errors
                        if(pEvalScript)
                            return inp[Number(pNumber)].replace("'", "\\\\u0027", "g").replace("\"", "\\\\u0022", "g").replace("\r", "\\\\u000D", "g").replace("\n", "\\\\u000A", "g");
                        else
                            return inp[Number(pNumber)];
                    }
                } );
            if(pEvalScript == true)
                expr = eval(expr);
        }
        else
        {
            expr = "";
        }
        return expr;
    }

    //	  read column type information for all columns in all tables of the alias specified
    this.getDataTypes = function(pAlias)
    {
        var tables = db.getTables(pAlias);
        var dataTypes = new Object();
        for(var i=0; i < tables.length; i++)
        {
            if (tables[i] != "trace_xe_action_map" && tables[i] != "trace_xe_event_map")
            {
                var cols = db.getColumns(tables[i], pAlias);
                var types = db.getColumnTypes(tables[i], cols, pAlias);
                dataTypes[ tables[i] ] = new Object();  // create sub-object to hold columns
                for(var j=0; j < cols.length; j++) dataTypes[tables[i]][cols[j]] = types[j];
            }
        }
        return dataTypes;   // return object with type information
    }

    //		sets the output record buffer according to "Action" performed
    this.setOutput = function(pObject, pValue)
    {
        try
        {
            var target = pObject.Target.split(".");
            this.setDefaultAction(pObject);
            // make sure we do have an output buffer
            if(this.OutputRecord[ this.getTableCase(target[0]) ] == undefined) this.OutputRecord[ this.getTableCase(target[0]) ] = new Object();
            if(this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ] == undefined) this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ] = new Object();
            switch(pObject.Action)
            {
                case "I":
                    this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["I"] = pValue;
                    break;
                case "U":
                    this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["U"] = pValue;
                    break;
                case "I+U":
                    this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["I"] = pValue;    //2009-06-16  TR
                    this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["U"] = pValue;    //2009-06-16  TR
                    this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["I+U"] = pValue;  //2009-06-16  TR
                    break;
            }
        }
        catch(ex)
        {
            this.writeLog(this.LogLevels.Error, "Property <Target> not set for mapping object!");
            logging.log(ex);
            logging.log("[IMPORTER] Property <Target> not set for mapping object!");
        }
    }
    //		get the content of the output record buffer according to "Action" performed
    this.getOutput = function(pObject, pTarget)
    {
        var target;
        var action;

        target = pTarget.split(".");
        if(pObject != undefined) action = pObject.Action; else action = undefined;
        if(action == undefined)  // set reasonable defaults for Action, if not specified
        {
            if(this.Config.ImportCommand == "insert") action = "I";
            else
            if(this.Config.ImportCommand == "update") action = "U";
            else
            if(this.Config.ImportCommand == "insert+update") action = "I+U";
        }
        var resAction;
        switch(action)
        {
            case "I":
                resAction = this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1])]["I"];
                break;
            case "U":
                resAction = this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["U"];
                break;
            case "I+U":
                resAction = this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["U"];
                if(resAction == undefined || resAction == "") resAction = this.OutputRecord[ this.getTableCase(target[0]) ][ this.getColumnCase(target[1]) ]["I"];
                break;
        }
        if(resAction == undefined) resAction = "";   // blank out undefined
        return resAction;
    }


    //		generates condition clause (without "WHERE") to check for existing key column values
    this.generateKeyCondition = function(pTable)
    {
        var whereclause = "";
        for(var col in this.KeyColumn[pTable])
        {
            var tmp = this.KeyColumn[pTable][col];  // contains I | I;U | I+U
            if(tmp != "")
            {
                var value = this.OutputRecord[pTable][col]["U"];
                if(value == undefined || value == "")
                    value = this.OutputRecord[pTable][col]["I+U"];
                if(value == undefined || value == "")
                    value = this.OutputRecord[pTable][col]["I"];

                // we cannot distinguish between an empty string and NULL in Jdito,
                // so if get an empty string for the value, we do a check for empty string OR NULL
                if(value == "")
                {
                    whereclause += " and (" + col + " = '' OR " + col + " is null)";
                }
                else
                {
                    whereclause += " and " + col + " = '" + db.quote(value, this.Config.AliasTo) + "'";
                }
            }
        }
        // remove leading "and" from where clause
        if(whereclause.substr(0, 4) == " and") whereclause = whereclause.substr(4, whereclause.length);

        return whereclause;
    }
    /*
    * yield a string representation of the record in pColumns and pValues for logging
    * purposes and debugging.
    *
    * @param {String[]} pColumns req columns that are represented
    * @param {String[]} pValues req values that are represented
    *
    * @return {String}
    */
    function getRecordString(pColumns, pValues)
    {
        var s = "";
        for(var i=0; i < pColumns.length; i++)
        {
            var v = "";
            if(pValues[i]) v = pValues[i].toString();
            if(v.length > 40) v = v.substr(0,39) + "...";
            s += "  COL: " + pColumns[i] + ": " + v + "\n";
        }

        return s;
    }

    //Converts a string with the tablename in upper or lower case
    this.getTableCase = function(pName)
    {
        if(this.TableCase == this.Cases.Upper)
            return pName.toUpperCase();
        else if (this.TableCase == this.Cases.Lower)
            return pName.toLowerCase();
        else
            return pName;
    }

    //Converts a string with the columnname in upper or lower case
    this.getColumnCase = function(pName)
    {
        if (this.ColumnCase == this.Cases.Upper)
            return pName.toUpperCase();
        else if (this.ColumnCase == this.Cases.Lower)
            return pName.toLowerCase();
        else
            return pName;
    }

    this.doIfCheck = function(pObject)
    {
        if (pObject.DoIf == undefined) return true;

        if (typeof(pObject.DoIf) == "function")
        {
            var expr;
            if(pObject.Source != undefined) expr = this.InputRecord[pObject.Source];
            if(pObject.Value != undefined) expr = this.resolveSymbol(pObject, pObject.Value, pObject.Eval);
            if(pObject.Map != undefined && pObject.Index) expr = pObject.Map[this.resolveSymbol(pObject, pObect.Index, pObject.Eval)];
            return pObject.DoIf.call(this, this.InputRecord, expr, pObject);
        }
        
        return (this.resolveSymbol(pObject, pObject.DoIf, true));
    }
}

/**
 * Data handler for csv paging
 *
 * @param pCurrentBatchNum
 * @param pBatchSize
 */
function batchCsvLoad(pCurrentBatchNum, pBatchSize)
{
    if (this.BatchStart == undefined) this.BatchStart = 0;
    if (this.Config.RowSeparator != undefined) this.writeLog(this.LogLevels.Warning, "'RowSeparator' option is currently not supported!")
    var dataStr = "";
    var fn = this.Config.DataFile;
    var bs = pBatchSize;
    if (!fileIO.exists(fn))
        throw new Error(translate.withArguments("file '%0' does not exist or you have got no permission on this file", [fn]));
    else if (!fileIO.canRead(fn))
        throw new Error(translate.withArguments("file '%0' cannot be read", [fn]));
    const MAX_LOOP_SIZE = this.Config.BatchSize;
    do
    {
        dataStr = null;
        var loopSize = MAX_LOOP_SIZE;
        try
        {
            dataStr = fileIO.getBulkData(fn, util.DATA_TEXT, this.fileInputCharset, this.BatchStart, bs);
        }
        catch(ex)
        {
            break;
        }
        this.BatchStart += bs;
        whileMultiline: while (dataStr.slice(-2) != "\r\n" && (dataStr.slice(-1) == "\n" || dataStr.slice(-1) == "\r"))//we have to load more Data
        {
            try
            {
                dataStr += fileIO.getBulkData(fn, util.DATA_TEXT, this.fileInputCharset, this.BatchStart, 1);
            }
            catch(ex)
            {
                this.writeLog(this.LogLevels.Warning, ex);
                break;
            }
            this.BatchStart++;
            if (loopSize == 0)
            {
                this.writeLog(this.LogLevels.Error, "MAX_LOOP_SIZE reached; Abort");
                break whileMultiline;
            }
            loopSize--;
        }
        if (dataStr.slice(-2) == "\r\n")
            dataStr = dataStr.slice(0, -2);
        var rs = this.Config.RowSeparator;
        if(rs == undefined) rs = "\r\n";
        var cs = this.Config.ColumnSeparator;
        if(cs == undefined) cs = ";";
        var sd = this.Config.StringDelimiter;
        if(sd == undefined) sd = "";
        return text.parseCSV(dataStr, rs, cs, sd);
    }
    while(dataStr != null);
    return null;
}