Где в интернете можно обсудить dynamic sql automatic checking?

6yrop

InfoQ.com – хороший, годный ресурс.
stackoverflow.com – такие обсуждения не подходят под их правила.
habrahabr.ru – имхо, сильно гноберский ресурс. Из полезного там только перепечатка из иностранных ресурсов.
rsdn.ru – ну тоже своя не очень конструктивная атмосфера.
Социальные сети — я там не бываю. Стоит заглянуть? в какую?

SergeRRRRRR

sql.ru ?

hprt

а что ты хочешь от автоматических проверок? Если просто на валидность - посмотри опцию PARSEONLY (MSSQL в других субд тоже должно быть что-нибудь для этого

6yrop

да, я собственно и использую FMTONLY. Но вопрос в другом. В интеграции баз данных и "обычных" языков программирования. Как из source code-а получить полный список собранных запросов, чтобы потом воспользоваться FMTONLY?

hprt

В общем случае, наверное, так сделать не получится. Особенно, если как-нибудь сложно собираешь запрос.
Даже в таком простом случае это довольно сложно сделать, мне кажется
  
sql = "select a.*";

if (b)
sql += ", b.*";

sql += " from ";

if (c)
sql += "c as a";
else
sql += "a";

if (b)
sql += " inner join b on b.id = a.id ";

sql += " where 1 = 1";

if (d)
sql += " and a.type_id = 1";
// Добавь сюда еще пяток условий

Плюс, как ты собираешься обрабатывать результаты?
FMTONLY сломается, если ты работаешь с временными таблицами
PARSEONLY только анализирует синтаксис. Т.е. если у тебя, скажем, схема поменялась, проблем все равно не будет

6yrop

Если набор колонок не варьируется, то вот так

var b = bool.Parse(Console.ReadLine;
var c = bool.Parse(Console.ReadLine;
var d = bool.Parse(Console.ReadLine;
foreach (var record in QExecutor.MaterializeReader(
command => new {b, c, d},
p => {
var q = new Q(@"from ");

if (p.c)
q._("c as a");
else
q._("a");

if (p.b)
q._(" inner join b on b.id = a.id ");

q._(" where 1 = 1");

if (p.d)
q._(" and a.type_id = 1");

return q.Select(_ => new {
id = _._<int>("a."
type_id = _._<int>("a.")
});
}
Console.WriteLine(record.id + ", " + record.type_id);

Инструмент в автоматическом режиме переберет все варианты запросов. Понятно, что количество вариантов растет экспоненциально. Кстати, в C# компиляторе type inference тоже имеет экспоненциальную сложность.

6yrop

Если набор колонок варьируется, то либо надо применять C# dynamic, либо вот так

var b = bool.Parse(Console.ReadLine;
var c = bool.Parse(Console.ReadLine;
var d = bool.Parse(Console.ReadLine;
foreach (var record in QExecutor.MaterializeReader(
command => new {b, c, d},
p => BuildQuery(p.b, p.c, p.d).Select(_ => new {
id = _._<int>("a."
type_id = _._<int>("a.")
}
Console.WriteLine(record.id + ", " + record.type_id);

if (b)
foreach (var record in QExecutor.MaterializeReader(
command => new {c, d},
p => BuildQuery(true, p.c, p.d).Select(_ => new {
id = _._<int>("a."
type_id = _._<int>("a."
id_b = _._<int>("b.id as ")
}
Console.WriteLine(record.id + ", " + record.type_id + ", " + record.id_b);

static Q BuildQuery(bool b, bool c, bool d)
{
var q = new Q(@"from ");

if (c)
q._("c as a");
else
q._("a");

if (b)
q._(" inner join b on b.id = a.id "

q._(" where 1 = 1");

if (d)
q._(" and a.type_id = 1");
return q;
}

hprt

ну т.е. тебе на каждый метод, собирающий запрос придется писать метод, перебирающий варианты. что я и имел в виду - в общем случае сложно.

6yrop

FMTONLY сломается, если ты работаешь с временными таблицами
спасибо, да, кажется не работает. В MSSQL2012 они предлагают замену FMTONLY sp_describe_first_result_set
Но почему только first им что было жалко для всех сделать....

6yrop

придется писать метод, перебирающий варианты.
такой метод писать не надо. Код, который я запостил полностью самодостаточный, к нему ничего дописывать не надо. Валидация происходит полностью автоматически (я проверил).

hprt

Это простой случай, когда у тебя есть, условно, 5 переменных, которые влияют на возможный запрос. И то, тебе надо писать перебор значений, чтобы все посмотреть. Добавится новое условие - тебе придется метод переписывать. Это можно на NUnit сделать том же, даже ничего писать самому не надо.
А теперь представь, что у тебя динамика собирается из метаданных. Тоже все делается, но уже сложнее
По поводу sp_describe_first_result_set - думаю, сделали так, потому что у тебя резалтсетов может быть много, т.е. с этими параметрами дергаешь - один, с другими - два или три. Например, sp_help. Я, правда, не очень представляю, как они и один сделали - вроде бы значения параметров туда не передаешь, только тип. Хотя надо поставить сервер, посмотреть.

6yrop

Это простой случай, когда у тебя есть, условно, 5 переменных, которые влияют на возможный запрос. И то, тебе надо писать перебор значений, чтобы все посмотреть.
еще раз, кроме того, что я запостил, ничего писать не надо! Инструмент сам найдет запрос и провалидирует его.
Добавится новое условие - тебе придется метод переписывать.
какой метод ты думаешь надо будет переписывать?
Это можно на NUnit сделать том же, даже ничего писать самому не надо.

Классический подход через NUnit это выписывание всех вариантов вручную. И ни каких гарантий, что человек не ошибется. От этого я и избавлялся.

hprt

Я, может, тебя не понял сразу. Я думал, ты пишешь мегаинструмент, который позволяет пройтись по коду и составить все возможные запросы. Но теперь стало очевидно, что ты просто хочешь по конкретному методу построить все возможные запросы. Т.е., скажем, будет у тебя в поиске 10 параметров - ты так же будешь все 10 описывать, как сейчас описываешь b,c,d
В таком случае, поздравляю с изобретением велосипеда! На NUnit это делается с помощью Values, Range, Random. Во всяком случае, базу периодически тестирую с NUnit, и вполне хватает
Если я насчет NUnit неправ - подскажи, в чем могучее отличие твоего кода от тех же Values, а то я на дотнете лет 5 не писал уже, и мне не всегда очевидно, что происходит

6yrop

Если набор колонок варьируется, то либо надо применять C# dynamic, либо вот так
скорее всего через возврат результата аля pattern mattching можно что-то более симпатичное замутить, но пока мне вариация колонок не встречалась, поэтому отложим...

6yrop

Если я насчет NUnit неправ - подскажи, в чем могучее отличие твоего кода от тех же Values
в том что не надо писать Values :o . Более того тест на запрос не надо писать. Т.е. не пишем ничего.

hprt

Можешь объяснить, как ты перебираешь ВСЕ варианты? Тут у тебя три була, с ними все просто. А что с интами или строками?
например, сортировка

if (sort_by == "Amount" && dir == "asc")
sql += "order by Amount";

if (sort_by == "Amount" && dir == "desc")
sql += "order by Amount desc";

if (sort_by == "Date" && dir == "asc")
sql += "order by Date asc, Amount desc";

if (sort_by == "Date" && dir == "desc")
sql += "order by Date desc, Amount desc";

И потом, перебирать ВСЕ варианты - слишком накладно. Т.е., скажем, все условия where можно проверить разом

6yrop

например, сортировка
почему переменная sort_by не enum типа? Вроде, как раз для таких случаев перечисления и придумывались. Вот (почти твой пример) разработчик сайта stackoverflow.com для вариантов сортировки использует enum http://samsaffron.com/archive/2011/09/05/Digging+ourselves+o...
В ролике рассматривается более сложный случай. Но как отмечено в статье:
На практике в большинстве случаев оказывается достаточно булевских параметров, параметров типа перечисления и параметров типа IOption. Писать такие классы как RedSwitch требуется в небольшом проценте случаев.

И потом, перебирать ВСЕ варианты - слишком накладно.

до 12-14 параметров (2^12 вариантов запросов) это меньше секунды. Если параметров больше, то, да, приходится разбивать параметры на группы (у нас всего один такой случай).
Т.е., скажем, все условия where можно проверить разом

не совсем понятно что ты имеешь ввиду. Затачиваться на структуру sql запроса — это движение в сторону query object, query monad и т.п., таких решений десятки или сотни, и ни одного удовлетворительного.

6yrop

Более того тест на запрос не надо писать.
Даже более того, сам запрос не оформляется в виде элемента (метода который можно тестировать NUnit-ом — запрос это просто часть тела некоторого метода.

ava3443

из source code-а получить полный список собранных запросов
Если эти проверки требуют такого ужасного (нечитаемого) кода, как ты привёл выше, то может ну нах эти проверки? Имхо не стоит оно того. Жизнь одна, зачем её тратить на работу с таким кодом?

6yrop

все кто попробовал с таким кодом работать очень довольны и просят добавки перевести его под .NET 2.0, чтобы использовать в старых проектах

kokoc88

все кто попробовал с таким кодом работать очень довольны и просят добавки перевести его под .NET 2.0, чтобы использовать в старых проектах
Это они тебе в лицо говорят, сходи с ними хоть на одну пьянку... :smirk:

6yrop

Это они тебе в лицо говорят, сходи с ними хоть на одну пьянку...
регулярно устраиваем, и часто обсуждаем эти темы

6yrop

Если эти проверки требуют такого ужасного (нечитаемого) кода, как ты привёл выше, то может ну нах эти проверки? Имхо не стоит оно того. Жизнь одна, зачем её тратить на работу с таким кодом?
кстати, хорошим тоном будет, если ты запостишь свой вариант кода. Будет о чем говорить.

kokoc88

кстати, хорошим тоном будет, если ты запостишь свой вариант кода. Будет о чем говорить.
Тебе уже говорили: сравни это с ручной работой с ADO.NET

6yrop

Тебе уже говорили
а кода нет :smirk:

kokoc88

а кода нет
Вот именно: нет твоего кода, в котором ты показываешь, что код на твоём поделии короче и лучше хотя бы ADO.NET

6yrop

где взять эталонный код на ADO.NET?

kokoc88

где взять эталонный код на ADO.NET?
Никто не просит эталонный - ты напиши обычный и хороший, если ты на это способен...

kokoc88

регулярно устраиваем, и часто обсуждаем эти темы
Кстати, мне очень интересен ответ на один единственный вопрос: какой вывод о своём поделии и о себе ты делаешь из реакции посетителей этого раздела?

6yrop

ок, попробую, но шансов мало на конструктивное обсуждение ...

var modelCode = Console.ReadLine;

//чистый ADO.NET
using (var connection = new SqlConnection(QExecutor.ConnectionString
{
connection.Open;
using (var command = new SqlCommand
{
command.Connection = connection;
command.CommandText = "SELECT ModelId FROM Model WRE ModelCode = @ModelCode";
command.Parameters.Add(new SqlParameter("@ModelCode", SqlDbType.NVarChar) { Size = -1, Value = modelCode });
using (var reader = command.ExecuteReader
while (reader.Read
Console.WriteLineint)reader["ModelId"] + 1);
}
}

//ControllableQuery
using (var connection = new SqlConnection(QExecutor.ConnectionString
{
connection.Open;
using (var command = new SqlCommand
{
command.Connection = connection;
var query = Func.Invoke(
new { modelCode = command.Param(modelCode) },
p => new Q(@"FROM Model WRE ModelCode = ")._(p.modelCode).Select(_ => new {ModelId = _._<int>};
command.CommandText = query.CommandText;
using (var reader = command.ExecuteReader
while (reader.Read
Console.WriteLine(query.Materialize(reader).ModelId + 1);
}
}

В варианте “чистый ADO.NET” запрос без валидации, без find usages, без простого рефакторинга базы. В варианте ControllableQuery всё это УЖЕ присутствует, о чем свидетельствует зеленый кружок напротив запроса (40-я строка):

kokoc88

ок, попробую, но шансов мало на конструктивное обсуждение ...
Ага, тебе уже более сотни постов разные люди пытались аргументированно объяснить, что твоя либа гавно. Если ты никого не послушал тогда, то и сейчас будешь продолжать гнуть своё.
        command.Parameters.Add(new SqlParameter("@ModelCode", SqlDbType.NVarChar) { Size = -1, Value = modelCode });
Почему не AddWithValue?
command.CommandText = query.CommandText;
     using (var reader = command.ExecuteReader
     while (reader.Read
     Console.WriteLine(query.Materialize(reader).ModelId + 1);

Почему полностью не использовал свою библиотеку? У тебя во всех примерах более дикие вариации. Давай-ка пример побольше, с динамическими запросами, в общем такой, где у тебя появляется гавнокод.

kokoc88

без простого рефакторинга базы
Это, что ли, даёт простой рефакторинг базы?
id = _._<int>("a."
type_id = _._<int>("a."
id_b = _._<int>("b.id as ")

И как мне тут переименовать столбец базы и алиас таблицы в запросе?
Сделай-ка нам скринкаст того, как ты в коде сверху переименовываешь алиас таблицы с "a" на "e"!?1

6yrop

Почему не AddWithValue?
http://www.sommarskog.se/dyn-search-2005.html#CLR
Don't Forget to Specify the Length!
When the parameter has any of the data types char, varchar, nchar, nvarchar, binary or varbinary, you need to specify the length of the parameter, whereas for fixed-length data types you leave it out.
Well, if you try, you may find that you always can leave out the length. You may also get the idea to use the method AddWithValue, which permits you to define the parameter and value in a single call. Do not fall into the trap of using these shortcuts!

Специалист по высоконагруженным системам не знал этого?

kokoc88

http://www.sommarskog.se/dyn-search-2005.html#CLR
Специалист по высоконагруженным системам не знал этого?
Не знал чего, простите? У меня в проекте с планами запросов со строками всё в порядке, ЧЯДНТ?
Кстати, а специалист по базам данных не знал, что при проектировании столбцы типа ModelCode никто не делает NVARCHAR?
Давай-ка ближе к делу, мы обсуждаем твой гавнокод, и ты должен доказывать его минимальную полезность, а не кричать о том, что все вокруг тупые, и только у тебя более 100500 скиллов работы с кодом. Хочу увидеть ответы на все свои вопросы:
1. Какой вывод о своём поделии и о себе ты делаешь из исключительно негативной реакции на твои потуги посетителей этого раздела?
2. Сделай скринкаст того, как ты в коде сверху переименовываешь алиас таблицы с "a" на "e".
3. Почему в примере сравнения с ADO.NET ты не полностью использовал свою библиотеку?

6yrop

Не знал чего, простите? У меня в проекте с планами запросов со строками всё в порядке, ЧЯДНТ?
проблема не в самих планах, а в кэше планов. Там же всё ясно написано. Я начинаю сомневаться в твоей способности читать.

kokoc88

проблема не в самих планах, а в кэше планов. Там же всё ясно написано.
Там написано, к чему она ведёт. Если тебе кто-то говорит, что у него с планами всё в порядке, не надо придираться к словам и тупить, кроме тебя все прекрасно понимают, о чём идёт речь.
Я начинаю сомневаться в твоей способности читать.

Я уже давно не сомневаюсь, что у тебя нет такой способности. Отвечай на вопросы, меня ты можешь обсудить в другой ветке. Это ты ошибочно считаешь себя умнее всех, вот и потрудись это доказать.
1. Какой вывод о своём поделии и о себе ты делаешь из исключительно негативной реакции на твои потуги посетителей этого раздела?
2. Сделай скринкаст того, как ты в коде сверху переименовываешь алиас таблицы с "a" на "e".
3. Почему в примере сравнения с ADO.NET ты не полностью использовал свою библиотеку?
4. Cпециалист по базам данных не знал, что при проектировании столбцы типа ModelCode никто не делает NVARCHAR?
5. Где в твоём сравнении с ADO.NET валидация длины строки при использовании твоего решения? Добавь это в пример.

6yrop

я на ответил? Ты согласен, что при прочих равных лучше не использовать AddWithValue?

6yrop

Сделай скринкаст
о :D , оказывается этот формат передачи информации не безнадежен :)

kokoc88

я на твой вопрос про AddWithValue ответил? Ты согласен, что при прочих равных лучше не использовать AddWithValue?
Нет, ты не ответил, ты наехал на меня. Как бы неважно, ты можешь считать меня полным лохом, от этого твоё весьма посредственное поделие лучше не станет. Чтобы доказать обратное нужно грамотно отвечать на все вопросы, а не кричать о том, что все вокруг тебя не понимают. Тебе как бы нужно доказать, что необходимость писать такой гавнокод, который по длине больше чем код на ADO.NET вместе с юнит тестами, хоть сколько-нибудь себя окупает.

kokoc88

о , оказывается этот формат передачи информации не безнадежен
Да, очень хочется посмотреть на то, как ты будешь крячиться со своим собственным поделием, и как оно тебе в этом случае поможет.

6yrop

для вот этого кода?

kokoc88

для вот этого кода?
Нет, давай для другого, конечно: Я бы ещё попросил во время рефакторинга навесить пару сложных CCK на столбцы, но пока что хватит и чего-нибудь совсем тривиального...

6yrop

Сделай скринкаст того, как ты в коде сверху переименовываешь алиас таблицы с "a" на "e".
ты намекаешь на то, что в SQL-е одноименный алиас может перекрывать вышестоящий?

SELECT *
FROM Model a
WRE ModelId IN ( SELECT ModelId
FROM Model a
WRE a.ModelId = 0 )

и при переименовании

SELECT *
FROM Model a
WRE ModelId IN ( SELECT ModelId
FROM Model e
WRE a.ModelId = 0 )

получаем валидный, но совершенно другой запрос.
Сейчас, да, это не решается. Но в принципе проблема решается через более тесную интеграцию с Visual Studio через Roslyn. А где гарантия, что "смысловые" unit-тесты такое отловят?

kokoc88

Сейчас, да, это не решается. Но в принципе проблема решается через более тесную интеграцию с Visual Studio через Roslyn. А где гарантия, что "смысловые" unit-тесты такое отловят?
Я не имею в виду ничего секретного. Всё, что мне интересно - это сколько действий требуется в предложенном варианте для переименования алиаса таблицы с "a" на "e". Чем эта технология для создания динамических запросов помогает, что окупается увеличение объёма кода в три раза и падение читаемости в девять?

6yrop

2. Сделай скринкаст того, как ты в коде сверху переименовываешь алиас таблицы с "a" на "e".
http://www.screencast.com/t/vKknGEffN6O

6yrop

3. Почему в примере сравнения с ADO.NET ты не полностью использовал свою библиотеку?
Там Controllable Query использован полностью. Отличие от представленного только тем, что вот этот метод заинлайнен. Этот метод не входит в Controllable Query.

public static IEnumerable<TRecordset> MaterializeReader<TParams, TRecordset>(
Func<SqlCommand, TParams> paramsFunc, Func<TParams, IQuery<TRecordset>> recordsetFunc)
{
using (var sqlCommand = new SqlCommand
{
var query = recordsetFunc(paramsFunc(sqlCommand;
sqlCommand.CommandText = query.TransformText;
using (var connection = new SqlConnection(ConnectionString
{
connection.Open;
sqlCommand.Connection = connection;
using (var sqlDataReader = sqlCommand.ExecuteReader
while (sqlDataReader.Read
yield return query.GetReader(sqlDataReader).Materialize;
}
}
}

Я пытаюсь выяснить, что вызывает затруднение в понимании.

6yrop

1. Какой вывод о своём поделии и о себе ты делаешь из исключительно негативной реакции на твои потуги посетителей этого раздела?
Моя позиция крайне проста. Ок, пусть Controllable Query плох. Идем дальше. Какую технология для работы с SQL СУБД мы будем использовать завтра в новом проекте? В 5-ом разделе есть некоторый список технологий, смотрим на него, сравниваем. ... Упс, а Controllable Query то нам лучше подходит.
Пока "посетители этого раздела" не предложат свои варианты, разговаривать не о чем.

hprt

Я вообще, DB девелопер, поэтому мне, может быть, не совсем понятны твои проблемы. Но все же, не понимаю, для чего ты все это городишь, если уже есть куча инструментов. Sql Server Data Tools видел? Там и Find Usages есть, и рефакторинг, причем не на уровне "поменяли колонку, смотрим, что сломалось", а нормальный такой. Да, динамический сиквел вроде не парсит, но может быть он и не нужен? Динамика нужна в первую очередь базданщикам, чтобы на сложных поисковых процедурах можно было использовать индексы нормально

6yrop

4. Cпециалист по базам данных не знал, что при проектировании столбцы типа ModelCode никто не делает NVARCHAR?
Это увеличивает информационный шум, а к Controllable Query не имеет ни малейшего отношения.
5. Где в твоём сравнении с ADO.NET валидация длины строки при использовании твоего решения? Добавь это в пример.
Непосредственно это не входит в Controllable Query. Controllable Query лишь предлагает колбек для мепинга SQL типов на .NET типы.
Я ответил на все твои вопросы. Теперь твоя очередь. Лучше всего, если будет код.

6yrop

Sql Server Data Tools видел?
да, использовали в нескольких проектах.
Да, динамический сиквел вроде не парсит, но может быть он и не нужен?

мы тоже долго так себя обманывали. Нет, динамический sql нужен.
Кстати, даже для не динамического sql-я кода получается меньше (в Controllable Query). Задача: считать modelCode с консоли и вывести ModelId + 1 в консоль. Вот на Controllable Query. Приведи весь код в своем подходе.

6yrop

рефакторинг, причем не на уровне "поменяли колонку, смотрим, что сломалось",
я как раз размышляю над такой функциональностью в Controllable Query. Я не вижу принципиальных трудностей для реализации такого. Но есть сильно трудозатратные шаги: парсинг SQL и C#, тесная интеграция ы Visual Studio.

kokoc88

http://www.screencast.com/t/vKknGEffN6O
После нескольких часов тренировки ты всё-таки сделал скринкаст, где на тривиальную задачу потратил две минуты. При этом твоя же библиотека отсылала тебя к номерам строк на C#, которые не связаны с ошибкой. Она же не смогла определить, что SQL ошибка теперь в клаузе FROM, оставшись на SELECT. Особенно порадовало изменение INNER JOIN и WRE, которые на самом деле на строке 40 и 47, а твоя библиотека показывает на строку 15.
Какую технология для работы с SQL СУБД мы будем использовать завтра в новом проекте? В 5-ом разделе есть некоторый список технологий, смотрим на него, сравниваем. ... Упс, а Controllable Query то нам лучше подходит.
Ответ на вопрос какую технологию: уж точно не твою. Она совершенно никому не подходит, неудобна в использовании, поощеряет размножение гавнокода.
Пока "посетители этого раздела" не предложат свои варианты, разговаривать не о чем.
Никто ничего тебе тут не предложит, здесь почти нет разработчиков, которые любят жевать кактусы и биться лбом об стену. Если тебе разговаривать с этим разделом не о чем, я предлагаю тебе молчать.
Я ответил на все твои вопросы. Теперь твоя очередь. Лучше всего, если будет код.

Моей очереди не будет. Если ты ещё не понял, то я не занимаюсь созданием какого-то фреймворка для работы с БД.

6yrop

Задача: считать modelCode с консоли и вывести ModelId + 1 в консоль.
консоль можешь заменить на HTML страничку, твой любимый UI-фраймворк, текстовый файл и т.д.

6yrop

При этом твоя же библиотека отсылала тебя к номерам строк на C#, которые не связаны с ошибкой. Она же не смогла определить, что SQL ошибка теперь в клаузе FROM, оставшись на SELECT. Особенно порадовало изменение INNER JOIN и WRE, которые на самом деле на строке 40 и 47, а твоя библиотека показывает на строку 15.
, ты смешен, ты просто не хочешь понимать те сообщение, которые выдает моя тулза. Ну это твои личные проблемы. Вменяемым людям сообщения и номера строк понятны.

kokoc88

, ты смешен, ты просто не хочешь понимать те сообщение, которые выдает моя тулза. Ну это твои личные проблемы. Вменяемым людям сообщения и номера строк понятны.
Вот именно, вменяемым людям они понятны.

Dasar

И как мне тут переименовать столбец базы и алиас таблицы в запросе?
Основная цель другая: при частых изменениях кода получить вменяемую гарантию, что код содержит минимум описок.
и эту цель сейчас достигает. Потерял, конечно, на простоте синтаксиса, но по другому на универсальном языке едва ли получится.

hprt

Задача считать поле с консоли и вывести ид+1 очень простая. Т.е. если, скажем, эта задача решается просто, а сложные через жопу, но есть инструменты, которые решают и то, и то по-другому, но в сложном случае намного проще, имеет смысл использовать другой инструмент. А тут я принципиальных преимуществ не вижу, кроме твоих Find usages + Refactoring. Я уже сказал, что рефакторинг в SSDT делается проще + у тебя его вообще нет как такового - ну ок, тесты в автомате запускаешь.
По поводу Find usages - я сам занимался подобной проблемой, но из-за нехватки времени забил. У меня часто стоит задача посмотреть, в каких процедурах меняется то или иное поле. Как показывает практика, тупой поиск по именам объекта и колонки дает не больше 10% левых результатов. Поиск просто по имени объекта ошибок почти не дает. Это все сделано на уровне базы, разумеется, + скрипты не надо писать, все сделано через SSMS Tools, т.е. я просто кликаю на колонке в таблице и делаю "find updates". Поиск идет по sys.sql_modules с простого поиска по маске, есть вариант с регулярными выражениями, но это надо отдельно деплоить, поэтому я остановился пока на простом поиске. Я, конечно, хочу сделать 100% поиск, но мне лень + не настолько хорошо знаю регулярки, чтобы написать это на все случаи жизни, поэтому 10% ошибок меня вполне устраивают (10% - максимум, при условии, что поле в процедуре не апдейтится, но все равно присутствует, или есть одноименное поле в другой таблице)
По поводу всего кода, который надо предоставить.. Писать свой код - а зачем? Во-первых, мы не пытаемся сравнить, кто круче кодит. Ты говоришь, что твой код круче аналогов, я вижу велосипед с квадратными колесами. Я могу быть неправ, но ты ж меня убедить не можешь. Ладно, я не ооп-девелопер, и действительно со многими проблемами не сталкиваюсь. Но остальные вроде как тоже того же мнения придерживаются. Во-вторых код я на дотнете пишу только тесты для своих процедур, и то редко, т.к. база почти не меняется. Соответственно, вызов процедур меня тоже не беспокоит - там ничего не сломается. Могу написать стандартный код для вызова процедуры, конечно. Он будет понятнее твоего, но ты скажешь, что там нет find usages + refactoring, ну ок, нету..

kokoc88

и эту цель сейчас достигает.
В прошлых простынях, некоторым из которых уже год, мы все давно убедились, что без юнит тестов всё равно ничего не получается.
Потерял, конечно, на простоте синтаксиса, но по другому на универсальном языке едва ли получится.
Потерял столько, что даже при полноценной реализации этой библиотеки её вряд ли можно будет использовать. Потому что быстрее будет написать и поддерживать простые запросы и полноценные юнит тесты, чем такой неизведанный кошмар. Цена слишком высокая - вот о чём уже сто раз все пытались аргументированно высказаться.

Dasar

мы все давно убедились, что без юнит тестов всё равно ничего не получается.
юнит тесты решают другую задачу.
Так же как статическая типизация не отменяет юнит-тесты, но здорово уменьшает их кол-во, и позволяет получить вменяемую гарантию на работоспособность кода при всех вариантах
задача code coverage опять же у Shirik-а легко решается.
зы
имхо, обычно над качеством кода никто не заморачивается (пользователи особо не пищат, ну и достаточно поэтому большинству и не понятно зачем какие-то телодвижения делает..

6yrop

Могу написать стандартный код для вызова процедуры, конечно. Он будет понятнее твоего
напиши

kokoc88

Так же как статическая типизация не отменяет юнит-тесты, но здорово уменьшает их кол-во, и позволяет получить вменяемую гарантию на работоспособность кода при всех вариантах
Предлагаемая библиотека пока что не может сократить количество юнит тестов, в чём все опять же могли убедиться в прошлогодних простынях.
имхо, обычно над качеством кода никто не заморачивается (пользователи особо не пищат, ну и достаточно поэтому большинству и не понятно зачем какие-то телодвижения делает..
При таком усложнении синтаксиса процент ошибок намного выше, чем при использовании простых юнит тестов. Поэтому большинству не понятно, зачем нужен такой ад, если при 1% полезных действий он усложняет написание и поддержку кода на 200%.

hprt

может тебя просто в мсдн послать?

hprt

И да, я добавляю параметры через AddWithValue. Когда дергаешь процедуру, с планами проблем нет

6yrop

может тебя просто в мсдн послать?
а почему не написать?

Dasar

При таком усложнении синтаксиса процент ошибок намного выше, чем при использовании простых юнит тестов.
не все ошибки одинаково хероваты.
например, самое простое - это клеить sql-строки руками, но при таком способе большинство ошибок приводят к утечке данных клиентов на сторону.
зы
в данном случае, решается стандартная задача: ошибки из runtime-а перетаскиваются на этап разработки, а то что при этом суммарно ошибок становится больше (за счет более навороченного синтаксиса) - это не критично. Главное, что сильно падает кол-во ошибок уровня runtime.

hprt


using (SqlConnection conn = new SqlConnection("connection string"
{
SqlCommand cmd = new SqlCommand("dummy_procedure", conn);
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@dummy", "Special for ");
cmd.ExecuteNonQuery;

}

kokoc88

например, самое простое - это клеить sql-строки руками, но при таком способе большинство ошибок приводят к утечке данных клиентов на сторону.

В таком сложном синтаксисе вероятность появления подобных ошибок выше. Ты просто запутаешься где-нибудь в дебрях этого говна и в одном из путей забудешь прописать нужный if с добавлением какого-нибудь ограничения. В простом коде это сразу же поймают во время code review или во время разработки юнит тестов.
Замечу, что предлагаемое поделие никак с этой задачей не справляется, потому что не тестирует логику запросов. То есть для избежания этой плохой ситуации всё равно нужны полноценные юнит тесты.
в данном случае, решается стандартная задача: ошибки из runtime-а перетаскиваются на этап разработки, а то что при этом суммарно ошибок становится больше (за счет более навороченного синтаксиса) - это не критично. Главное, что сильно падает кол-во ошибок уровня runtime.
Очевидно, что в данном случае эта задача не решается: проверки типов для этого не достаточно.

kokoc88

Ты просто запутаешься где-нибудь в дебрях этого говна
Допустим код:
return q.Select(_ => new {
     id = _._<int>("a."
     type_id = _._<int>("a.")
     });

На самом деле работает и проходит все тесты, но должен был быть написан как:
return q.Select(_ => new {
     id = _._<int>("a."
     type_id = _._<int>("b.")
     });

Причём в случае кода от Шурика места для изменения "a" на "b" два или больше, и тесты в обоих случаях проходят. Разработчик, работая с чужим плохо понятным кодом заменит в одном месте и не увидит во втором. Полагаясь на поделие, проведёт тестирование и вот тебе твои "данные клиентов" на блюдечке.

Dasar

Очевидно, что в данном случае эта задача не решается: проверки типов для этого не достаточно.
достаточность не нужна.
Необходимо лишь, чтобы инструменты кардинально уменьшали вероятность пролезания в runtime основных ограничений человека: забывчивость, леность, неточность(неряшливость) и т.д.

kokoc88

Необходимо лишь, чтобы инструменты кардинально уменьшали вероятность пролезания в runtime основных ограничений человека: забывчивость, леность, неточность и т.д.
Очевидно, предлагаемое поделие кардинально увеличивают эту вероятность, особенно ленивость.

Dasar

Очевидно, предлагаемое поделие кардинально увеличивают эту вероятность, особенно ленивость.
если за каждую найденную plain-текст склейку вставляют, то леность сама проходят.

kokoc88

если за каждую найденную plain-текст склейку вставляют, то леность сама проходят.
И при чём тут обсуждаемое нами поделие?

Dasar

легко автоматизировать поиск "некошерного" доступа к БД

Dasar

ты чем вообще недоволен?
данный подход нужен когда sql-запросов в коде десятки тысяч, что нормально для бизнес-приложений, но редкость для других приложений.
если у тебя такого кол-ва запросов нет, то и данный подход тебе нафиг не уперся.

kokoc88

легко автоматизировать поиск "некошерного" доступа к БД
Каким именно образом? Допустим, заебало меня делать всякие q_Q, и я написал вместо одного:
q._(" where 1 = 1");
if (d)
q._(" and a.type_id = 1");

другое:
q._(" where 1 = 1" + (d ? " and a.type_id = 1" : "";
Теперь вопрос: при code review проверяющему будет проще прочитать простой код или код на поделии? Не забывай, что поиск некошерного доступа тоже подпадает под ленивость, которая намного выше, когда приходится вычитывать тонну гавнокода.

kokoc88

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

Dasar

Каким именно образом?
если правильно, то сначала константные строки похожие на sql (содержащие where, and, or и т.д.) пометить типом sql-string, а потом проверить, что два типа sql-string не складываются напрямую.
есть еще всякие приблизительные методы.

kokoc88

есть еще всякие приблизительные методы.
У меня ощущение, что ты весьма приблизительно отвечаешь на мои посты....

Dasar

большего оно не стоит.

6yrop

using (SqlConnection conn = new SqlConnection("connection string"
2{
3 SqlCommand cmd = new SqlCommand("dummy_procedure", conn);
4 cmd.CommandType = System.Data.CommandType.StoredProcedure;
5 cmd.Parameters.AddWithValue("@dummy", "Special for ");
6 cmd.ExecuteNonQuery;
7}
Заказчик заплатил вот за эту задачу:
Задача: считать modelCode с консоли и вывести ModelId + 1 в консоль.

В твоем коде нет НИ ОДНОГО элемента оплаченной задачи. Не говоря уже о полном решении.

6yrop

Допустим, заебало меня делать всякие q_Q, и я написал вместо одного:
q._(" where 1 = 1");
    if (d)
     q._(" and a.type_id = 1");
другое:
q._(" where 1 = 1" + (d ? " and a.type_id = 1" : "";
Подходят оба варианта. Вообще, класс Q это обертка над StringBuilder c одной целью — автоматически вызывать IParam.ToSql. По желанию можешь отказаться от автоматического вызова и переложить эту ответственность на code review.
 

public class Q
{
public readonly StringBuilder Builder;

public Q(string value)
{
Builder = new StringBuilder(value);
}

public Q _(string value)
{
Builder.Append(value);
return this;
}

public Q _<T>(IParam<T> param)
{
return _(param.ToSql;
}
}

kokoc88

Подходят оба варианта.
Нет, в нашем примере второй вариант не подходит.

6yrop

Нет, в нашем примере второй вариант не подходит.
почему?

hprt

Вот когда ты оплатишь, тогда тебе и будет решение твоей задачи. Пока же я отвечал тебе на другой вопрос, как выглядит мой код. Если тебе до сих пор принципиально узнать, как неспециалист разбирает cmd.ExecuteReader я таки попрошу оплаты - как будто мне делать больше нечего, чем перед идиотом распинаться "сделай то, сделай се", когда все уже написано (я на дотнете не пишу обычно, код очень близок к мсдн)

6yrop

Вот когда ты оплатишь, тогда тебе и будет решение твоей задачи.
Давай, называй цену.

hprt

и чо, правда заплатишь? Про оплату сказал только потому что ты сказал, что "заказчик оплатил". Давай серьезно - ты привел в пример поделку, я тоже. но ты рекламируешь свой продукт, я просто спрашиваю, нахрена он мне нужен. Я готов делиться кодом бесплатно, если считаю его общественно полезным :) Кстати, примерно из той же области - про поддержку кода web-страница

luna89

Шурик, я в прошлом твоем треде предлагал . Ты возразил, что там всегда будет фулскан. Сегодня я проверил это на постгресе:

postgres=# create table test as select id from generate_series(1,1000000) t(id);
SELECT 1000000
postgres=# create index testi on test(id);
CREATE INDEX
postgres=# prepare testq(integer) as select * from test where ($1 is null or id = $1);
PREPARE
postgres=# explain execute testq(1234456);
QUERY PLAN
------------------------------------------------------------------------
Bitmap Heap Scan on test (cost=96.10..4793.98 rows=5000 width=4)
Recheck Cond: (id = 1234456)
-> Bitmap Index Scan on testi (cost=0.00..94.85 rows=5000 width=0)
Index Cond: (id = 1234456)
(4 rows)


postgres=# explain execute testq(null);
QUERY PLAN
--------------------------------------------------------------
Seq Scan on test (cost=0.00..14425.00 rows=1000000 width=4)
(1 row)

Как видим, в не самом продвинутом постгресе все хорошо с планами такого рода препаренных запросов. Ты уверен что в SQL сервере есть такие проблемы? Я бы сам проверил, но лень возиться с установкой.

Dasar

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

luna89

Чуть запрос усложнишь и хороший план летит к черту.
Можно пример такого, что шуриковский план не летит к черту, а предложенный мной летит? Шурика волновало, что СУБД не делает constant folding для препаренных запросов. Из моего примера видно, что делает. Что еще надо?

6yrop

Шурика волновало, что СУБД не делает constant folding для препаренных запросов.
То, о чем ты говоришь, известная проблема. Почитай форумы и статьи. Суть там в использовании кеша планов запросов. Для подстановки констант серверу требуется разбор запроса, а он старается использовать уже готовый план из кеша. Есть специальный хинт на запрос для перекомпиляции каждый раз.

Dasar

create table test as select id from generate_series(1,1000000) t(id);
сделай нормальную таблицу с большим кол-вом колонок.
таблица с колонкой из одного ключа особая, т.к. индекс имеет данные по всему ряду, что не бывает для нормальных таблиц

6yrop

Кстати, разработчики BLToolkit говорили, что подставляют константы при генерации SQL из LINQ. Но BLToolkit это BLToolkit :smirk:

Dasar

у меня вот уже такой запрос хочет table scan

declare @rank int
set @Rank =12

select id, rank, data
from temp
where @rank is null or Rank = @Rank

а такой

declare @rank int
set @Rank =12

select id, rank, data
from temp
where Rank = @Rank

делает index_seek, при условии что все колонки included

Dasar

Кстати, разработчики BLToolkit говорили, что подставляют константы при генерации SQL из LINQ
это вроде палка о двух концах. если подставлять константы, то каждый запрос становится уникальный, и у sql bd не получается использовать авто-кэширование планов запросов.

6yrop

это вроде палка о двух концах. если подставлять константы, то каждый запрос становится уникальный, и у sql bd не получается использовать авто-кэширование планов запросов.
они вроде там хитро делаю, вычисляют определенный класс выражений типа "@p IS NULL", а основную массу параметров отправляют на сервер как параметры. Это приводит к достаточно конечному множеству запросов.

luna89


postgres=# drop table test;
DROP TABLE
postgres=# create table test as
postgres-# select
postgres-# id,
postgres-# md5(id||'foo') as foo,
postgres-# md5(id||'bar') as bar,
postgres-# lpad('',100,md5(id||'baz' as baz
postgres-# from generate_series(1,1000000) t(id);
SELECT 1000000
postgres=# create index testi on test(id,bar);
CREATE INDEX
postgres=# prepare testq(integer,text) as select * from test where ($1 is null or id = $1) and ($2 is null or bar = $2);
ERROR: prepared statement "testq" already exists
postgres=# deallocate testq;
DEALLOCATE
postgres=# prepare testq(integer,text) as select * from test where ($1 is null or id = $1) and ($2 is null or bar = $2);
PREPARE
postgres=# explain execute testq(100500,'qq');
QUERY PLAN
---------------------------------------------------------------------
Bitmap Heap Scan on test (cost=7.39..105.39 rows=25 width=100)
Recheck Cond: id = 100500) AND (bar = 'qq'::text
-> Bitmap Index Scan on testi (cost=0.00..7.38 rows=25 width=0)
Index Cond: id = 100500) AND (bar = 'qq'::text
(4 rows)

luna89

у меня вот уже такой запрос хочет table scan
Значит, для SQL сервера это не работает. По-видимому, это не является принципиальным ограничением (постгрес как-то это делает). Следовательно, всех устраивает конкатенация строк.

ava3443

постгрес как-то это делает
оракл тоже
в очередной раз сделал для себя вывод - держаться подальше от MSSQL :)

Dasar

где-то присутствует жульничество.
получение колонок id, foo, bar, baz не может быть выполнено только из индекса testi, т.к. там хранятся только колонки id, bar. Обязательно еще должен быть запрос к кластерному индексу для получения колонок foo, baz.
Либо testi является кластерным индексом, но тогда понятно почему план запросов всегда делается через него.
ps
добавь еще индекс по bar и покажи, что происходит если последовательно спрашивать через testq:
id != null, bar == null
id != null, bar != null
id == null, bar != null
id == null, bar == null
тоже самое желательно еще проделать с тремя колонками id, foo, bar, используя восемь запросов и имея индексы:
кластерный id, foo, bar
некластерный foo, bar
некластерный id, bar
некластерный bar

6yrop

в очередной раз сделал для себя вывод - держаться подальше от MSSQL
в очередной раз не разобравшись в проблеме. Проблема общая и об этом везде пишут.

luna89

получение колонок id, foo, bar, baz не может быть выполнено только из индекса testi, т.к. там хранятся только колонки id, bar. Обязательно еще должен быть запрос к кластерному индексу для получения колонок foo, baz.
Либо testi является кластерным индексом, но тогда понятно почему план запросов всегда делается через него.
См. в первой строке плана heap scan. Heap это и есть сама таблица. Постгрес сначала лезет в индекс, чтобы найти строки, которые подпадают под критерий, заданный в where, затем лезет в таблицу, чтобы достать столбцы, которых нет в индексе.

kokoc88

Обязательно еще должен быть запрос к кластерному индексу для получения колонок foo, baz.

Dasar

в первой строке плана heap scan. Heap это и есть сама таблица.
так понятнее, спасибо.

Dasar

можешь дальше продолжать верить в магию.

kokoc88

можешь дальше продолжать верить в магию.
Можешь дальше не лечиться от Microsoft-а головного мозга.

ava3443

да, я облажался
на примере посмотрел планы 3 запросов (Oracle 11.2):
1) select * from test where (:id is null or id = :id) and (:bar is null or bar = :bar);
2) select * from test where (id = :id) and (:bar is null or bar = :bar);
3) select * from test where (id = :id) and (bar = :bar);
1 - table access full
2 и 3 - range scan по индексу

Andbar

1) select * from test where (:id is null or id = :id) and (:bar is null or bar = :bar);
ну так null-ы в индекс не запихнёшь, плюс - нелюбовь оракла к or'ам.
попробуй заменить :_param is null or column = :_param на nvl(:_param, column) = column

6yrop

Если набор колонок варьируется, то вот так

Query.New(
p => {
var q = new Q("select a.*");

IQuery<IRecord> result;
if (p.b)
{
q._(", b.id as id_b");
result = q.Query<IBRecord>
}
else
result = q.Query<IARecord>

q._(" from ");

if (p.c)
q._("c as a");
else
q._("a");

if (p.b)
q._(" inner join b on b.id = a.id ");

q._(" where 1 = 1");

if (p.d)
q._(" and a.type_id = 1");
return result;
},
new {b, c, d}
).Execute;

interface IRecord
{
T Match<T>(Func<IARecord, T> a, Func<IBRecord, T> b);
}

private interface IARecord : IRecord
{
int id { get; }
int type_id { get; }
}

private interface IBRecord : IARecord
{
int id_b { get; }
}

mbolik1

Сегодня я проверил это на постгресе:
Долго думал как может работать эта чёрная магия.
Оказалось всё просто:
This is another situation where EXECUTE can be used to force a new plan to be generated for each execution.

6yrop

Долго думал как может работать эта чёрная магия.
т.е. магия не работает. Школота разочарована. Поставил плюс.

luna89

This is another situation where EXECUTE can be used to force a new plan to be generated for each execution.

Там, откуда ты взял эту цитату, речь идет о plpgsql execute, который не имеет никакого отношения к SQL execute, кроме названия.

6yrop

вбей фразу в гугл

6yrop

вот случайно натолкнулся, специально для вас всё расписали, как раз PostgreSQL http://thoughts.j-davis.com/2011/07/09/building-sql-strings-...

luna89

специально для вас всё расписали, как раз PostgreSQL
Это старая статья. Мой пример работает начиная с версии 9.2.
Allow the planner to generate custom plans for specific parameter values even when using prepared statements (Tom Lane)
In the past, a prepared statement always had a single "generic" plan that was used for all parameter values, which was frequently much inferior to the plans used for non-prepared statements containing explicit constant values. Now, the planner attempts to generate custom plans for specific parameter values. A generic plan will only be used after custom plans have repeatedly proven to provide no benefit. This change should eliminate the performance penalties formerly seen from use of prepared statements (including non-dynamic statements in PL/pgSQL).

6yrop

Это старая статья. Мой пример работает начиная с версии 9.2.
Во-первых. Старая статья? Ты про 9.2, который еще не зарелизен?
Во-вторых, например, MS SQL Server вот такое "Now, the planner attempts to generate custom plans for specific parameter values" делает уже давно (и есть хинт для отключения этого). Но это проблему не решает. По моей ссылке объясняется, что проблема не разрешима в принципе. Там логическая цепочка из трех шагов, если ты ее не в состоянии понять, то донести до тебя эту информацию не предоставляется возможным.

6yrop

проблема не разрешима в принципе
наверное, можно делать ключи кеша с учетом значений параметров, выделяя специальные значения такие как NULL, но это усложнение, и пока ни одна СУБД так не делает.

6yrop

explain execute testq(1234456);
надо смотреть не так, а прям вытащить план из кеша (в MS SQL Server такое можно, думаю и в посгресе тоже).
Оставить комментарий
Имя или ник:
Комментарий: