Linq bug?
От: Ziaw Россия  
Дата: 02.06.10 08:42
Оценка: 58 (1)
Не могу понять где баг — в Nemerle.Data.Linq или BLToolkit.

Прходит простой такой тест:
   using (db = Db())
   {
        def id1 = System.Guid("005B55CD-C6E9-433F-8983-74E53465714E");
        def id2 = System.Guid("7017AC86-450E-4B0D-987C-844B4EF7ACBA");

        def single1 = db.Pages.Single(t => t.Id == id1);
        def single2 = db.Pages.Single(t => t.Id == id2);

        Expect(single1.Id == single2.Id); // == 005B55CD-C6E9-433F-8983-74E53465714E
   }


Структура базы не имеет значения кроме того, что Id имеет тип Guid.

Фишка в том, что тулкит кеширует экспрешены. В C# id1 и id2 превращаются в замыкания. В немерле в константы. Тулкит по каким-то своим причинам не различает константы типа Guid (IsConstant для Guid возвращает false). Поэтому он считает экспрешены эквивалентными и использует запрос сгенеренный для первой выборки во второй.

Прежде чем постить баг в тулкит хотелось бы понять, правильно ли ведет себе генератор эеспрешенов в Nemerle.Linq.
Re: Linq bug?
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.06.10 16:05
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Не могу понять где баг — в Nemerle.Data.Linq или BLToolkit.


Z>Прходит простой такой тест:

Z>
Z>   using (db = Db())
Z>   {
Z>        def id1 = System.Guid("005B55CD-C6E9-433F-8983-74E53465714E");
Z>        def id2 = System.Guid("7017AC86-450E-4B0D-987C-844B4EF7ACBA");

Z>        def single1 = db.Pages.Single(t => t.Id == id1);
Z>        def single2 = db.Pages.Single(t => t.Id == id2);

Z>        Expect(single1.Id == single2.Id); // == 005B55CD-C6E9-433F-8983-74E53465714E
Z>   }
Z>


Пришли описание класса Page. Так чтобы можно было воспроизвести ошибку без обращения к БД.

Ну, или как-то по другому сделай так чтобы можно было воспроизвести ошибку на чужой машине.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Linq bug?
От: Ziaw Россия  
Дата: 02.06.10 16:53
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Ну, или как-то по другому сделай так чтобы можно было воспроизвести ошибку на чужой машине.


using System;
using System.Console;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using Nemerle.Data.Linq;
using BLToolkit.Data;
using BLToolkit.Data.Linq;

public class Data
{
   public Id : Guid {get;set;}
}
    
module Program
{
    Main() : void
    {
        def toExpr(e : Expression[Func[Data, bool]]) { e }
        
        def id1 = Guid.NewGuid();
        def id2 = Guid.NewGuid();
        
        def e1 = toExpr(d => d.Id == id1);
        def e2 = toExpr(d => d.Id == id2);
        
        // корень проблемы:
        WriteLine($"Тулкит думает, что $e1 == $e2");
        // продемонстрировать напрямую не получается, потому, что метод сравнения в internal классе
        // ExpressionHelper.Compare(e1, e2) вернет true, поскольку различаются они только значением константы, 
        // а тулкит по каким-то причинам не сравнивает значения констант для Guid (скорее всего такая ситуация не встречается в C#)
        
        // воспроизведение:
        // Нужен sqlserver и база в которой запущено create table Data (Id uniqueidentifier)
        DbTest(@"data source=.\SQLEXPRESS;Integrated Security=true;database=TestDb");
    }

    DbTest(connString : string) : void
    {
        using (conn = SqlConnection(connString), db = DbManager(conn))
        {
            _ = db.BeginTransaction(); // rollback
            
            def d1 = Data(); d1.Id = Guid.NewGuid();
            def d2 = Data(); d2.Id = Guid.NewGuid();
            
            _ = db.Insert(d1);
            _ = db.Insert(d2);
            
            def id1 = d1.Id; // если в експрешен отдать d1.Id константы не будет и тест пройдет
            def id2 = d2.Id;
            
            _ =  db.GetTable.[Data]().Single(d => d.Id == id1);
            // вот тут тулкит считает, что ему передали тот же самый экспрешен и выполняет закешированный запрос
            def test = db.GetTable.[Data]().Single(d => d.Id == id2); 
            
            if (test.Id == d2.Id) // test.Id в данном случае будет равен d1.Id
                WriteLine("Ok")
            else
                WriteLine("Error");
        }
    }
}
Re[3]: Linq bug?
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.06.10 17:12
Оценка:
Здравствуйте, Ziaw, Вы писали:

VD>>Ну, или как-то по другому сделай так чтобы можно было воспроизвести ошибку на чужой машине.


Z>
Z>        DbTest(@"data source=.\SQLEXPRESS;Integrated Security=true;database=TestDb");
Z>


Я же говорю. Мне бы без БД. Ну, или тогда нужно как-то эту БД мне создать. Тогда вопрос — как?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Linq bug?
От: Ziaw Россия  
Дата: 02.06.10 17:38
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>>>Ну, или как-то по другому сделай так чтобы можно было воспроизвести ошибку на чужой машине.


Z>>
Z>>        DbTest(@"data source=.\SQLEXPRESS;Integrated Security=true;database=TestDb");
Z>>


VD>Я же говорю. Мне бы без БД. Ну, или тогда нужно как-то эту БД мне создать. Тогда вопрос — как?


Без БД никак Я же написал, что метод сравнения експрешенов в тулките интернал. Поэтому проявить баг можно только в боевом режиме.

Со студией идет sqlexpress. Если он стоит надо сделать так:
Создаешь файл db.sql

create database TestDb
go
use TestDb
go
create table Data (Id uniqueidentifier)
go


и запускаешь sqlcmd (лежит в папке %ProgramFiles%\Microsoft SQL Server\{version}\Tools\Binn) с параметрами

SQLCMD.EXE -E -S .\sqlexpress -i db.sql
Re[5]: Linq bug?
От: seregaa Ниоткуда http://blogtani.ru
Дата: 02.06.10 18:31
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Без БД никак Я же написал, что метод сравнения експрешенов в тулките интернал. Поэтому проявить баг можно только в боевом режиме.

А нельзя вместо SqlConnection подсунуть фейковый провайдер? ну или провайдер, использующий csv файлы?
Мобильная версия сайта RSDN — http://rsdn.org/forum/rsdn/6938747
Автор: sergeya
Дата: 19.10.17
Re[5]: Linq bug?
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.06.10 18:37
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>Без БД никак Я же написал, что метод сравнения експрешенов в тулките интернал. Поэтому проявить баг можно только в боевом режиме.


ОК. Попробуй в своем примере сделать следующий изврат...

Создай левый класс. Скажем:

class Fake
{
  public GuidValue : Guid { get; set;  }
}

и далее:
def fake = Fake();

fake.GuidValue = Guid.NewGuid();
def e1 = toExpr(d => d.Id == fake.GuidValue);

fake.GuidValue = Guid.NewGuid();
def e2 = toExpr(d => d.Id == fake.GuidValue);

...


Если это сработает, то в принципе ясно в чем причина.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Linq bug?
От: Ziaw Россия  
Дата: 02.06.10 18:49
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Если это сработает, то в принципе ясно в чем причина.


Сработает.
Причина мне была ясна до написания первого поста. Просто я видимо ее плохо описал. Попробую еще раз:


Вопрос что лучше править, тулкит или Nemerle.Linq?
Re[6]: Linq bug?
От: Ziaw Россия  
Дата: 02.06.10 18:52
Оценка:
Здравствуйте, seregaa, Вы писали:

Z>>Без БД никак Я же написал, что метод сравнения експрешенов в тулките интернал. Поэтому проявить баг можно только в боевом режиме.

S>А нельзя вместо SqlConnection подсунуть фейковый провайдер? ну или провайдер, использующий csv файлы?

Тулкит умеет только с определенным списком СУБД работать. Генерация запросов сильно завязана на конкретные диалекты SQL.
Re[7]: Linq bug?
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.06.10 19:37
Оценка:
Здравствуйте, Ziaw, Вы писали:

