Список, разделенный запятыми и кодогенерация
От: rg45 СССР  
Дата: 08.06.14 06:48
Оценка:
Доброго дня всем,

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

Итак, суть проблемы на синтетическом примере. Допустим, требуется сгенерировать SQL код такого запроса:

select
  foo, bar, baz, qux, quux, corge
from a.table

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

select
--,
  foo, 
  bar, 
  baz, 
  qux, 
  quux, 
  corge
from a.table

А скрипт, например, генератора T4 как-то так:

select
--<#
foreach (name in names)
{
#>,
  <#=name#>
<#
}
#>
from a.table

Этот способ не нравится во-первых, тем, что создает лишние сложности для того, кто будет поддерживать эти скрипты. А во-вторых, тем, что он не универсален и работает только в тех случаях, когда вообще предусмотрены какие-либо однострочные комментарии типа "//" и "--".

Буду благодарен за любые идеи.
Я жду, когда этот пидарас
Автор: Bj777x
Дата: 22.06.20
либо удалит свое хамство здесь
Автор: Bj777x
Дата: 14.10 21:26
и здесь
Автор: Bj777x
Дата: 27.09 11:34
, либо восстановит ответы.
Re: Список, разделенный запятыми и кодогенерация
От: Tissot Россия  
Дата: 08.06.14 07:28
Оценка: 11 (1)
Здравствуйте, rg45, Вы писали:

R>А скрипт, например, генератора T4 как-то так:


R>
R>select
R>--<#
R>foreach (name in names)
R>{
R>#>,
R>  <#=name#>
R><#
R>}
R>#>
R>from a.table
R>


а если так?
select
--<#
var separators = new[] {""}.Concat(Enumerable.Repeat(", ", int.MaxValue));
foreach (name in names.Zip(separators, (n, s) => s+n))
{
#>
  <#=name#>
<#
}
#>
from a.table
Re[2]: Список, разделенный запятыми и кодогенерация
От: rg45 СССР  
Дата: 08.06.14 07:43
Оценка:
Здравствуйте, Tissot, Вы писали:

T>а если так?

T>
T>select
T>--<#
T>var separators = new[] {""}.Concat(Enumerable.Repeat(", ", int.MaxValue));
T>foreach (name in names.Zip(separators, (n, s) => s+n))
T>{
T>#>
T>  <#=name#>
T><#
T>}
T>#>
T>from a.table
T>




В реальных скриптах элемент списка, как правило, сложнее, чем просто имя, например:

