Partager via


Как импортировать конфигурацию Windows Firewall в SQL Server. Часть 2.

 В предыдущем решении (см. Скрипт 9 прошлой серии) есть одно но. В нем используется явный фиксированный список новых колонок Rule_Name, [Enabled], Direction, ... т.е. названия будущих элементов и атрбутов должны быть заранее известны, что применительно к нашему случаю выглядит не совсем гибко. В самом деле, список будущих колонок для свода я взял, посмотрев, из чего состоят первые несколько правил файрвола (см. колонка Name Рис.6 предыдущей серии). Но, во-первых, чем это лучше select … for xml, а во-вторых, кто, действительно, гарантировал, что в колонке Name не появится новое значение (которому должна соответствовать новая колонка в сводной таблице)? К сожалению, оператор pivot умеет оперировать только с фиксированным списком значений for in (…), по крайней мере, на данный момент (2008 R2). Это можно обойти при помощи динамического SQL. Получим в виде строки список всех уникальных значений колонки Name, разделенных запятыми, чтобы сформировать перечень for in (…) для оператора pivot. Я окружил каждое значение квадратными скобками на случай пробелов и других символов, не могущих выступать названиями колонок.

 

declare @s nvarchar(max) = stuff( (select distinct ', [' + name + ']' from #t order by 1 for xml path('')), 1, 2, '')

select @s

image

Рис.1

 

Тогда Скрипт 9 предыдущей серии можно переписать в виде динамического SQL:

 

if OBJECT_ID('FirewallRules', 'U') is not null drop table FirewallRules

declare @s nvarchar(max) = stuff( (select distinct ', [' + name + ']' from #t order by 1 for xml path('')), 1, 2, '')

exec ( 'select * into FirewallRules from (select ISNULL(parent_id, id) ID, name, value from #t) t pivot (min(value) for name in (' + @s + ')) p' )

 

image

Рис.2

 

И обещаный XML получается, например, непринужденно так:

 

select * from FirewallRules for xml path('Rule'), root('FirewallRules')

 

image

Рис.3

 

Въедливые читатели заметят, что в Скрипте 8 предыдущего поста имя правила я обещал сделать атрибутом родительского элемента <Rule>, а здесь он получился в виде дочернего элемента Rule_Name. Кроме того, внутри Rule имеется дочерний элемент ID, который я в предыдущем посте вообще не обещал, он как-то сам получился. Надо удалить у всех Rules элемент ID, а элемент Rule_Name перенести в атрибут Name.

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

 

declare @x xml = '<Persons>

  <Person ID="aaa">

    <Property1>aaa1</Property1>

    <Property2>aaa2</Property2>

  </Person>

  <Person ID="bbb">

    <Property1>bbb1</Property1>

    <Property2>bbb2</Property2>

  </Person>

  <Person ID="ccc">

    <Property1>ccc1</Property1>

    <Property2>ccc2</Property2>

  </Person>

</Persons>'

select @x.query('for $x in Persons/Person[@ID >= "b"] return <Contact ID="{$x/@ID}">{concat($x/Property1[1], "+", $x/Property2[1])}</Contact>')

 

image

Рис.4

 

Я не хочу явно перечислять элементы <Action>, <Direction> и другие свойства файрвольного правила, тем более, что их полный список можно составить, только просмотрев все контейнерные элементы <Rule>. От нас требуется всего лишь удалить один дочерний элемент, а другой сделать атрибутом. Но это нужно сделать для всех содержащих их элементов <Rule>, то есть, по сути, преобразовать схему XML-документа. Что касается преобразования схемы, например, добавления, удаления, переименования и замены элементов и атрибутов, таких возможностей в XQuery 1.0 в явном виде не предусматривается. Они были предложены, в частности, в статье «Extending XQuery with Transformation Operators» и вошли в дополнительную спецификацию XQuery Update Facility, получившую статус Candidate Recommendation (к внесению в XQuery) в марте 2008 г. В SQL Server 2008 эти расширения не поддерживаются. По сравнению с 2005 в нем добавилось, по сути, две функции: upper-case() и lower-case(). Также добавилась поддержка оператора LET, т.е. из акронима FLOWR теперь формально поддерживаются все буквы:

 

select @x.query('for $x in Persons/Person let $y := concat($x/Property1[1], "+", $x/Property2[1]) where $x/@ID >= "b" order by $x/@ID descending return <Contact ID="{$x/@ID}">{$y}</Contact>')

 

image

Рис.5

 

Хотя, например, сконструировать элемент при помощи let на данный момент в SQL Server не удается:

 

select @x.query('let $y := (<aaa>1</aaa>) return $y')

 

image

Рис.6

 

Вычисляемые конструкторы также не поддерживаются. Я имею в виду динамическое конструирование узла при помощи ключевых слов element, attribute и т.д. Например, вместо

 

select cast('' as xml).query('

<aaa bbb="ccc">

 ddd

 <aaa1 bbb1="ccc1">

  ddd1

  <aaa2>

   ddd2

  </aaa2>

 </aaa1>

</aaa>

')

 

можно написать

 

select cast('' as xml).query('

element aaa { attribute bbb{"ccc"}, text{"ddd"}, element aaa1{attribute bbb1{"ccc1"}, text{"ddd1"}, element aaa2{"ddd2"} } }

')

 

image

Рис.7

 

Однако в имени узла на данный момент разрешено использовать только константы. Попытка поставить выражение приводит к ошибке

 

select cast('' as xml).query('

element {concat("aa", "a")} { attribute bbb{"ccc"}, text{"ddd"}, element aaa1{attribute bbb1{"ccc1"}, text{"ddd1"}, element aaa2{"ddd2"} } }

')

 

image

Рис.8

 

Рекомендованный обходной путь, как легко догадаться, это снова использовать динамический SQL. Возможности, предоставляемые XQuery для трансформации схемы XML скудны, но и из них поддерживается только подмножество. Список поддерживаемых функций из стандарта XQuery можно посмотреть, например, здесь. В SQL Server 2008 R2 этот список по сравнению с 2008 не увеличился.

 

Помимо метода query, выполняющего XQuery-запрос, у XMLного типа SQL Server имеется метод modify, который позволяет вставлять и удалять узлы:

 

declare @x xml = '<Persons>

  <Person ID="aaa">

    <Property1>aaa1</Property1>

    <Property2>aaa2</Property2>

  </Person>

  <Person ID="bbb">

    <Property1>bbb1</Property1>

    <Property2>bbb2</Property2>

  </Person>

  <Person ID="ccc">

    <Property1>ccc1</Property1>

    <Property2>ccc2</Property2>

  </Person>

</Persons>'

set @x.modify('insert attribute Name { "nnn" } into (/Persons/Person)[1]')

set @x.modify('insert element Property3 { "aaa3" } into (/Persons/Person[@ID="aaa"])[1]')

declare @y xml = '<Property0>bbb0</Property0>'

set @x.modify('insert sql:variable("@y") before (/Persons/Person[2]/Property1)[1]')

set @x.modify('delete /Persons/Person[3]/Property1[1]')

set @x.modify('delete /Persons/Person[3]/Property2[1]/text()')

set @x.modify('replace value of (/Persons/Person[2]/@ID)[1] with "bbbbb"')

set @x.modify('replace value of (/Persons/Person[2]/Property1/text())[1] with "bbbbb1"')

select @x

 

image

Рис.9

 

Однако в отличие от кандидатской рекомендации XQuery Update Facility, где операции insert, delete, replace и пр. ориентированы на изменение схемы документа, метод modify изменяет избранные узлы. Он не позволяет добавить или удалить узел сразу у всех элементов XML-документа:

 

set @x.modify('insert attribute Age { "0" } into /Persons/Person')

------------------------------------

Msg 2226, Level 16, State 1, Line 25

XQuery [modify()]: The target of 'insert' must be a single node, found 'element(Person,xdt:untyped) *'

 

Здесь предлагаются обходные пути через select for xml или замену подстроки в строковой конвертации XML. Ни тот, ни другой способ не являются полноценной заменой отсутствия встроенных механизмов преобразования структуры XML-документа.

 

Что касается исходной задачи (Рис.3), требуемые преобразования достаточно просты и могут быть выполнены средствами XQuery:

 

select cast((select * from FirewallRules for xml path('Rule'), root('FirewallRules')) as XML).query('

<FW>

{ for $rule in FirewallRules/Rule return <Rule Name="{$rule/Rule_Name}">

  { for $ruleprop in $rule/*[position() > 1] where local-name($ruleprop) != "Rule_Name" return $ruleprop

  } </Rule>

} </FW>')

 

 

image

Рис.10

 

В первом цикле $rule пробегается по всем элементам Rule и для каждого на выходе создается элемент Rule с атрибутом Name. Значение этого атрибута берется из дочернего элемента Rule_Name текущего элемента Rule в исходном XML. Вложенный цикл с параметром $ruleprop пробегается по всем дочерним элементам текущего элемента Rule (который сейчас находится в параметре внешнего цикла $rule), после первого. Первым дочерним элементом в каждом Rule идет ID, мы его опускаем, то есть не переносим в результат, то есть, грубо говоря, удаляем. Дополнительным фильтром (where) во внутреннем цикле выступает условие, что если имя дочернего элемента равно «Rule_Name», то мы его тоже пропускаем, потому что его значение мы уже положили в атрибут Name родительского элемента. Все остальные дочерние элементы ($ruleprop) переносятся как есть.

 

Окончание следует.

 

 

Алексей Шуленин