Z>
  • Nemerle.Linq при генерации экспрешенов превращает локальные переменные в Expression.Constant (возможно в частных случаях), а C# генерит замыкания.

    Ага. Проблема в том, что макросы не могут управлять такими тонкими вещами как замыканиями. (Возможно я просто пока что не не знаю как это реализовать.)

    Z>
  • Тулкит почему-то считает две константы одинаковыми если их тип не входит в некий перечень типов, Guid туда не входит. Поэтому два экспрешена, различающиеся только значением константы, он тоже считает одинаковыми.

    Я говорил с IT по этому поводу. Это он обещал поправить.

    Z>
  • Перед созданием запроса из экспрешена тулкит проверяет, не лежит ли в кеше запрос из такого же экспрешена. Если лежит он его использует, проблема в том, что он считает одинаковыми реально отличающиеся экспрешены.

    Это его баг, но конечно по уму и Nemerle.Linq нужно научить делать замыкания и ссылаться на его поля. Проблема в том, что это не штатное использование макросов, а значит сложно. Возможно что-то и получится если немного докрутить компилятор.

    Возможно получится сделать некий АПИ для работы с переменными которые в последствии могут превратиться в замыкания.
    К сожалению преобразование функций в замыкания производится на поздних стадиях компиляции. Когда отрабатывают макросы никакой информации о замыканиях еще нет. Кроме того не ясно как сослаться на объект замыкания. Каким-то образом нужно уметь получить ссылку (this) на объект замыкания, чтобы породить примерно такой код (псевдокод):
            | PExpr.Ref(nm) as expr => 
              match (lparms.Filter(_.ContainsKey(nm.Id)))
              {
                | hd :: _ => hd[nm.Id] // это параметр лямбды!
                | _       => 
    
                  // Сейчас здесь вот такой код:
                  // <[ Expression.Constant($(nm : name)) ]>
                  // А нужно что-то вроде этого:
                  def inst = PExpr.Typed(TExpr.This());
    
                  match (expr.TypedObject)
                  {
                    | TExpr.LocalRef(decl) =>
                      def tField = ???;
                      <[ Expression.Field($inst, $tField : FieldInfo) ]>
    
                    | _ => сообщаем об ошибке
                  }
              }


    Z>Вопрос что лучше править, тулкит или Nemerle.Linq?


    И то, и то.
  • Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re: Linq bug?
    От: IT Россия linq2db.com
    Дата: 03.06.10 03:32
    Оценка: 16 (1)
    Здравствуйте, Ziaw, Вы писали:

    Z>Прежде чем постить баг в тулкит хотелось бы понять, правильно ли ведет себе генератор эеспрешенов в Nemerle.Linq.


    Ведёт себя неправильно. Но баг в тулкит всё равно постить надо (было). В булките уже пофикшено, в компиляторе всё гораздо сложнее.
    Если нам не помогут, то мы тоже никого не пощадим.
    Re[8]: Linq bug?
    От: Ziaw Россия  
    Дата: 03.06.10 03:59
    Оценка:
    Здравствуйте, VladD2, Вы писали:

    VD>Я говорил с IT по этому поводу. Это он обещал поправить.


    Гут. Я у себя подправил, но форкать (тем более svn) не хотелось бы

    VD>Это его баг, но конечно по уму и Nemerle.Linq нужно научить делать замыкания и ссылаться на его поля. Проблема в том, что это не штатное использование макросов, а значит сложно. Возможно что-то и получится если немного докрутить компилятор.


    VD>Возможно получится сделать некий АПИ для работы с переменными которые в последствии могут превратиться в замыкания.

    VD>К сожалению преобразование функций в замыкания производится на поздних стадиях компиляции. Когда отрабатывают макросы никакой информации о замыканиях еще нет. Кроме того не ясно как сослаться на объект замыкания. Каким-то образом нужно уметь получить ссылку (this) на объект замыкания, чтобы породить примерно такой код (псевдокод):

    Тут вообще на первый взгляд все как-то сложно:

    mutable n = 1;
    toExpr(_ => n > 0);
    n++;

    должно превратиться во что-то типа
    def closure = new (n = 1);
    closure.n = 1;
    Expression.Lambda(... Expression.MemberAccess(closure, "n") ...) // API не помню, но принцип такой
    closure.n++;


    непонятно что делать в более сложных случаях:
    mutable n = 1;
    mutable m = 2;
    toExpr(_ => n > 0);
    toExpr(_ => n > 0 && m > 0);
    toExpr(_ => m > 0);
    {
      mutable m = 3;
      toExpr(_ => n > 0 && m > 0);
      toExpr(_ => m > 0);
      toExpr(_ => m > 0);
    }
    n++;
    m++;


    Но шарп такие проблемы как-то решает, надо смотреть как.
    Re[2]: Linq bug?
    От: Ziaw Россия  
    Дата: 03.06.10 04:05
    Оценка:
    Здравствуйте, IT, Вы писали:

    IT>Ведёт себя неправильно.


    Кстати, вопрос скорее философский. Почему генерация констант из иммутабельных переменных является неверным поведением? Для конкретного экспрешена это будет константа. А для мутабельных да, нужны замыкания.
    Re[3]: Linq bug?
    От: IT Россия linq2db.com
    Дата: 03.06.10 04:23
    Оценка:
    Здравствуйте, Ziaw, Вы писали:

    IT>>Ведёт себя неправильно.


    Z>Кстати, вопрос скорее философский. Почему генерация констант из иммутабельных переменных является неверным поведением? Для конкретного экспрешена это будет константа. А для мутабельных да, нужны замыкания.


    Может компилятор наоптимизировал?
    Если нам не помогут, то мы тоже никого не пощадим.
    Re[4]: Linq bug?
    От: Ziaw Россия  
    Дата: 03.06.10 04:31
    Оценка:
    Здравствуйте, IT, Вы писали:

    IT>>>Ведёт себя неправильно.


    Z>>Кстати, вопрос скорее философский. Почему генерация констант из иммутабельных переменных является неверным поведением? Для конкретного экспрешена это будет константа. А для мутабельных да, нужны замыкания.


    IT>Может компилятор наоптимизировал?


    Ответ не понял. Вопрос был почему вообще генерация Expression.Constant считается (считается ли) неверным поведением если они генерятся из иммутабельных переменных?
    Re[5]: Linq bug?
    От: IT Россия linq2db.com
    Дата: 03.06.10 05:07
    Оценка:
    Здравствуйте, Ziaw, Вы писали:

    IT>>>>Ведёт себя неправильно.

    Z>>>Кстати, вопрос скорее философский. Почему генерация констант из иммутабельных переменных является неверным поведением? Для конкретного экспрешена это будет константа. А для мутабельных да, нужны замыкания.
    IT>>Может компилятор наоптимизировал?
    Z>Ответ не понял. Вопрос был почему вообще генерация Expression.Constant считается (считается ли) неверным поведением если они генерятся из иммутабельных переменных?

    Тогда я не понял к кому вопрос
    Если нам не помогут, то мы тоже никого не пощадим.
    Re[6]: Linq bug?
    От: Ziaw Россия  
    Дата: 03.06.10 05:15
    Оценка:
    Здравствуйте, IT, Вы писали:

    Z>>Ответ не понял. Вопрос был почему вообще генерация Expression.Constant считается (считается ли) неверным поведением если они генерятся из иммутабельных переменных?


    IT>Тогда я не понял к кому вопрос


    здесь
    Автор: IT
    Дата: 03.06.10
    ты сказал, что компилятор ведет себя неправильно. Если это относится к общему неумению генерить замыкания в экспрешенах — вопрос снимается. В примере же константы вместо замыканий мне кажутся вполне логичным поведением.
    Re[9]: Linq bug?
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 03.06.10 12:16
    Оценка:
    Здравствуйте, Ziaw, Вы писали:

    Z>
    Z>mutable n = 1;
    Z>toExpr(_ => n > 0);
    Z>n++;
    Z>

    Z>должно превратиться во что-то типа
    Z>
    Z>def closure = new (n = 1);
    Z>closure.n = 1;
    Z>Expression.Lambda(... Expression.MemberAccess(closure, "n") ...) // API не помню, но принцип такой
    Z>closure.n++;
    Z>


    Ага. Почти так. Только имена будут не такими красивыми, а чем-то вроде
    Expression.Lambda(... Expression.MemberAccess(._N_Main_clo_6537, "_N_id1_6520") ...)

    И конечно же лучше имя поля не строкой задавать, а через TExpr.FieldOf().

    Вот только макрос работают намного раньше формирования замыканий и соответствующего переписывания кода.

    Я вчера пол ночи сидел и смотрел что можно сделать чтобы это обойти, но так ничего и не сделал.
    В голову приходит только одно решение — добавить два новых TExpr-шона:
    | TExpr.Closure
    | TExpr.ClosureвFieldOf { localValue : LocalValue }

    Для них придется добавить обработку на поздних стадиях компиляции и генерацию кода. Это не так-то просто.
    Но по другому никак (по крайней мере я не вижу как).
    Попробую в ближайшее время реализовать. Механизм в общем-то универсальный. Может и в других местах пригодиться.

    Z>Но шарп такие проблемы как-то решает, надо смотреть как.


    Примерно так как ты описал.
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Re[7]: Linq bug?
    От: IT Россия linq2db.com
    Дата: 03.06.10 13:31
    Оценка: 8 (1)
    Здравствуйте, Ziaw, Вы писали:

    Z>здесь
    Автор: IT
    Дата: 03.06.10
    ты сказал, что компилятор ведет себя неправильно. Если это относится к общему неумению генерить замыкания в экспрешенах — вопрос снимается. В примере же константы вместо замыканий мне кажутся вполне логичным поведением.


    Guid в CLR в принципе нельзя объявить константой. Если какая-либо программа учитывает этот факт, то кто будет виноват — компилятор или эта программа?
    Если нам не помогут, то мы тоже никого не пощадим.
    Re: Linq bug?
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 03.06.10 22:54
    Оценка:
    Здравствуйте, Ziaw, Вы писали:

    Z>Фишка в том, что тулкит кеширует экспрешены. В C# id1 и id2 превращаются в замыкания. В немерле в константы. Тулкит по каким-то своим причинам не различает константы типа Guid (IsConstant для Guid возвращает false). Поэтому он считает экспрешены эквивалентными и использует запрос сгенеренный для первой выборки во второй.


    Реализовал поддержку замыканий в Linq-макросе. Так что теперь указанный код должен работать корректно даже со старым БЛТулкит-провайдором (по тестируй... только не забуть пересобрать компилятор и Nwmerle.Linq.dll).

    Кроме того теперь будут корректно работать множественное выполнение одного запроса с изменением значений переменных используемых внутри запроса.

    ЗЫ

    IT вроде как поправил и свой провайдер, так что бага вообще не должно появляться ни при каких условиях.
    Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.