Пример #1
0
 /// <summary>
 /// Method to use proper database
 /// </summary>
 /// <param name="database">name of chosen database</param>
 public void Use(string database, ColumnarDBClient client)
 {
     try
     {
         client.UseDatabase(database);
         Console.WriteLine(SuccessfulUse(database));
     }
     catch (IOException e)
     {
         Console.WriteLine(e.Message);
     }
     catch (QueryException e)
     {
         Console.WriteLine("Query Exception: " + e.Message);
     }
     catch (Exception e)
     {
         Console.WriteLine(UnknownException() + e.Message);
     }
 }
Пример #2
0
        /// <summary>
        /// Method to import CSV file
        /// </summary>
        /// <param name="path">Path to csv file that should be imported</param>
        /// <param name="databaseName">Database that is used</param>
        /// <param name="blockSize">Block size of table, if not specified default value of database server configuration will be used</param>
        /// <param name="hasHeader">Specifies whether input csv has header, if not specified default value is true</param>
        /// <param name="columnSeparator">Char representing column separator, if not specified default value will be guessed</param>
        /// <param name="batchSize">Number of lines processed in one batch, if not specified default value is 65536</param>
        /// <param name="threadsCount">Number of threads for processing csv, if not specified number of cores of the client CPU will be used</param>
        /// <param name="encoding">Encoding of csv, if not specified it will be guessed</param>
        public void Import(string path, string databaseName,
                           string tableName = "", int blockSize = 0, bool hasHeader = true, char columnSeparator = '\0', int batchSize = 65536, int threadsCount = 0, Encoding encoding = null)
        {
            this.databaseName = databaseName;

            try
            {
                // Table name from path or from argument if present
                if (tableName == "")
                {
                    tableName = Path.GetFileNameWithoutExtension(path);
                }

                this.tableName = tableName;

                if (encoding == null)
                {
                    encoding = ParserCSV.GuessEncoding(path);
                }
                if (columnSeparator == '\0')
                {
                    columnSeparator = ParserCSV.GuessSeparator(path, encoding);
                }
                var types = ParserCSV.GuessTypes(path, hasHeader, columnSeparator, encoding);
                streamLength = ParserCSV.GetStreamLength(path);

                client.Connect();
                client.UseDatabase(databaseName);
                CreateTable(databaseName, tableName, types, blockSize);

                ParserCSV.Configuration configuration = new ParserCSV.Configuration(batchSize: batchSize, encoding: encoding, columnSeparator: columnSeparator);

                if (threadsCount <= 0)
                {
                    // Use more threads if file is bigger than 10MB
                    if (streamLength > 10000000)
                    {
                        threadsCount = Environment.ProcessorCount;
                    }
                    else
                    {
                        threadsCount = 1;
                    }
                }

                Console.WriteLine("Importing started with " + threadsCount + " threads.");
                var startTime = DateTime.Now;

                long streamThreadLength = streamLength / threadsCount;
                long lines  = 0;
                long errors = 0;
                linesImported = new long[threadsCount];
                bytesImported = new long[threadsCount];
                linesError    = new long[threadsCount];
                Thread[]    threads          = new Thread[threadsCount];
                Exception[] threadExceptions = new Exception[threadsCount];

                // Each thread has its own instance of the Parser (because of different position of read) and ColumnarDBClient (because of concurrent bulk import)
                for (int i = 0; i < threadsCount; i++)
                {
                    long start = i * streamThreadLength;
                    long end   = i * streamThreadLength + streamThreadLength + 1;
                    int  index = i;
                    threadExceptions[index] = null;
                    threads[index]          = new Thread(() =>
                    {
                        try
                        {
                            this.ParseAndImportBatch(index, path, configuration, types, start, end);
                        }
                        catch (Exception ex)
                        {
                            threadExceptions[index] = ex;
                        }
                    });
                    threads[index].Start();
                    //this.ParseAndImportBatch(index, path, configuration, types, start, end);
                }

                for (int i = 0; i < threadsCount; i++)
                {
                    threads[i].Join();
                }

                for (int i = 0; i < threadsCount; i++)
                {
                    if (threadExceptions[i] != null)
                    {
                        throw threadExceptions[i];
                    }
                }

                for (int i = 0; i < threadsCount; i++)
                {
                    lines  += linesImported[i];
                    errors += linesError[i];
                }

                client.Dispose();

                var endTime = DateTime.Now;
                Console.WriteLine();
                Console.WriteLine("Importing done (imported " + lines + " records, " + errors + " failed, " + (endTime - startTime).TotalSeconds.ToString() + "sec.).");
            }
            catch (FileNotFoundException)
            {
                Console.WriteLine(FileNotFound(path));
            }
            catch (IOException e)
            {
                Console.WriteLine(e.Message);
            }
            catch (QueryException e)
            {
                Console.WriteLine("Query Exception: " + e.Message);
            }
            catch (ParserCSV.ParserException e)
            {
                Console.WriteLine("Parser Exception: " + e.Message);
            }
            catch (Exception e)
            {
                Console.WriteLine(UnknownException() + e.Message);
            }
        }
