Где в интернете можно обсудить dynamic sql automatic checking?
sql.ru ?
а что ты хочешь от автоматических проверок? Если просто на валидность - посмотри опцию PARSEONLY (MSSQL в других субд тоже должно быть что-нибудь для этого
да, я собственно и использую FMTONLY. Но вопрос в другом. В интеграции баз данных и "обычных" языков программирования. Как из source code-а получить полный список собранных запросов, чтобы потом воспользоваться FMTONLY?
Даже в таком простом случае это довольно сложно сделать, мне кажется
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 только анализирует синтаксис. Т.е. если у тебя, скажем, схема поменялась, проблем все равно не будет
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 тоже имеет экспоненциальную сложность.
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;
}
ну т.е. тебе на каждый метод, собирающий запрос придется писать метод, перебирающий варианты. что я и имел в виду - в общем случае сложно.
FMTONLY сломается, если ты работаешь с временными таблицамиспасибо, да, кажется не работает. В MSSQL2012 они предлагают замену FMTONLY sp_describe_first_result_set
Но почему только first им что было жалко для всех сделать....
придется писать метод, перебирающий варианты.такой метод писать не надо. Код, который я запостил полностью самодостаточный, к нему ничего дописывать не надо. Валидация происходит полностью автоматически (я проверил).
А теперь представь, что у тебя динамика собирается из метаданных. Тоже все делается, но уже сложнее
По поводу sp_describe_first_result_set - думаю, сделали так, потому что у тебя резалтсетов может быть много, т.е. с этими параметрами дергаешь - один, с другими - два или три. Например, sp_help. Я, правда, не очень представляю, как они и один сделали - вроде бы значения параметров туда не передаешь, только тип. Хотя надо поставить сервер, посмотреть.
Это простой случай, когда у тебя есть, условно, 5 переменных, которые влияют на возможный запрос. И то, тебе надо писать перебор значений, чтобы все посмотреть.еще раз, кроме того, что я запостил, ничего писать не надо! Инструмент сам найдет запрос и провалидирует его.
Добавится новое условие - тебе придется метод переписывать.какой метод ты думаешь надо будет переписывать?
Это можно на NUnit сделать том же, даже ничего писать самому не надо.
Классический подход через NUnit это выписывание всех вариантов вручную. И ни каких гарантий, что человек не ошибется. От этого я и избавлялся.
В таком случае, поздравляю с изобретением велосипеда! На NUnit это делается с помощью Values, Range, Random. Во всяком случае, базу периодически тестирую с NUnit, и вполне хватает
Если я насчет NUnit неправ - подскажи, в чем могучее отличие твоего кода от тех же Values, а то я на дотнете лет 5 не писал уже, и мне не всегда очевидно, что происходит
Если набор колонок варьируется, то либо надо применять C# dynamic, либо вот такскорее всего через возврат результата аля pattern mattching можно что-то более симпатичное замутить, но пока мне вариация колонок не встречалась, поэтому отложим...
Если я насчет NUnit неправ - подскажи, в чем могучее отличие твоего кода от тех же Valuesв том что не надо писать Values . Более того тест на запрос не надо писать. Т.е. не пишем ничего.
например, сортировка
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 можно проверить разом
например, сортировкапочему переменная 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 и т.п., таких решений десятки или сотни, и ни одного удовлетворительного.
Более того тест на запрос не надо писать.Даже более того, сам запрос не оформляется в виде элемента (метода который можно тестировать NUnit-ом — запрос это просто часть тела некоторого метода.
из source code-а получить полный список собранных запросовЕсли эти проверки требуют такого ужасного (нечитаемого) кода, как ты привёл выше, то может ну нах эти проверки? Имхо не стоит оно того. Жизнь одна, зачем её тратить на работу с таким кодом?
все кто попробовал с таким кодом работать очень довольны и просят добавки перевести его под .NET 2.0, чтобы использовать в старых проектахЭто они тебе в лицо говорят, сходи с ними хоть на одну пьянку...
Это они тебе в лицо говорят, сходи с ними хоть на одну пьянку...регулярно устраиваем, и часто обсуждаем эти темы
Если эти проверки требуют такого ужасного (нечитаемого) кода, как ты привёл выше, то может ну нах эти проверки? Имхо не стоит оно того. Жизнь одна, зачем её тратить на работу с таким кодом?кстати, хорошим тоном будет, если ты запостишь свой вариант кода. Будет о чем говорить.
кстати, хорошим тоном будет, если ты запостишь свой вариант кода. Будет о чем говорить.Тебе уже говорили: сравни это с ручной работой с ADO.NET
Тебе уже говорилиа кода нет
а кода нетВот именно: нет твоего кода, в котором ты показываешь, что код на твоём поделии короче и лучше хотя бы ADO.NET
где взять эталонный код на ADO.NET?
где взять эталонный код на ADO.NET?Никто не просит эталонный - ты напиши обычный и хороший, если ты на это способен...
регулярно устраиваем, и часто обсуждаем эти темыКстати, мне очень интересен ответ на один единственный вопрос: какой вывод о своём поделии и о себе ты делаешь из реакции посетителей этого раздела?
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-я строка):
ок, попробую, но шансов мало на конструктивное обсуждение ...Ага, тебе уже более сотни постов разные люди пытались аргументированно объяснить, что твоя либа гавно. Если ты никого не послушал тогда, то и сейчас будешь продолжать гнуть своё.
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);
Почему полностью не использовал свою библиотеку? У тебя во всех примерах более дикие вариации. Давай-ка пример побольше, с динамическими запросами, в общем такой, где у тебя появляется гавнокод.
без простого рефакторинга базыЭто, что ли, даёт простой рефакторинг базы?
id = _._<int>("a."
type_id = _._<int>("a."
id_b = _._<int>("b.id as ")
И как мне тут переименовать столбец базы и алиас таблицы в запросе?
Сделай-ка нам скринкаст того, как ты в коде сверху переименовываешь алиас таблицы с "a" на "e"!?1
Почему не 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!
Специалист по высоконагруженным системам не знал этого?
http://www.sommarskog.se/dyn-search-2005.html#CLRНе знал чего, простите? У меня в проекте с планами запросов со строками всё в порядке, ЧЯДНТ?
Специалист по высоконагруженным системам не знал этого?
Кстати, а специалист по базам данных не знал, что при проектировании столбцы типа ModelCode никто не делает NVARCHAR?
Давай-ка ближе к делу, мы обсуждаем твой гавнокод, и ты должен доказывать его минимальную полезность, а не кричать о том, что все вокруг тупые, и только у тебя более 100500 скиллов работы с кодом. Хочу увидеть ответы на все свои вопросы:
1. Какой вывод о своём поделии и о себе ты делаешь из исключительно негативной реакции на твои потуги посетителей этого раздела?
2. Сделай скринкаст того, как ты в коде сверху переименовываешь алиас таблицы с "a" на "e".
3. Почему в примере сравнения с ADO.NET ты не полностью использовал свою библиотеку?
Не знал чего, простите? У меня в проекте с планами запросов со строками всё в порядке, ЧЯДНТ?проблема не в самих планах, а в кэше планов. Там же всё ясно написано. Я начинаю сомневаться в твоей способности читать.
проблема не в самих планах, а в кэше планов. Там же всё ясно написано.Там написано, к чему она ведёт. Если тебе кто-то говорит, что у него с планами всё в порядке, не надо придираться к словам и тупить, кроме тебя все прекрасно понимают, о чём идёт речь.
Я начинаю сомневаться в твоей способности читать.
Я уже давно не сомневаюсь, что у тебя нет такой способности. Отвечай на вопросы, меня ты можешь обсудить в другой ветке. Это ты ошибочно считаешь себя умнее всех, вот и потрудись это доказать.
1. Какой вывод о своём поделии и о себе ты делаешь из исключительно негативной реакции на твои потуги посетителей этого раздела?
2. Сделай скринкаст того, как ты в коде сверху переименовываешь алиас таблицы с "a" на "e".
3. Почему в примере сравнения с ADO.NET ты не полностью использовал свою библиотеку?
4. Cпециалист по базам данных не знал, что при проектировании столбцы типа ModelCode никто не делает NVARCHAR?
5. Где в твоём сравнении с ADO.NET валидация длины строки при использовании твоего решения? Добавь это в пример.
я на ответил? Ты согласен, что при прочих равных лучше не использовать AddWithValue?
Сделай скринкасто , оказывается этот формат передачи информации не безнадежен
я на твой вопрос про AddWithValue ответил? Ты согласен, что при прочих равных лучше не использовать AddWithValue?Нет, ты не ответил, ты наехал на меня. Как бы неважно, ты можешь считать меня полным лохом, от этого твоё весьма посредственное поделие лучше не станет. Чтобы доказать обратное нужно грамотно отвечать на все вопросы, а не кричать о том, что все вокруг тебя не понимают. Тебе как бы нужно доказать, что необходимость писать такой гавнокод, который по длине больше чем код на ADO.NET вместе с юнит тестами, хоть сколько-нибудь себя окупает.
о , оказывается этот формат передачи информации не безнадеженДа, очень хочется посмотреть на то, как ты будешь крячиться со своим собственным поделием, и как оно тебе в этом случае поможет.
для вот этого кода?
для вот этого кода?Нет, давай для другого, конечно: Я бы ещё попросил во время рефакторинга навесить пару сложных CCK на столбцы, но пока что хватит и чего-нибудь совсем тривиального...
Сделай скринкаст того, как ты в коде сверху переименовываешь алиас таблицы с "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-тесты такое отловят?
Сейчас, да, это не решается. Но в принципе проблема решается через более тесную интеграцию с Visual Studio через Roslyn. А где гарантия, что "смысловые" unit-тесты такое отловят?Я не имею в виду ничего секретного. Всё, что мне интересно - это сколько действий требуется в предложенном варианте для переименования алиаса таблицы с "a" на "e". Чем эта технология для создания динамических запросов помогает, что окупается увеличение объёма кода в три раза и падение читаемости в девять?
2. Сделай скринкаст того, как ты в коде сверху переименовываешь алиас таблицы с "a" на "e".http://www.screencast.com/t/vKknGEffN6O
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;
}
}
}
Я пытаюсь выяснить, что вызывает затруднение в понимании.
1. Какой вывод о своём поделии и о себе ты делаешь из исключительно негативной реакции на твои потуги посетителей этого раздела?Моя позиция крайне проста. Ок, пусть Controllable Query плох. Идем дальше. Какую технология для работы с SQL СУБД мы будем использовать завтра в новом проекте? В 5-ом разделе есть некоторый список технологий, смотрим на него, сравниваем. ... Упс, а Controllable Query то нам лучше подходит.
Пока "посетители этого раздела" не предложат свои варианты, разговаривать не о чем.
Я вообще, DB девелопер, поэтому мне, может быть, не совсем понятны твои проблемы. Но все же, не понимаю, для чего ты все это городишь, если уже есть куча инструментов. Sql Server Data Tools видел? Там и Find Usages есть, и рефакторинг, причем не на уровне "поменяли колонку, смотрим, что сломалось", а нормальный такой. Да, динамический сиквел вроде не парсит, но может быть он и не нужен? Динамика нужна в первую очередь базданщикам, чтобы на сложных поисковых процедурах можно было использовать индексы нормально
4. Cпециалист по базам данных не знал, что при проектировании столбцы типа ModelCode никто не делает NVARCHAR?Это увеличивает информационный шум, а к Controllable Query не имеет ни малейшего отношения.
5. Где в твоём сравнении с ADO.NET валидация длины строки при использовании твоего решения? Добавь это в пример.Непосредственно это не входит в Controllable Query. Controllable Query лишь предлагает колбек для мепинга SQL типов на .NET типы.
Я ответил на все твои вопросы. Теперь твоя очередь. Лучше всего, если будет код.
Sql Server Data Tools видел?да, использовали в нескольких проектах.
Да, динамический сиквел вроде не парсит, но может быть он и не нужен?
мы тоже долго так себя обманывали. Нет, динамический sql нужен.
Кстати, даже для не динамического sql-я кода получается меньше (в Controllable Query). Задача: считать modelCode с консоли и вывести ModelId + 1 в консоль. Вот на Controllable Query. Приведи весь код в своем подходе.
рефакторинг, причем не на уровне "поменяли колонку, смотрим, что сломалось",я как раз размышляю над такой функциональностью в Controllable Query. Я не вижу принципиальных трудностей для реализации такого. Но есть сильно трудозатратные шаги: парсинг SQL и C#, тесная интеграция ы Visual Studio.
http://www.screencast.com/t/vKknGEffN6OПосле нескольких часов тренировки ты всё-таки сделал скринкаст, где на тривиальную задачу потратил две минуты. При этом твоя же библиотека отсылала тебя к номерам строк на C#, которые не связаны с ошибкой. Она же не смогла определить, что SQL ошибка теперь в клаузе FROM, оставшись на SELECT. Особенно порадовало изменение INNER JOIN и WRE, которые на самом деле на строке 40 и 47, а твоя библиотека показывает на строку 15.
Какую технология для работы с SQL СУБД мы будем использовать завтра в новом проекте? В 5-ом разделе есть некоторый список технологий, смотрим на него, сравниваем. ... Упс, а Controllable Query то нам лучше подходит.Ответ на вопрос какую технологию: уж точно не твою. Она совершенно никому не подходит, неудобна в использовании, поощеряет размножение гавнокода.
Пока "посетители этого раздела" не предложат свои варианты, разговаривать не о чем.Никто ничего тебе тут не предложит, здесь почти нет разработчиков, которые любят жевать кактусы и биться лбом об стену. Если тебе разговаривать с этим разделом не о чем, я предлагаю тебе молчать.
Я ответил на все твои вопросы. Теперь твоя очередь. Лучше всего, если будет код.
Моей очереди не будет. Если ты ещё не понял, то я не занимаюсь созданием какого-то фреймворка для работы с БД.
Задача: считать modelCode с консоли и вывести ModelId + 1 в консоль.консоль можешь заменить на HTML страничку, твой любимый UI-фраймворк, текстовый файл и т.д.
При этом твоя же библиотека отсылала тебя к номерам строк на C#, которые не связаны с ошибкой. Она же не смогла определить, что SQL ошибка теперь в клаузе FROM, оставшись на SELECT. Особенно порадовало изменение INNER JOIN и WRE, которые на самом деле на строке 40 и 47, а твоя библиотека показывает на строку 15., ты смешен, ты просто не хочешь понимать те сообщение, которые выдает моя тулза. Ну это твои личные проблемы. Вменяемым людям сообщения и номера строк понятны.
, ты смешен, ты просто не хочешь понимать те сообщение, которые выдает моя тулза. Ну это твои личные проблемы. Вменяемым людям сообщения и номера строк понятны.Вот именно, вменяемым людям они понятны.
И как мне тут переименовать столбец базы и алиас таблицы в запросе?Основная цель другая: при частых изменениях кода получить вменяемую гарантию, что код содержит минимум описок.
и эту цель сейчас достигает. Потерял, конечно, на простоте синтаксиса, но по другому на универсальном языке едва ли получится.
По поводу Find usages - я сам занимался подобной проблемой, но из-за нехватки времени забил. У меня часто стоит задача посмотреть, в каких процедурах меняется то или иное поле. Как показывает практика, тупой поиск по именам объекта и колонки дает не больше 10% левых результатов. Поиск просто по имени объекта ошибок почти не дает. Это все сделано на уровне базы, разумеется, + скрипты не надо писать, все сделано через SSMS Tools, т.е. я просто кликаю на колонке в таблице и делаю "find updates". Поиск идет по sys.sql_modules с простого поиска по маске, есть вариант с регулярными выражениями, но это надо отдельно деплоить, поэтому я остановился пока на простом поиске. Я, конечно, хочу сделать 100% поиск, но мне лень + не настолько хорошо знаю регулярки, чтобы написать это на все случаи жизни, поэтому 10% ошибок меня вполне устраивают (10% - максимум, при условии, что поле в процедуре не апдейтится, но все равно присутствует, или есть одноименное поле в другой таблице)
По поводу всего кода, который надо предоставить.. Писать свой код - а зачем? Во-первых, мы не пытаемся сравнить, кто круче кодит. Ты говоришь, что твой код круче аналогов, я вижу велосипед с квадратными колесами. Я могу быть неправ, но ты ж меня убедить не можешь. Ладно, я не ооп-девелопер, и действительно со многими проблемами не сталкиваюсь. Но остальные вроде как тоже того же мнения придерживаются. Во-вторых код я на дотнете пишу только тесты для своих процедур, и то редко, т.к. база почти не меняется. Соответственно, вызов процедур меня тоже не беспокоит - там ничего не сломается. Могу написать стандартный код для вызова процедуры, конечно. Он будет понятнее твоего, но ты скажешь, что там нет find usages + refactoring, ну ок, нету..
и эту цель сейчас достигает.В прошлых простынях, некоторым из которых уже год, мы все давно убедились, что без юнит тестов всё равно ничего не получается.
Потерял, конечно, на простоте синтаксиса, но по другому на универсальном языке едва ли получится.Потерял столько, что даже при полноценной реализации этой библиотеки её вряд ли можно будет использовать. Потому что быстрее будет написать и поддерживать простые запросы и полноценные юнит тесты, чем такой неизведанный кошмар. Цена слишком высокая - вот о чём уже сто раз все пытались аргументированно высказаться.
мы все давно убедились, что без юнит тестов всё равно ничего не получается.юнит тесты решают другую задачу.
Так же как статическая типизация не отменяет юнит-тесты, но здорово уменьшает их кол-во, и позволяет получить вменяемую гарантию на работоспособность кода при всех вариантах
задача code coverage опять же у Shirik-а легко решается.
зы
имхо, обычно над качеством кода никто не заморачивается (пользователи особо не пищат, ну и достаточно поэтому большинству и не понятно зачем какие-то телодвижения делает..
Могу написать стандартный код для вызова процедуры, конечно. Он будет понятнее твоегонапиши
Так же как статическая типизация не отменяет юнит-тесты, но здорово уменьшает их кол-во, и позволяет получить вменяемую гарантию на работоспособность кода при всех вариантахПредлагаемая библиотека пока что не может сократить количество юнит тестов, в чём все опять же могли убедиться в прошлогодних простынях.
имхо, обычно над качеством кода никто не заморачивается (пользователи особо не пищат, ну и достаточно поэтому большинству и не понятно зачем какие-то телодвижения делает..При таком усложнении синтаксиса процент ошибок намного выше, чем при использовании простых юнит тестов. Поэтому большинству не понятно, зачем нужен такой ад, если при 1% полезных действий он усложняет написание и поддержку кода на 200%.
может тебя просто в мсдн послать?
И да, я добавляю параметры через AddWithValue. Когда дергаешь процедуру, с планами проблем нет
может тебя просто в мсдн послать?а почему не написать?
При таком усложнении синтаксиса процент ошибок намного выше, чем при использовании простых юнит тестов.не все ошибки одинаково хероваты.
например, самое простое - это клеить sql-строки руками, но при таком способе большинство ошибок приводят к утечке данных клиентов на сторону.
зы
в данном случае, решается стандартная задача: ошибки из runtime-а перетаскиваются на этап разработки, а то что при этом суммарно ошибок становится больше (за счет более навороченного синтаксиса) - это не критично. Главное, что сильно падает кол-во ошибок уровня runtime.
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;
}
например, самое простое - это клеить sql-строки руками, но при таком способе большинство ошибок приводят к утечке данных клиентов на сторону.
В таком сложном синтаксисе вероятность появления подобных ошибок выше. Ты просто запутаешься где-нибудь в дебрях этого говна и в одном из путей забудешь прописать нужный if с добавлением какого-нибудь ограничения. В простом коде это сразу же поймают во время code review или во время разработки юнит тестов.
Замечу, что предлагаемое поделие никак с этой задачей не справляется, потому что не тестирует логику запросов. То есть для избежания этой плохой ситуации всё равно нужны полноценные юнит тесты.
в данном случае, решается стандартная задача: ошибки из runtime-а перетаскиваются на этап разработки, а то что при этом суммарно ошибок становится больше (за счет более навороченного синтаксиса) - это не критично. Главное, что сильно падает кол-во ошибок уровня runtime.Очевидно, что в данном случае эта задача не решается: проверки типов для этого не достаточно.
Ты просто запутаешься где-нибудь в дебрях этого говнаДопустим код:
return q.Select(_ => new {
id = _._<int>("a."
type_id = _._<int>("a.")
});
На самом деле работает и проходит все тесты, но должен был быть написан как:
return q.Select(_ => new {
id = _._<int>("a."
type_id = _._<int>("b.")
});
Причём в случае кода от Шурика места для изменения "a" на "b" два или больше, и тесты в обоих случаях проходят. Разработчик, работая с чужим плохо понятным кодом заменит в одном месте и не увидит во втором. Полагаясь на поделие, проведёт тестирование и вот тебе твои "данные клиентов" на блюдечке.
Очевидно, что в данном случае эта задача не решается: проверки типов для этого не достаточно.достаточность не нужна.
Необходимо лишь, чтобы инструменты кардинально уменьшали вероятность пролезания в runtime основных ограничений человека: забывчивость, леность, неточность(неряшливость) и т.д.
Необходимо лишь, чтобы инструменты кардинально уменьшали вероятность пролезания в runtime основных ограничений человека: забывчивость, леность, неточность и т.д.Очевидно, предлагаемое поделие кардинально увеличивают эту вероятность, особенно ленивость.
Очевидно, предлагаемое поделие кардинально увеличивают эту вероятность, особенно ленивость.если за каждую найденную plain-текст склейку вставляют, то леность сама проходят.
если за каждую найденную plain-текст склейку вставляют, то леность сама проходят.И при чём тут обсуждаемое нами поделие?
легко автоматизировать поиск "некошерного" доступа к БД
данный подход нужен когда sql-запросов в коде десятки тысяч, что нормально для бизнес-приложений, но редкость для других приложений.
если у тебя такого кол-ва запросов нет, то и данный подход тебе нафиг не уперся.
легко автоматизировать поиск "некошерного" доступа к БДКаким именно образом? Допустим, заебало меня делать всякие 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 проверяющему будет проще прочитать простой код или код на поделии? Не забывай, что поиск некошерного доступа тоже подпадает под ленивость, которая намного выше, когда приходится вычитывать тонну гавнокода.
данный подход нужен когда sql-запросов в коде десятки тысяч, что нормально для бизнес-приложений, но редкость для других приложений.Данный подход не нужен.
Каким именно образом?если правильно, то сначала константные строки похожие на sql (содержащие where, and, or и т.д.) пометить типом sql-string, а потом проверить, что два типа sql-string не складываются напрямую.
есть еще всякие приблизительные методы.
есть еще всякие приблизительные методы.У меня ощущение, что ты весьма приблизительно отвечаешь на мои посты....
большего оно не стоит.
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 в консоль.
В твоем коде нет НИ ОДНОГО элемента оплаченной задачи. Не говоря уже о полном решении.
Допустим, заебало меня делать всякие q_Q, и я написал вместо одного:Подходят оба варианта. Вообще, класс Q это обертка над StringBuilder c одной целью — автоматически вызывать IParam.ToSql. По желанию можешь отказаться от автоматического вызова и переложить эту ответственность на code review.
q._(" where 1 = 1");
if (d)
q._(" and a.type_id = 1");
другое:
q._(" where 1 = 1" + (d ? " and a.type_id = 1" : "";
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;
}
}
Подходят оба варианта.Нет, в нашем примере второй вариант не подходит.
Нет, в нашем примере второй вариант не подходит.почему?
Вот когда ты оплатишь, тогда тебе и будет решение твоей задачи. Пока же я отвечал тебе на другой вопрос, как выглядит мой код. Если тебе до сих пор принципиально узнать, как неспециалист разбирает cmd.ExecuteReader я таки попрошу оплаты - как будто мне делать больше нечего, чем перед идиотом распинаться "сделай то, сделай се", когда все уже написано (я на дотнете не пишу обычно, код очень близок к мсдн)
Вот когда ты оплатишь, тогда тебе и будет решение твоей задачи.Давай, называй цену.
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 сервере есть такие проблемы? Я бы сам проверил, но лень возиться с установкой.
Как видим, в не самом продвинутом постгресе все хорошо с планами такого рода препаренных запросов.раз на раз обычно не приходится. Чуть запрос усложнишь и хороший план летит к черту.
Чуть запрос усложнишь и хороший план летит к черту.Можно пример такого, что шуриковский план не летит к черту, а предложенный мной летит? Шурика волновало, что СУБД не делает constant folding для препаренных запросов. Из моего примера видно, что делает. Что еще надо?
Шурика волновало, что СУБД не делает constant folding для препаренных запросов.То, о чем ты говоришь, известная проблема. Почитай форумы и статьи. Суть там в использовании кеша планов запросов. Для подстановки констант серверу требуется разбор запроса, а он старается использовать уже готовый план из кеша. Есть специальный хинт на запрос для перекомпиляции каждый раз.
create table test as select id from generate_series(1,1000000) t(id);сделай нормальную таблицу с большим кол-вом колонок.
таблица с колонкой из одного ключа особая, т.к. индекс имеет данные по всему ряду, что не бывает для нормальных таблиц
Кстати, разработчики BLToolkit говорили, что подставляют константы при генерации SQL из LINQ. Но BLToolkit это BLToolkit
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
Кстати, разработчики BLToolkit говорили, что подставляют константы при генерации SQL из LINQэто вроде палка о двух концах. если подставлять константы, то каждый запрос становится уникальный, и у sql bd не получается использовать авто-кэширование планов запросов.
это вроде палка о двух концах. если подставлять константы, то каждый запрос становится уникальный, и у sql bd не получается использовать авто-кэширование планов запросов.они вроде там хитро делаю, вычисляют определенный класс выражений типа "@p IS NULL", а основную массу параметров отправляют на сервер как параметры. Это приводит к достаточно конечному множеству запросов.
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)
у меня вот уже такой запрос хочет table scanЗначит, для SQL сервера это не работает. По-видимому, это не является принципиальным ограничением (постгрес как-то это делает). Следовательно, всех устраивает конкатенация строк.
постгрес как-то это делаеторакл тоже
в очередной раз сделал для себя вывод - держаться подальше от MSSQL
получение колонок 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
в очередной раз сделал для себя вывод - держаться подальше от MSSQLв очередной раз не разобравшись в проблеме. Проблема общая и об этом везде пишут.
получение колонок id, foo, bar, baz не может быть выполнено только из индекса testi, т.к. там хранятся только колонки id, bar. Обязательно еще должен быть запрос к кластерному индексу для получения колонок foo, baz.См. в первой строке плана heap scan. Heap это и есть сама таблица. Постгрес сначала лезет в индекс, чтобы найти строки, которые подпадают под критерий, заданный в where, затем лезет в таблицу, чтобы достать столбцы, которых нет в индексе.
Либо testi является кластерным индексом, но тогда понятно почему план запросов всегда делается через него.
Обязательно еще должен быть запрос к кластерному индексу для получения колонок foo, baz.
в первой строке плана heap scan. Heap это и есть сама таблица.так понятнее, спасибо.
можешь дальше продолжать верить в магию.
можешь дальше продолжать верить в магию.Можешь дальше не лечиться от Microsoft-а головного мозга.
на примере посмотрел планы 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 по индексу
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
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; }
}
Сегодня я проверил это на постгресе:Долго думал как может работать эта чёрная магия.
Оказалось всё просто:
This is another situation where EXECUTE can be used to force a new plan to be generated for each execution.
Долго думал как может работать эта чёрная магия.т.е. магия не работает. Школота разочарована. Поставил плюс.
This is another situation where EXECUTE can be used to force a new plan to be generated for each execution.
Там, откуда ты взял эту цитату, речь идет о plpgsql execute, который не имеет никакого отношения к SQL execute, кроме названия.
вбей фразу в гугл
вот случайно натолкнулся, специально для вас всё расписали, как раз PostgreSQL
специально для вас всё расписали, как раз 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).
Это старая статья. Мой пример работает начиная с версии 9.2.Во-первых. Старая статья? Ты про 9.2, который еще не зарелизен?
Во-вторых, например, MS SQL Server вот такое "Now, the planner attempts to generate custom plans for specific parameter values" делает уже давно (и есть хинт для отключения этого). Но это проблему не решает. По моей ссылке объясняется, что проблема не разрешима в принципе. Там логическая цепочка из трех шагов, если ты ее не в состоянии понять, то донести до тебя эту информацию не предоставляется возможным.
проблема не разрешима в принципенаверное, можно делать ключи кеша с учетом значений параметров, выделяя специальные значения такие как NULL, но это усложнение, и пока ни одна СУБД так не делает.
explain execute testq(1234456);надо смотреть не так, а прям вытащить план из кеша (в MS SQL Server такое можно, думаю и в посгресе тоже).
Оставить комментарий
6yrop
InfoQ.com – хороший, годный ресурс.stackoverflow.com – такие обсуждения не подходят под их правила.
habrahabr.ru – имхо, сильно гноберский ресурс. Из полезного там только перепечатка из иностранных ресурсов.
rsdn.ru – ну тоже своя не очень конструктивная атмосфера.
Социальные сети — я там не бываю. Стоит заглянуть? в какую?