CREATE TYPE dbo.ADP_<#=rowset.dbName#>UDTT AS TABLE
(
--<#
foreach (var row in rowset.GetADPViewByRows(
   (field, row, column) => new { name = field.dbName, type = field.dbType, row }))
{#>,
--- Row <#=row.Key + 1#><#
   foreach (var field in row)
   {
#>,
   <#=field.name#> <#=field.type#> null, Update<#=field.name#> bit not null<#
   }
}#>

Но идея, тем не менее, здравая, можно додумать. Спасибо.
Я жду, когда этот пидарас
Автор: Bj777x
Дата: 22.06.20
либо удалит свое хамство здесь
Автор: Bj777x
Дата: 14.10 21:26
и здесь
Автор: Bj777x
Дата: 27.09 11:34
, либо восстановит ответы.
Re: Список, разделенный запятыми и кодогенерация
От: LuciferSaratov Россия  
Дата: 08.06.14 08:15
Оценка:
Здравствуйте, rg45, Вы писали:

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


идея еще такая: если приходится заниматься генерацией не только SQL, а кода на разных языках, то стоит проверять, поддерживает ли язык лишние запятые в списках.
потому что, например, в C++ вот такой код будет правильным:
enum names{
  foo, 
  bar, 
  baz, 
  qux, 
  quux, 
  corge,  // <- запятая, которая ничего не разделяет.
};

и насколько мне известно, такой синтаксис поддерживается как раз из соображений упрощения кодогенерации.
Re[2]: Список, разделенный запятыми и кодогенерация
От: rg45 СССР  
Дата: 08.06.14 08:27
Оценка:
Здравствуйте, LuciferSaratov, Вы писали:


LS>идея еще такая: если приходится заниматься генерацией не только SQL, а кода на разных языках, то стоит проверять, поддерживает ли язык лишние запятые в списках.

LS>потому что, например, в C++ вот такой код будет правильным:
LS>
LS>enum names{
LS>  foo, 
LS>  bar, 
LS>  baz, 
LS>  qux, 
LS>  quux, 
LS>  corge,  // <- запятая, которая ничего не разделяет.
LS>};
LS>

LS>и насколько мне известно, такой синтаксис поддерживается как раз из соображений упрощения кодогенерации.

Конечно, эта возможность тоже местами используется, но, опять же, не хватает общности — в том же C/С++ замыкающая запятая допускается при определении массивов и перечислимых типов, но не допускается для задания списков формальных и фактических параметров функций, шаблонов и макросов.
Я жду, когда этот пидарас
Автор: Bj777x
Дата: 22.06.20
либо удалит свое хамство здесь
Автор: Bj777x
Дата: 14.10 21:26
и здесь
Автор: Bj777x
Дата: 27.09 11:34
, либо восстановит ответы.
Re[2]: Delimiters vs. separators
От: Qbit86 Россия
Дата: 08.06.14 08:47
Оценка: +1
Здравствуйте, Tissot, Вы писали:

T>а если так?

T>
T>select
T>--<#
T>var separators = new[] {""}.Concat(Enumerable.Repeat(", ", int.MaxValue));
T>foreach (name in names.Zip(separators, (n, s) => s+n))
T>{
T>#>
T>  <#=name#>
T><#
T>}
T>#>
T>from a.table
T>


var names = new[] { "foo", /* "bar", "baz", */ }.Select(n => ...);
var join = String.Join(",\n", names);
Глаза у меня добрые, но рубашка — смирительная!
Re[3]: Delimiters vs. separators
От: rg45 СССР  
Дата: 08.06.14 09:20
Оценка:
Здравствуйте, Qbit86, Вы писали:

Q>
Q>var names = new[] { "foo", /* "bar", "baz", */ }.Select(n => ...);
Q>var join = String.Join(",\n", names);
Q>


Да, я тоже сразу про join подумал.
Я жду, когда этот пидарас
Автор: Bj777x
Дата: 22.06.20
либо удалит свое хамство здесь
Автор: Bj777x
Дата: 14.10 21:26
и здесь
Автор: Bj777x
Дата: 27.09 11:34
, либо восстановит ответы.
Re[2]: Список, разделенный запятыми и кодогенерация
От: rg45 СССР  
Дата: 08.06.14 09:52
Оценка:
Здравствуйте, Tissot, Вы писали:

T>а если так?

T>
T>select
T>--<#
T>var separators = new[] {""}.Concat(Enumerable.Repeat(", ", int.MaxValue));
T>foreach (name in names.Zip(separators, (n, s) => s+n))
T>{
T>#>
T>  <#=name#>
T><#
T>}
T>#>
T>from a.table
T>



Есть один не очень приятный момент во всех решениях подобного рода: некоторая часть контента переходит из непосредственного тела скрипта в строковые переменные. Тут вот какое дело, допустим, для таблицы требуется сгенерировать секцию "order by" для сортировки по primary key, если таковой задан. А если ключ не задан, то эта секция должна отсутствовать. Поскольку ключ может быть составным, то секция может выглядеть как-то так:

order by foo, bar, baz

Решение напрашивает само собой: можно сгенерировать для первого элемента префикс "order by ", а для остальных — ", ". После чего желаемый эффект достигается автоматически простым использованием цикла foreach. Этот принцип можно применять и в более сложных случаях — создание хранимых процедур, табличных типов и т.д. В этом случае сложность первого префикса становится практически неограниченной. И заталкивание этого контента в строковые переменные может ухудшать наглядность скриптов.
Я жду, когда этот пидарас
Автор: Bj777x
Дата: 22.06.20
либо удалит свое хамство здесь
Автор: Bj777x
Дата: 14.10 21:26
и здесь
Автор: Bj777x
Дата: 27.09 11:34
, либо восстановит ответы.
Re[3]: Список, разделенный запятыми и кодогенерация
От: Qbit86 Россия
Дата: 08.06.14 09:59
Оценка: 11 (1)
Здравствуйте, rg45, Вы писали:

R>Есть один не очень приятный момент во всех решениях подобного рода: некоторая часть контента переходит из непосредственного тела скрипта в строковые переменные. Тут вот какое дело, допустим, для таблицы требуется сгенерировать секцию "order by" для сортировки по primary key, если таковой задан. А если ключ не задан, то эта секция должна отсутствовать. Поскольку ключ может быть составным, то секция может выглядеть как-то так:

R>
R>order by foo, bar, baz
R>

R>Решение напрашивает само собой: можно сгенерировать для первого элемента префикс "order by ", а для остальных — ", ".

Не, «order by» пихать в префикс это лишнее. В T4 сморишь, `if` есть ключ, то добавляешь секцию сортировки, в параметры передавая зип ограничителей с аргументами, где первый ограничитель пустой, а остальные — разделители.
Глаза у меня добрые, но рубашка — смирительная!
Re[4]: Список, разделенный запятыми и кодогенерация
От: rg45 СССР  
Дата: 08.06.14 10:44
Оценка:
Здравствуйте, Qbit86, Вы писали:


Q>Не, «order by» пихать в префикс это лишнее. В T4 сморишь, `if` есть ключ, то добавляешь секцию сортировки, в параметры передавая зип ограничителей с аргументами, где первый ограничитель пустой, а остальные — разделители.


Да, пожалуй, это и есть та самая "золотая середина" в данном случае. Спасибо.
Я жду, когда этот пидарас
Автор: Bj777x
Дата: 22.06.20
либо удалит свое хамство здесь
Автор: Bj777x
Дата: 14.10 21:26
и здесь
Автор: Bj777x
Дата: 27.09 11:34
, либо восстановит ответы.
Re: Вот, что получилось
От: rg45 СССР  
Дата: 08.06.14 12:23
Оценка:
Здравствуйте, rg45, Вы писали:

Ну что ж, полученным результатом я вполне удовлетворен.

CREATE TYPE dbo.ADP_<#=rowset.dbName#>UDTT AS TABLE
(
<#
var delimiters = new[] {"   ", ",  "}.Select((delimiter, order) => new {delimiter, selector = (order != 0)});

foreach (var row in
   from field in
     from field in rowset.Fields
     orderby field.adpRow, field.adpColumn
     join delimiter in delimiters on field.order != 0 equals delimiter.selector
     select new { dbName = field.dbName, dbType = field.dbType, adpRow = field.adpRow, delimiter.delimiter }
   group field by field.adpRow)
{
#>
--- Row <#=row.Key + 1#>
<#
   foreach (var field in row)
   {
#>
<#=field.delimiter#><#=field.dbName#> <#=field.dbType#> null, Update<#=field.dbName#> bit not null
<#
   }
}
#>
)


  Сгенерированный код
CREATE TYPE dbo.ADP_DBTable98UDTT AS TABLE
(
--- Row 1
   Column_20_10 varchar(max) null, UpdateColumn_20_10 bit not null
,  Column_20_40 varchar(max) null, UpdateColumn_20_40 bit not null
,  Column_20_70 varchar(max) null, UpdateColumn_20_70 bit not null
,  Column_20_0 int null, UpdateColumn_20_0 bit not null
,  Column_20_20 int null, UpdateColumn_20_20 bit not null
,  Column_20_30 int null, UpdateColumn_20_30 bit not null
,  Column_20_50 int null, UpdateColumn_20_50 bit not null
,  Column_20_60 int null, UpdateColumn_20_60 bit not null
,  Column_20_80 int null, UpdateColumn_20_80 bit not null
,  Column_20_90 int null, UpdateColumn_20_90 bit not null
--- Row 2
,  Column_20_100 varchar(max) null, UpdateColumn_20_100 bit not null
,  Column_20_130 varchar(max) null, UpdateColumn_20_130 bit not null
,  Column_20_160 varchar(max) null, UpdateColumn_20_160 bit not null
,  Column_20_110 int null, UpdateColumn_20_110 bit not null
,  Column_20_120 int null, UpdateColumn_20_120 bit not null
,  Column_20_140 int null, UpdateColumn_20_140 bit not null
,  Column_20_150 int null, UpdateColumn_20_150 bit not null
,  Column_20_170 int null, UpdateColumn_20_170 bit not null
,  Column_20_180 int null, UpdateColumn_20_180 bit not null
,  Column_20_200 int null, UpdateColumn_20_200 bit not null
--- Row 3
,  Column_20_190 varchar(max) null, UpdateColumn_20_190 bit not null
,  Column_20_220 varchar(max) null, UpdateColumn_20_220 bit not null
,  Column_20_210 int null, UpdateColumn_20_210 bit not null
,  Column_20_230 int null, UpdateColumn_20_230 bit not null
,  Column_20_240 int null, UpdateColumn_20_240 bit not null
)


Спасибо ответившим!
Я жду, когда этот пидарас
Автор: Bj777x
Дата: 22.06.20
либо удалит свое хамство здесь
Автор: Bj777x
Дата: 14.10 21:26
и здесь
Автор: Bj777x
Дата: 27.09 11:34
, либо восстановит ответы.
Re[2]: Вот, что получилось
От: rg45 СССР  
Дата: 08.06.14 12:55
Оценка:
Здравствуйте, rg45, Вы писали:

R>Ну что ж, полученным результатом я вполне удовлетворен.


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

var delimiters = new[] {"   ", ",  "}.Select((delimiter, order) => new {delimiter, selector = (order != 0)});

Есть предложения?
Я жду, когда этот пидарас
Автор: Bj777x
Дата: 22.06.20
либо удалит свое хамство здесь
Автор: Bj777x
Дата: 14.10 21:26
и здесь
Автор: Bj777x
Дата: 27.09 11:34
, либо восстановит ответы.
Re: Список, разделенный запятыми и кодогенерация
От: AleksandrN Россия  
Дата: 09.06.14 05:26
Оценка:
Здравствуйте, rg45, Вы писали:

R>
R>select
R>  foo, bar, baz, qux, quux, corge
R>from a.table
R>


1. При создании списка колонок, ставить запятую после названия колонки, а после создания списка — стирать последний символ.

2. Создавать название колонки из двух переменных, первую переменную инициализировать пробелом, а после первой итерации — заменить на запятую. Т.е — получится <пробел> foo, bar, baz, qux, quux, corge
Re: Список, разделенный запятыми и кодогенерация
От: wildwind Россия  
Дата: 09.06.14 08:52
Оценка:
Здравствуйте, rg45, Вы писали:

R>Буду благодарен за любые идеи.


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

(Все ответы не читал.)
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.