Пример #3
0
        /// <summary>
        /// Load benchmark queries from a file, execute them one by one and save results.
        /// </summary>
        public static int Main(string[] args)
        {
            bool avgTimePassed        = true;
            bool correctResultsPassed = true;

            Array.Sort(args);

            ColumnarDBClient client = new ColumnarDBClient($"Host={ipAddress};Port={port.ToString()};");

            client.Connect();
            Console.WriteLine("Client has successfully connected to server.");

            UseDatabase use = new UseDatabase();

            if (File.Exists(resultFilePath))
            {
                File.Delete(resultFilePath);
            }

            var resultFile = new System.IO.StreamWriter(resultFilePath);

            if (args.GetLength(0) == 0)
            {
                string[]      array    = { "-a" };
                List <string> tempList = new List <string>(array);
                args = tempList.ToArray();
            }

            resultFile.WriteLine($"Each query was executed {numberOfQueryExec} times and the average time was saved.");

            foreach (string arg in args)
            {
                string queryString;
                string queryExptectedString;

                if (String.Equals(arg, "-a") || String.Equals(arg, "-b"))
                {
                    // test telco queries:
                    use.Use(telcoDbName, client);

                    client.UseDatabase(telcoDbName);

                    var queryFile = new System.IO.StreamReader(telcoQueriesPath);
                    Console.WriteLine($"Benchmark queries from file '{telcoQueriesPath}' were loaded.");

                    while ((queryString = queryFile.ReadLine()) != null)
                    {
                        Console.WriteLine($"Executing benchmark query: {queryString}");
                        resultFile.WriteLine(queryString);

                        // execute query first time (no cache):
                        double resultSum = 0;
                        try
                        {
                            client.Query(queryString);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e);
                            break;
                        }
                        Dictionary <string, float> executionTimes = null;
                        ColumnarDataTable          result         = null;

                        while (((result, executionTimes) = client.GetNextQueryResult()).result != null)
                        {
                            resultSum += executionTimes.Values.Sum();
                        }

                        resultSum = Math.Round(resultSum);

                        // save query result to a file:
                        resultFile.WriteLine((resultSum).ToString() + " (first run)");
                        Console.WriteLine((resultSum).ToString() + " (first run)");

                        // execute query N times (used cache):
                        for (int i = 0; i < numberOfQueryExec; i++)
                        {
                            try
                            {
                                client.Query(queryString);
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine(e);
                                break;
                            }

                            while (((result, executionTimes) = client.GetNextQueryResult()).result != null)
                            {
                                resultSum += executionTimes.Values.Sum();
                            }
                        }

                        double avgQueryExec = Math.Round(resultSum / numberOfQueryExec);

                        // save query result to a file:
                        resultFile.WriteLine(avgQueryExec.ToString() + " (average cached N runs)");
                        Console.WriteLine(avgQueryExec + " (average cached N runs)");

                        // check if query execution time is acceptable and save the result:
                        int queryExpectedExecTime = System.Convert.ToInt32(queryFile.ReadLine());
                        if (avgQueryExec < queryExpectedExecTime)
                        {
                            resultFile.WriteLine($"The query '{queryString}' has passed the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime.ToString()} / {avgQueryExec.ToString()}");
                            Console.WriteLine($"The query '{queryString}' has passed the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime} / {avgQueryExec}");
                        }
                        else
                        {
                            resultFile.WriteLine($"The query '{queryString}' has FAILED the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime.ToString()} / {avgQueryExec.ToString()}");
                            Console.WriteLine($"The query '{queryString} ' has FAILED the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime} / {avgQueryExec}");
                            avgTimePassed = false;
                        }
                    }
                    queryFile.Close();
                }

                if (String.Equals(arg, "-a") || String.Equals(arg, "-g"))
                {
                    // test geo queries:
                    use.Use(geoDbName, client);

                    client.UseDatabase(geoDbName);

                    var queryFile = new System.IO.StreamReader(geoQueriesPath);
                    Console.WriteLine($"Benchmark queries from file '{geoQueriesPath}' were loaded.");

                    while ((queryString = queryFile.ReadLine()) != null)
                    {
                        Console.WriteLine($"Executing benchmark query: {queryString}");
                        resultFile.WriteLine(queryString);

                        // execute query first time (no cache):
                        double resultSum = 0;
                        try
                        {
                            client.Query(queryString);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e);
                            break;
                        }
                        Dictionary <string, float> executionTimes = null;
                        ColumnarDataTable          result         = null;

                        while (((result, executionTimes) = client.GetNextQueryResult()).result != null)
                        {
                            resultSum += executionTimes.Values.Sum();
                        }

                        resultSum = Math.Round(resultSum);

                        // save query result to a file:
                        resultFile.WriteLine((resultSum).ToString() + " (first run)");
                        Console.WriteLine((resultSum).ToString() + " (first run)");

                        // execute query N times (used cache):
                        for (int i = 0; i < numberOfQueryExec; i++)
                        {
                            try
                            {
                                client.Query(queryString);
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine(e);
                                break;
                            }

                            while (((result, executionTimes) = client.GetNextQueryResult()).result != null)
                            {
                                resultSum += executionTimes.Values.Sum();
                            }
                        }

                        double avgQueryExec = Math.Round(resultSum / numberOfQueryExec);

                        // save query result to a file:
                        resultFile.WriteLine(avgQueryExec.ToString() + " (average cached N runs)");
                        Console.WriteLine(avgQueryExec + " (average cached N runs)");

                        // check if query execution time is acceptable and save the result:
                        int queryExpectedExecTime = System.Convert.ToInt32(queryFile.ReadLine());
                        if (avgQueryExec < queryExpectedExecTime)
                        {
                            resultFile.WriteLine($"The query '{queryString}' has passed the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime.ToString()} / {avgQueryExec.ToString()}");
                            Console.WriteLine($"The query '{queryString}' has passed the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime} / {avgQueryExec}");
                        }
                        else
                        {
                            resultFile.WriteLine($"The query '{queryString}' has FAILED the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime.ToString()} / {avgQueryExec.ToString()}");
                            Console.WriteLine($"The query '{queryString} ' has FAILED the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime} / {avgQueryExec}");
                            avgTimePassed = false;
                        }
                    }
                    queryFile.Close();
                }

                if (String.Equals(arg, "-a") || String.Equals(arg, "-t"))
                {
                    // test taxi queries:
                    use.Use(taxiDbName, client);

                    client.UseDatabase(taxiDbName);

                    var queryFile = new System.IO.StreamReader(taxiQueriesPath);
                    Console.WriteLine($"Benchmark queries from file '{taxiQueriesPath}' were loaded.");

                    while ((queryString = queryFile.ReadLine()) != null)
                    {
                        Console.WriteLine($"Executing benchmark query: {queryString}");
                        resultFile.WriteLine(queryString);

                        // execute query first time (no cache):
                        double resultSum = 0;
                        client.Query(queryString);
                        Dictionary <string, float> executionTimes = null;
                        ColumnarDataTable          result         = null;

                        while (((result, executionTimes) = client.GetNextQueryResult()).result != null)
                        {
                            resultSum += executionTimes.Values.Sum();
                        }

                        resultSum = Math.Round(resultSum);

                        // save query result to a file:
                        resultFile.WriteLine((resultSum).ToString() + " (first run)");
                        Console.WriteLine((resultSum).ToString() + " (first run)");

                        // execute query N times (used cache):
                        for (int i = 0; i < numberOfQueryExec; i++)
                        {
                            client.Query(queryString);

                            while (((result, executionTimes) = client.GetNextQueryResult()).result != null)
                            {
                                resultSum += executionTimes.Values.Sum();
                            }
                        }

                        // save query result to a file:
                        resultFile.WriteLine((resultSum / numberOfQueryExec).ToString() + " (average cached N runs)");
                        Console.WriteLine((resultSum / numberOfQueryExec) + " (average cached N runs)");
                    }
                    queryFile.Close();
                }

                if (String.Equals(arg, "-a") || String.Equals(arg, "--ci"))
                {
                    // test taxi queries:
                    use.Use(taxiDbName, client);

                    client.UseDatabase(taxiDbName);

                    var queryFile = new System.IO.StreamReader(ciQueriesPath);
                    Console.WriteLine($"Benchmark queries from file '{ciQueriesPath}' were loaded.");

                    int queryIndex = 1; // indexing from 1, not zero, because we use to call it taxi rides query number 1, not number 0, so it is not confusing
                    Dictionary <string, IList> columnData = null;

                    while ((queryString = queryFile.ReadLine()) != null)
                    {
                        Console.WriteLine($"Executing benchmark query: {queryString}");
                        resultFile.WriteLine(queryString);

                        // execute query first time (no cache):
                        double resultSum = 0;
                        try
                        {
                            client.Query(queryString);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e);
                            break;
                        }
                        Dictionary <string, float> executionTimes = null;
                        ColumnarDataTable          result         = null;

                        // read file where the results of a particular query are saved:
                        var queryExpectedResultFile = new StreamReader($"../../../QikkDB.BenchmarkUtility/{taxiDbName}_testQuery_{queryIndex}.txt");
                        queryIndex++;

                        // read the file header
                        var expectedColumnNames = queryExpectedResultFile.ReadLine().Split('|');

                        // read expected column data types
                        var expectedDataTypes = queryExpectedResultFile.ReadLine().Split('|');

                        Dictionary <string, IList> exptectedColumns = new Dictionary <string, IList>();

                        bool firstPart = true;

                        while (((result, executionTimes) = client.GetNextQueryResult()).result != null)
                        {
                            // long results are divided into multiple parts
                            if (firstPart)
                            {
                                columnData = result.GetColumnData();
                            }
                            else
                            {
                                for (int i = 0; i < expectedColumnNames.Length; i++)
                                {
                                    for (int j = 0; j < result.GetColumnData()[expectedColumnNames[i]].Count; j++)
                                    {
                                        columnData[expectedColumnNames[i]].Add(result.GetColumnData()[expectedColumnNames[i]][j]);
                                    }
                                }
                            }

                            resultSum += executionTimes.Values.Sum();
                            firstPart  = false;
                        }

                        for (int i = 0; i < expectedColumnNames.Length; i++)
                        {
                            switch (expectedDataTypes[i])
                            {
                            case "INT":
                                exptectedColumns.Add(expectedColumnNames[i], new List <Int32>());
                                break;

                            case "LONG":
                                exptectedColumns.Add(expectedColumnNames[i], new List <Int64>());
                                break;

                            case "FLOAT":
                                exptectedColumns.Add(expectedColumnNames[i], new List <Single>());
                                break;

                            case "DOUBLE":
                                exptectedColumns.Add(expectedColumnNames[i], new List <Double>());
                                break;

                            case "STRING":
                                exptectedColumns.Add(expectedColumnNames[i], new List <String>());
                                break;
                            }
                        }

                        // read results from a file:
                        while ((queryExptectedString = queryExpectedResultFile.ReadLine()) != null)
                        {
                            var results = queryExptectedString.Split('|');

                            for (int i = 0; i < expectedColumnNames.Length; i++)
                            {
                                switch (expectedDataTypes[i])
                                {
                                case "INT":
                                    exptectedColumns[expectedColumnNames[i]].Add(Int32.Parse(results[i]));
                                    break;

                                case "LONG":
                                    exptectedColumns[expectedColumnNames[i]].Add(Int64.Parse(results[i]));
                                    break;

                                case "FLOAT":
                                    exptectedColumns[expectedColumnNames[i]].Add(Single.Parse(results[i]));
                                    break;

                                case "DOUBLE":
                                    var styles   = NumberStyles.AllowDecimalPoint;
                                    var provider = CultureInfo.CreateSpecificCulture("en-US");
                                    exptectedColumns[expectedColumnNames[i]].Add(Double.Parse(results[i], styles, provider));
                                    break;

                                case "STRING":
                                    exptectedColumns[expectedColumnNames[i]].Add(results[i]);
                                    break;
                                }
                            }
                        }

                        // check if the expected result dictionary is the same as actual query result dictionary:
                        for (int i = 0; i < expectedColumnNames.Length; i++)
                        {
                            try
                            {
                                if (exptectedColumns[expectedColumnNames[i]].Count != columnData[expectedColumnNames[i]].Count)
                                {
                                    resultFile.WriteLine($"The query '{queryString}' has FAILED the correct results test. Expected / Actual count of result entries: {exptectedColumns[expectedColumnNames[i]].Count.ToString()} / {columnData[expectedColumnNames[i]].Count.ToString()}");
                                    Console.WriteLine($"The query '{queryString} ' has FAILED the correct results test. Expected / Actual count of result entries: {exptectedColumns[expectedColumnNames[i]].Count} / {columnData[expectedColumnNames[i]].Count}");
                                    correctResultsPassed = false;
                                }
                                else
                                {
                                    // check each element in result's lists
                                    for (int j = 0; j < exptectedColumns[expectedColumnNames[i]].Count; j++)
                                    {
                                        bool tempCorrectResultsPassed = true;

                                        switch (expectedDataTypes[i])
                                        {
                                        case "INT":
                                            if ((int)exptectedColumns[expectedColumnNames[i]][j] != (int)columnData[expectedColumnNames[i]][j])
                                            {
                                                tempCorrectResultsPassed = false;
                                            }
                                            break;

                                        case "LONG":
                                            if ((long)exptectedColumns[expectedColumnNames[i]][j] != (long)columnData[expectedColumnNames[i]][j])
                                            {
                                                tempCorrectResultsPassed = false;
                                            }
                                            break;

                                        case "FLOAT":
                                            if (Math.Abs((float)exptectedColumns[expectedColumnNames[i]][j] - (float)columnData[expectedColumnNames[i]][j]) > 0.001)
                                            {
                                                tempCorrectResultsPassed = false;
                                            }
                                            break;

                                        case "DOUBLE":
                                            if (Math.Abs((double)exptectedColumns[expectedColumnNames[i]][j] - (double)columnData[expectedColumnNames[i]][j]) > 0.001)
                                            {
                                                tempCorrectResultsPassed = false;
                                            }
                                            break;

                                        case "STRING":
                                            if ((string)exptectedColumns[expectedColumnNames[i]][j] != (string)columnData[expectedColumnNames[i]][j])
                                            {
                                                tempCorrectResultsPassed = false;
                                            }
                                            break;
                                        }

                                        if (!tempCorrectResultsPassed)
                                        {
                                            resultFile.WriteLine($"The query '{queryString}' has FAILED the correct results test. Expected[{expectedColumnNames[i].ToString()}][{j.ToString()}] / Actual[{j.ToString()}] returned value: {exptectedColumns[expectedColumnNames[i]][j].ToString()} / {columnData[expectedColumnNames[i]][j].ToString()}");
                                            Console.WriteLine($"The query '{queryString}' has FAILED the correct results test.  Expected[{expectedColumnNames[i].ToString()}][{j}] / Actual[{j}] returned value: {exptectedColumns[expectedColumnNames[i]][j]} / {columnData[expectedColumnNames[i]][j]}");
                                            correctResultsPassed = false;
                                        }
                                    }
                                }
                            }
                            catch (System.Collections.Generic.KeyNotFoundException e)
                            {
                                resultFile.WriteLine($"The query '" + queryString + "' has FAILED the correct results test. Expected / Actual returned value: " + exptectedColumns[expectedColumnNames[i]].ToString() + " / System.Collections.Generic.KeyNotFoundException was thrown: " + e.Message);
                                Console.WriteLine($"The query '" + queryString + "' has FAILED the correct results test. Expected / Actual returned value: " + exptectedColumns[expectedColumnNames[i]] + " / System.Collections.Generic.KeyNotFoundException was thrown: " + e.Message);
                                correctResultsPassed = false;
                            }
                        }

                        resultSum = Math.Round(resultSum);

                        // save query result to a file:
                        resultFile.WriteLine((resultSum).ToString() + " (first run)");
                        Console.WriteLine((resultSum).ToString() + " (first run)");

                        // execute query N times (used cache):
                        for (int i = 0; i < numberOfQueryExec; i++)
                        {
                            try
                            {
                                client.Query(queryString);
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine(e);
                                break;
                            }

                            while (((result, executionTimes) = client.GetNextQueryResult()).result != null)
                            {
                                resultSum += executionTimes.Values.Sum();
                            }
                        }

                        double avgQueryExec = Math.Round(resultSum / numberOfQueryExec);

                        // save query result to a file:
                        resultFile.WriteLine(avgQueryExec.ToString() + " (average cached N runs)");
                        Console.WriteLine(avgQueryExec + " (average cached N runs)");

                        // check if query execution time is acceptable and save the result:
                        int queryExpectedExecTime = System.Convert.ToInt32(queryFile.ReadLine());
                        if (avgQueryExec < queryExpectedExecTime)
                        {
                            resultFile.WriteLine($"The query '{queryString}' has passed the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime.ToString()} / {avgQueryExec.ToString()}");
                            Console.WriteLine($"The query '{queryString}' has passed the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime} / {avgQueryExec}");
                        }
                        else
                        {
                            resultFile.WriteLine($"The query '{queryString}' has FAILED the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime.ToString()} / {avgQueryExec.ToString()}");
                            Console.WriteLine($"The query '{queryString} ' has FAILED the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime} / {avgQueryExec}");
                            avgTimePassed = false;
                        }
                    }
                    queryFile.Close();
                }

                if (String.Equals(arg, "-a") || String.Equals(arg, "-s"))
                {
                    // test stcs queries:
                    use.Use(stcsDbName, client);

                    client.UseDatabase(stcsDbName);

                    var queryFile = new System.IO.StreamReader(stcsQueriesPath);
                    Console.WriteLine($"Benchmark queries from file '{stcsQueriesPath}' were loaded.");

                    while ((queryString = queryFile.ReadLine()) != null)
                    {
                        Console.WriteLine($"Executing benchmark query: {queryString}");
                        resultFile.WriteLine(queryString);

                        // execute query first time (no cache):
                        double resultSum = 0;
                        try
                        {
                            client.Query(queryString);
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e);
                            break;
                        }
                        Dictionary <string, float> executionTimes = null;
                        ColumnarDataTable          result         = null;

                        while (((result, executionTimes) = client.GetNextQueryResult()).result != null)
                        {
                            resultSum += executionTimes.Values.Sum();
                        }

                        resultSum = Math.Round(resultSum);

                        // save query result to a file:
                        resultFile.WriteLine((resultSum).ToString() + " (first run)");
                        Console.WriteLine((resultSum).ToString() + " (first run)");

                        // execute query N times (used cache):
                        for (int i = 0; i < numberOfQueryExec; i++)
                        {
                            try
                            {
                                client.Query(queryString);
                            }
                            catch (Exception e)
                            {
                                Console.WriteLine(e);
                                break;
                            }

                            while (((result, executionTimes) = client.GetNextQueryResult()).result != null)
                            {
                                resultSum += executionTimes.Values.Sum();
                            }
                        }

                        double avgQueryExec = Math.Round(resultSum / numberOfQueryExec);

                        // save query result to a file:
                        resultFile.WriteLine(avgQueryExec.ToString() + " (average cached N runs)");
                        Console.WriteLine(avgQueryExec + " (average cached N runs)");

                        // check if query execution time is acceptable and save the result:
                        int queryExpectedExecTime = System.Convert.ToInt32(queryFile.ReadLine());
                        if (avgQueryExec < queryExpectedExecTime)
                        {
                            resultFile.WriteLine($"The query '{queryString}' has passed the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime.ToString()} / {avgQueryExec.ToString()}");
                            Console.WriteLine($"The query '{queryString}' has passed the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime} / {avgQueryExec}");
                        }
                        else
                        {
                            resultFile.WriteLine($"The query '{queryString}' has FAILED the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime.ToString()} / {avgQueryExec.ToString()}");
                            Console.WriteLine($"The query '{queryString} ' has FAILED the execution time test. Expected / Actual average query execution time: {queryExpectedExecTime} / {avgQueryExec}");
                            avgTimePassed = false;
                        }
                    }
                    queryFile.Close();
                }
            }
            resultFile.Close();

            // return exit code:
            if (correctResultsPassed && avgTimePassed)
            {
                // everything was successful
                return(0);
            }

            if (!correctResultsPassed && avgTimePassed)
            {
                // query results were not correct, but query has finished in expected time
                return(1);
            }

            if (correctResultsPassed && !avgTimePassed)
            {
                // query results were corrcet, but query has not finished in expected time
                return(2);
            }

            if (!correctResultsPassed && !avgTimePassed)
            {
                // neither query results were correct, nor query has finished execution in expected time
                return(3);
            }

            // something else has happend
            return(4);
        }