/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

namespace Apache.Ignite.Tests.Linq;

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Ignite.Sql;
using Ignite.Table;
using Microsoft.Extensions.Logging;
using NodaTime;
using NUnit.Framework;
using Table;

/// <summary>
/// Basic LINQ provider tests.
/// </summary>
[SuppressMessage("ReSharper", "PossibleLossOfFraction", Justification = "Tests")]
[SuppressMessage("ReSharper", "NotAccessedPositionalProperty.Local", Justification = "Tests")]
[SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix", Justification = "Tests")]
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Tests")]
[SuppressMessage("ReSharper", "UnusedMember.Local", Justification = "Tests")]
public partial class LinqTests : IgniteTestsBase
{
    private const int Count = 10;

    private IRecordView<PocoByte> PocoByteView { get; set; } = null!;

    private IRecordView<PocoShort> PocoShortView { get; set; } = null!;

    private IRecordView<PocoInt> PocoIntView { get; set; } = null!;

    private IRecordView<PocoIntEnum> PocoIntEnumView { get; set; } = null!;

    private IRecordView<PocoLong> PocoLongView { get; set; } = null!;

    private IRecordView<PocoFloat> PocoFloatView { get; set; } = null!;

    private IRecordView<PocoDouble> PocoDoubleView { get; set; } = null!;

    private IRecordView<PocoDecimal> PocoDecimalView { get; set; } = null!;

    private IRecordView<PocoBigDecimal> PocoBigDecimalView { get; set; } = null!;

    private IRecordView<PocoString> PocoStringView { get; set; } = null!;

    [OneTimeSetUp]
    public async Task InsertData()
    {
        var tableNames = new[]
        {
            TableName, TableDateTimeName, TableDoubleName, TableFloatName, TableDecimalName, TableInt8Name,
            TableInt16Name, TableInt32Name, TableInt64Name
        };

        foreach (var tableName in tableNames)
        {
            await Client.Sql.ExecuteAsync(null, "delete from " + tableName);
        }

        PocoByteView = (await Client.Tables.GetTableAsync(TableInt8Name))!.GetRecordView<PocoByte>();
        PocoShortView = (await Client.Tables.GetTableAsync(TableInt16Name))!.GetRecordView<PocoShort>();
        PocoIntView = (await Client.Tables.GetTableAsync(TableInt32Name))!.GetRecordView<PocoInt>();
        PocoIntEnumView = (await Client.Tables.GetTableAsync(TableInt32Name))!.GetRecordView<PocoIntEnum>();
        PocoLongView = (await Client.Tables.GetTableAsync(TableInt64Name))!.GetRecordView<PocoLong>();
        PocoFloatView = (await Client.Tables.GetTableAsync(TableFloatName))!.GetRecordView<PocoFloat>();
        PocoDoubleView = (await Client.Tables.GetTableAsync(TableDoubleName))!.GetRecordView<PocoDouble>();
        PocoStringView = (await Client.Tables.GetTableAsync(TableStringName))!.GetRecordView<PocoString>();

        var tableDecimal = await Client.Tables.GetTableAsync(TableDecimalName);
        PocoDecimalView = tableDecimal!.GetRecordView<PocoDecimal>();
        PocoBigDecimalView = tableDecimal.GetRecordView<PocoBigDecimal>();

        for (int i = 0; i < Count; i++)
        {
            await PocoView.UpsertAsync(null, new Poco { Key = i, Val = "v-" + i });

            await PocoByteView.UpsertAsync(null, new PocoByte((sbyte)i, (sbyte)(i / 3)));
            await PocoShortView.UpsertAsync(null, new PocoShort((short)(i * 2), (short)(i * 2)));
            await PocoIntView.UpsertAsync(null, new PocoInt(i, i * 100));
            await PocoLongView.UpsertAsync(null, new PocoLong(i, i * 2));

            await PocoFloatView.UpsertAsync(null, new(i, i));
            await PocoDoubleView.UpsertAsync(null, new(i, i));
            await PocoDecimalView.UpsertAsync(null, new(i, i));

            await PocoStringView.UpsertAsync(null, new("k-" + i, "v-" + i * 2));

            var pocoAllColumns = new PocoAllColumnsSqlNullable(
                i,
                "v -" + i,
                (sbyte)(i + 1),
                (short)(i + 2),
                i + 3,
                i + 4,
                i + 5.5f,
                i + 6.5,
                new LocalDate(2022, 12, i + 1),
                new LocalTime(11, 38, i + 1),
                new LocalDateTime(2022, 12, 19, 11, i + 1),
                Instant.FromUnixTimeSeconds(i + 1),
                new byte[] { 1, 2 },
                i + 7.7m,
                new Guid(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, (byte)(i + 1)),
                i % 2 == 0);

            await PocoAllColumnsSqlNullableView.UpsertAsync(null, pocoAllColumns);
        }

        await PocoAllColumnsSqlNullableView.UpsertAsync(null, new PocoAllColumnsSqlNullable(100));
    }

    [OneTimeTearDown]
    public async Task CleanTables()
    {
        await TupleView.DeleteAllAsync(null, Enumerable.Range(0, Count).Select(x => GetTuple(x)));
        await PocoIntView.DeleteAllAsync(null, Enumerable.Range(0, Count).Select(x => new PocoInt(x, 0)));
    }

    [Test]
    public void TestSelectOneColumn()
    {
        var query = PocoView.AsQueryable()
            .Where(x => x.Key == 3)
            .Select(x => x.Val);

        string?[] res = query.ToArray();

        CollectionAssert.AreEqual(new[] { "v-3" }, res);
    }

    [Test]
    public void TestSelectOneColumnSingle()
    {
        var res = PocoView.AsQueryable()
            .Where(x => x.Key == 3)
            .Select(x => x.Val)
            .Single();

        Assert.AreEqual("v-3", res);
    }

    [Test]
    public void TestSelectOneColumnSingleWithMultipleRowsThrows()
    {
        // ReSharper disable once ReturnValueOfPureMethodIsNotUsed
        var ex = Assert.Throws<InvalidOperationException>(
            () => PocoView.AsQueryable()
            .Select(x => x.Val)
            .Single());

        const string expected = "ResultSet is expected to have one row, but has more: " +
                                "select _T0.VAL from PUBLIC.TBL1 as _T0 limit 2";

        Assert.AreEqual(expected, ex!.Message);
    }

    [Test]
    public void TestSelectOneColumnFirst()
    {
        var res = PocoView.AsQueryable()
            .OrderBy(x => x.Key)
            .Select(x => x.Val)
            .First();

        Assert.AreEqual("v-0", res);
    }

    [Test]
    public async Task TestSelectOneColumnAsResultSet()
    {
        var query = PocoView.AsQueryable()
            .Where(x => x.Key == 3)
            .Select(x => x.Val);

        await using IResultSet<string?> resultSet = await query.ToResultSetAsync();
        List<string?> rows = await resultSet.ToListAsync();

        CollectionAssert.AreEqual(new[] { "v-3" }, rows);
        Assert.IsTrue(resultSet.HasRowSet);
        Assert.IsNotNull(resultSet.Metadata);
        Assert.AreEqual("VAL", resultSet.Metadata!.Columns.Single().Name);
    }

    [Test]
    public async Task TestSelectOneColumnAsAsyncEnumerable()
    {
        var query = PocoView.AsQueryable()
            .Where(x => x.Key == 3)
            .Select(x => x.Val);

        var count = 0;

        await foreach (var row in query.AsAsyncEnumerable())
        {
            Assert.AreEqual("v-3", row);
            count++;
        }

        Assert.AreEqual(1, count);
    }

    [Test]
    public void TestSelectEntireObject()
    {
        Poco[] res = PocoView.AsQueryable()
            .Where(x => x.Key == 3)
            .ToArray();

        Assert.AreEqual(1, res.Length);
        Assert.AreEqual(3, res[0].Key);
        Assert.AreEqual("v-3", res[0].Val);
    }

    [Test]
    public void TestSelectEntireRecordObject()
    {
        PocoInt res = PocoIntView.AsQueryable().Single(x => x.Key == 3);

        Assert.AreEqual(3, res.Key);
        Assert.AreEqual(300, res.Val);
    }

    [Test]
    public void TestSelectTwoColumns()
    {
        var res = PocoView.AsQueryable()
            .Where(x => x.Key == 2)
            .Select(x => new { x.Key, x.Val })
            .ToArray();

        Assert.AreEqual(1, res.Length);
        Assert.AreEqual(2, res[0].Key);
        Assert.AreEqual("v-2", res[0].Val);
    }

    [Test]
    public void TestSelectComputedColumnIntoAnonymousType()
    {
        var res = PocoView.AsQueryable()
            .Where(x => x.Key == 7)
            .Select(x => new { x.Key, x.Val, Key2 = x.Key + 1 })
            .ToArray();

        Assert.AreEqual(1, res.Length);
        Assert.AreEqual(7, res[0].Key);
        Assert.AreEqual(8, res[0].Key2);
        Assert.AreEqual("v-7", res[0].Val);
    }

    [Test]
    public void TestSelectComputedColumnIntoPoco()
    {
        var res = PocoView.AsQueryable()
            .Where(x => x.Key == 3)
            .Select(x => new Poco { Val = x.Val, Key = x.Key - 1 })
            .ToArray();

        Assert.AreEqual(1, res.Length);
        Assert.AreEqual(2, res[0].Key);
        Assert.AreEqual("v-3", res[0].Val);
    }

    [Test]
    public void TestSkip()
    {
        var query = PocoView.AsQueryable()
            .OrderBy(x => x.Key)
            .Select(x => x.Key)
            .Skip(7);

        List<long> res = query.ToList();

        Assert.AreEqual(new long[] { 7, 8, 9 }, res);

        StringAssert.Contains(
            "select _T0.KEY from PUBLIC.TBL1 as _T0 " +
            "order by _T0.KEY asc " +
            "offset ?",
            query.ToString());
    }

    [Test]
    public void TestTake()
    {
        var query = PocoView.AsQueryable()
            .OrderBy(x => x.Key)
            .Take(2);

        List<Poco> res = query.ToList();

        Assert.AreEqual(new long[] { 0, 1 }, res.Select(x => x.Key));

        StringAssert.Contains(
            "select _T0.KEY, _T0.VAL from PUBLIC.TBL1 as _T0 " +
            "order by _T0.KEY asc " +
            "limit ?",
            query.ToString());
    }

    [Test]
    public void TestOrderBySkipTake()
    {
        var query = PocoView.AsQueryable()
            .OrderByDescending(x => x.Key)
            .Select(x => x.Key)
            .Skip(1)
            .Take(2);

        List<long> res = query.ToList();

        Assert.AreEqual(new long[] { 8, 7 }, res);

        StringAssert.Contains(
            "select _T0.KEY from PUBLIC.TBL1 as _T0 " +
            "order by _T0.KEY desc " +
            "limit ? offset ?",
            query.ToString());
    }

    [Test]
    public void TestSkipTakeFirst()
    {
        var query = PocoView.AsQueryable()
            .OrderByDescending(x => x.Key)
            .Select(x => x.Key)
            .Skip(1)
            .Take(2);

        long res = query.First();

        Assert.AreEqual(8, res);

        StringAssert.Contains(
            "select _T0.KEY from PUBLIC.TBL1 as _T0 " +
            "order by _T0.KEY desc " +
            "limit ? offset ?",
            query.ToString());
    }

    [Test]
    public void TestOrderBySkipTakeBeforeSelect()
    {
        var query = PocoView.AsQueryable()
            .OrderByDescending(x => x.Key)
            .Skip(1)
            .Take(2)
            .Select(x => x.Key);

        List<long> res = query.ToList();

        Assert.AreEqual(new long[] { 8, 7 }, res);

        StringAssert.Contains(
            "select _T0.KEY from (" +
            "select * from PUBLIC.TBL1 as _T1 " +
            "order by _T1.KEY desc " +
            "limit ? offset ?) as _T0",
            query.ToString());
    }

    [Test]
    public void TestOrderByMultiple()
    {
        var query = PocoAllColumnsSqlView.AsQueryable()
            .Where(x => x.Key < 10)
            .OrderBy(x => x.Key)
            .ThenByDescending(x => x.Int8)
            .ThenBy(x => x.Int16)
            .Select(x => new { x.Decimal, x.Double });

        var res = query.ToList();

        Assert.AreEqual(6.5d, res[0].Double);
        Assert.AreEqual(7.7m, res[0].Decimal);

        StringAssert.Contains(
            "select _T0.\"DECIMAL\", _T0.\"DOUBLE\" " +
            "from PUBLIC.TBL_ALL_COLUMNS_SQL as _T0 " +
            "where (_T0.KEY < ?) " +
            "order by _T0.KEY asc, _T0.INT8 desc, _T0.INT16 asc",
            query.ToString());
    }

    [Test]
    public void TestContains()
    {
        var keys = new long[] { 4, 2 };

        var query = PocoView.AsQueryable()
            .Where(x => keys.Contains(x.Key))
            .Select(x => x.Val);

        List<string?> res = query.ToList();

        CollectionAssert.AreEquivalent(new[] { "v-2", "v-4" }, res);

        StringAssert.Contains(
            "select _T0.VAL from PUBLIC.TBL1 as _T0 where (_T0.KEY IN (?, ?)), Parameters = [ 4, 2 ]",
            query.ToString());
    }

    [Test]
    public void TestDistinctOneField()
    {
        var query = PocoByteView.AsQueryable()
            .Select(x => x.Val)
            .Distinct();

        List<sbyte?> res = query.ToList();

        CollectionAssert.AreEquivalent(new[] { 0, 1, 2, 3 }, res);

        StringAssert.Contains("select distinct _T0.VAL from PUBLIC.TBL_INT8 as _T0", query.ToString());
    }

    [Test]
    public void TestDistinctEntireObject()
    {
        var query = PocoByteView.AsQueryable()
            .Distinct();

        List<PocoByte> res = query.ToList();

        Assert.AreEqual(10, res.Count);

        StringAssert.Contains("select distinct _T0.KEY, _T0.VAL from PUBLIC.TBL_INT8 as _T0", query.ToString());
    }

    [Test]
    public void TestDistinctProjection()
    {
        var query = PocoByteView.AsQueryable()
            .Select(x => new { Id = x.Val + 10, V = x.Val })
            .Distinct();

        var res = query.ToList();

        Assert.AreEqual(4, res.Count);

        StringAssert.Contains(
            "select distinct (cast(_T0.VAL as int) + ?) as ID, _T0.VAL " +
            "from PUBLIC.TBL_INT8 as _T0",
            query.ToString());
    }

    [Test]
    public void TestDistinctAfterOrderBy()
    {
        var query = PocoByteView.AsQueryable()
            .Select(x => new { Id = x.Val + 10, V = x.Val })
            .OrderByDescending(x => x.V)
            .Distinct();

        var res = query.ToList();

        Assert.AreEqual(4, res.Count);
        Assert.AreEqual(13, res[0].Id);

        StringAssert.Contains(
            "select distinct (cast(_T0.VAL as int) + ?) as ID, _T0.VAL " +
            "from PUBLIC.TBL_INT8 as _T0 " +
            "order by _T0.VAL desc",
            query.ToString());
    }

    [Test]
    public void TestDistinctBeforeOrderBy()
    {
        var query = PocoByteView.AsQueryable()
            .Select(x => new { Id = x.Val + 10, V = x.Val })
            .Distinct()
            .OrderByDescending(x => x.Id);

        var res = query.ToList();

        Assert.AreEqual(4, res.Count);
        Assert.AreEqual(13, res[0].Id);

        StringAssert.Contains(
            "select * from " +
            "(select distinct (cast(_T0.VAL as int) + ?) as ID, _T0.VAL from PUBLIC.TBL_INT8 as _T0) as _T1 " +
            "order by _T1.ID desc",
            query.ToString());
    }

    [Test]
    public void TestCustomColumnNameMapping()
    {
        var res = Table.GetRecordView<PocoCustomNames>().AsQueryable()
            .Select(x => new { Key = x.Id, Val = x.Name })
            .Where(x => x.Key == 3)
            .ToArray();

        Assert.AreEqual(1, res.Length);
        Assert.AreEqual(3, res[0].Key);
        Assert.AreEqual("v-3", res[0].Val);
    }

    [Test]
    public async Task TestTransactionIsPropagatedToServer()
    {
        using var server = new FakeServer();
        using var client = await server.ConnectClientAsync();

        var tx = await client.Transactions.BeginAsync();
        var tbl = await client.Tables.GetTableAsync(FakeServer.ExistingTableName);
        var pocoView = tbl!.GetRecordView<PocoInt>();

        _ = pocoView.AsQueryable(tx).Select(x => x.Key).ToArray();

        Assert.AreEqual(0, server.LastSqlTxId);
    }

    [Test]
    public void TestEnumeration()
    {
        var query = PocoView.AsQueryable(options: new QueryableOptions(PageSize: 2)).OrderBy(x => x.Key);
        int count = 0;

        foreach (var poco in query)
        {
            Assert.AreEqual(count++, poco.Key);
        }

        Assert.AreEqual(10, count);
    }

    [Test]
    public void TestSelectDecimalIntoAnonymousTypeUsesCorrectScale()
    {
        var query = PocoDecimalView.AsQueryable()
            .OrderByDescending(x => x.Val)
            .Select(x => new
            {
                Id = x.Key
            });

        var res = query.ToList();
        Assert.AreEqual(9.0m, res[0].Id);
    }

    [Test]
    public void TestSelectDecimalIntoUserDefinedTypeUsesCorrectScale()
    {
        var query = PocoDecimalView.AsQueryable()
            .OrderByDescending(x => x.Val)
            .Select(x => new PocoDecimal(x.Key, x.Val));

        var res = query.ToList();
        Assert.AreEqual(9.0m, res[0].Val);
    }

    [Test]
    public void TestSelectAllColumnTypes()
    {
        var res = PocoAllColumnsSqlView.AsQueryable()
            .OrderBy(x => x.Key)
            .Take(3)
            .ToList();

        Assert.AreEqual(0, res[0].Key);
        Assert.AreEqual(1, res[0].Int8);
        Assert.AreEqual(2, res[0].Int16);
        Assert.AreEqual(3, res[0].Int32);
        Assert.AreEqual(4, res[0].Int64);
        Assert.AreEqual(5.5f, res[0].Float);
        Assert.AreEqual(6.5, res[0].Double);
        Assert.AreEqual(7.7, res[0].Decimal);
        Assert.AreEqual(new LocalDate(2022, 12, 1), res[0].Date);
        Assert.AreEqual(new LocalTime(11, 38, 1), res[0].Time);
        Assert.AreEqual(new LocalDateTime(2022, 12, 19, 11, 1), res[0].DateTime);
        Assert.AreEqual(Instant.FromUnixTimeSeconds(1), res[0].Timestamp);
        Assert.AreEqual(new byte[] { 1, 2 }, res[0].Blob);
        Assert.AreEqual(new Guid(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1), res[0].Uuid);
        Assert.IsTrue(res[0].Boolean);
    }

    [Test]
    public void TestSelectAllColumnTypesNullable()
    {
        var res = PocoAllColumnsSqlNullableView.AsQueryable()
            .OrderBy(x => x.Key)
            .ToList();

        Assert.AreEqual(0, res[0].Key);
        Assert.AreEqual(1, res[0].Int8);
        Assert.AreEqual(2, res[0].Int16);
        Assert.AreEqual(3, res[0].Int32);
        Assert.AreEqual(4, res[0].Int64);
        Assert.AreEqual(5.5f, res[0].Float);
        Assert.AreEqual(6.5, res[0].Double);
        Assert.AreEqual(7.7, res[0].Decimal);
        Assert.AreEqual(new LocalDate(2022, 12, 1), res[0].Date);
        Assert.AreEqual(new LocalTime(11, 38, 1), res[0].Time);
        Assert.AreEqual(new LocalDateTime(2022, 12, 19, 11, 1), res[0].DateTime);
        Assert.AreEqual(Instant.FromUnixTimeSeconds(1), res[0].Timestamp);
        Assert.AreEqual(new byte[] { 1, 2 }, res[0].Blob);
        Assert.AreEqual(new Guid(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1), res[0].Uuid);
        Assert.IsTrue(res[0].Boolean);
    }

    [Test]
    public void TestSelectAllColumnTypesNullableNull()
    {
        var res = PocoAllColumnsSqlNullableView.AsQueryable()
            .OrderByDescending(x => x.Key)
            .ToList();

        Assert.AreEqual(100, res[0].Key);
        Assert.IsNull(res[0].Str);
        Assert.IsNull(res[0].Int8);
        Assert.IsNull(res[0].Int16);
        Assert.IsNull(res[0].Int32);
        Assert.IsNull(res[0].Int64);
        Assert.IsNull(res[0].Float);
        Assert.IsNull(res[0].Double);
        Assert.IsNull(res[0].Decimal);
        Assert.IsNull(res[0].Date);
        Assert.IsNull(res[0].Time);
        Assert.IsNull(res[0].DateTime);
        Assert.IsNull(res[0].Timestamp);
        Assert.IsNull(res[0].Blob);
        Assert.IsNull(res[0].Uuid);
        Assert.IsNull(res[0].Boolean);
    }

    [Test]
    public void TestWhereNull()
    {
        var query = PocoAllColumnsSqlNullableView.AsQueryable()
            .Where(x => x.Int8 == null);

        StringAssert.Contains("where (_T0.INT8 IS NOT DISTINCT FROM ?)", query.ToString());

        var res = query.ToList();

        Assert.AreEqual(100, res[0].Key);
        Assert.AreEqual(1, res.Count);
    }

    [Test]
    public void TestWhereNotNull()
    {
        var query = PocoAllColumnsSqlNullableView.AsQueryable()
            .Where(x => x.Int8 != null)
            .OrderBy(x => x.Key);

        StringAssert.Contains("where (_T0.INT8 IS DISTINCT FROM ?)", query.ToString());

        var res = query.ToList();

        Assert.AreEqual(0, res[0].Key);
        Assert.AreEqual(10, res.Count);
    }

    [Test]
    public void TestFilterAndSelectEnumColumn()
    {
        var query = PocoIntEnumView.AsQueryable()
            .Where(x => x.Val == TestEnum.B)
            .Select(x => new { x.Key, Res = x.Val });

        StringAssert.Contains(
            "select _T0.KEY, _T0.VAL from PUBLIC.TBL_INT32 as _T0 where (cast(_T0.VAL as int) IS NOT DISTINCT FROM ?), Parameters = [ 300 ]",
            query.ToString());

        var res = query.ToList();
        var resEnum = res[0].Res;

        Assert.AreEqual(3, res[0].Key);
        Assert.AreEqual(TestEnum.B, resEnum);
        Assert.AreEqual(1, res.Count);
    }

    [Test]
    [SuppressMessage("Globalization", "CA1311:Specify a culture or use an invariant version", Justification = "SQL")]
    [SuppressMessage("Performance", "CA1862:Use the \'StringComparison\' method overloads to perform case-insensitive string comparisons", Justification = "SQL")]
    public async Task TestGeneratedSqlIsLoggedWithDebugLevel()
    {
        var config = GetConfig();
        var logger = new ListLoggerFactory(new[] { LogLevel.Debug });
        config.LoggerFactory = logger;

        using var client = await IgniteClient.StartAsync(config);
        var table = (await client.Tables.GetTableAsync(TableName))!;
        var view = table.GetRecordView<Poco>();

        _ = await view.AsQueryable(null, new QueryableOptions { Timeout = TimeSpan.FromSeconds(15), PageSize = 123 })
            .Where(x => x.Key > 5 && x.Val!.ToUpper() == "abc")
            .SumAsync(x => x.Key);

        var logEntry = logger.Entries.Single(x => x.Category == "Apache.Ignite.Internal.Linq.IgniteQueryExecutor");

        var expectedMessage =
            "Executing SQL statement generated by LINQ provider [statement=SqlStatement { " +
            "Query = select sum(_T0.KEY) from PUBLIC.TBL1 as _T0 where ((_T0.KEY > ?) and (upper(_T0.VAL) IS NOT DISTINCT FROM ?)), " +
            "Timeout = 00:00:15, " +
            "Schema = PUBLIC, " +
            "PageSize = 123, " +
            "Properties = { } }, " +
            "parameters=5, abc]";

        Assert.AreEqual(expectedMessage, logEntry.Message);
        Assert.AreEqual(LogLevel.Debug, logEntry.Level);
        Assert.IsNull(logEntry.Exception);
    }

    private enum TestEnum
    {
        None = 0,
        A = 100,
        B = 300
    }

    private record PocoByte(sbyte Key, sbyte? Val);

    private record PocoShort(short Key, short? Val);

    private record PocoInt(int Key, int? Val);

    private record PocoLong(long Key, long? Val);

    private record PocoFloat(float Key, float? Val);

    private record PocoDouble(double Key, double? Val);

    private record PocoDecimal(decimal Key, decimal? Val);

    private record PocoBigDecimal(BigDecimal Key, BigDecimal? Val);

    private record PocoString(string Key, string? Val);

    private record PocoDate(LocalDate Key, LocalDate? Val);

    private record PocoTime(LocalTime Key, LocalTime? Val);

    private record PocoDateTime(LocalDateTime Key, LocalDateTime? Val);

    private record PocoIntEnum(int Key, TestEnum? Val);
}
