Как установить многозначный атрибут XML с использованием SQLXML-модификации - PullRequest
0 голосов
/ 11 декабря 2018

Я пытаюсь использовать функцию изменения SQLXML для обновления атрибута с несколькими значениями (xs: list).Я могу установить несколько значений при построении XML (из строки), но изменение SQLXML не позволяет мне устанавливать несколько значений.

Исходный XML:

<AccessControlList xmlns="http://www.acme.com/Authorization/2013/01">
  <AccessControlRecord Permissions="Fullcontrol" />
  <AccessControlRecord Permissions="DenyCreate DenyRead DenyUpdate DenyDelete" />
</AccessControlList>

Установка значения SINGLEотлично работает:

DECLARE @SingleValue NVARCHAR(100) = 'DenyCreate';
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
UPDATE dbo.Widget
SET ACL.modify('replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] with sql:variable("@SingleValue") cast as A:AccessPermissions ?')
FROM dbo.Widget;

Установка нескольких значений FAILS :

DECLARE @MultipleValues NVARCHAR(100) = 'DenyCreate DenyRead DenyUpdate DenyDelete';
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
UPDATE dbo.Widget
SET ACL.modify('replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] with sql:variable("@MultipleValues") cast as A:AccessPermissions ?')
FROM dbo.Widget;

с этой ошибкой:

XQuery: Replacing the value of a node with an empty sequence is allowed only if '()' is used as the new value expression. The new value expression evaluated to an empty sequence but it is not '()'.

Переменная не равна нулюили пусто.Я также пробовал другие варианты, которые не с разными ошибками.

Полный SQL для воспроизведения:

-- Drop table and schema collection
IF OBJECT_ID('dbo.Widget') IS NOT NULL
    DROP TABLE dbo.Widget;
IF EXISTS ( SELECT * FROM sys.xml_schema_collections WHERE SCHEMA_NAME(schema_id) = 'dbo' AND name = 'AccessControlList' )
    DROP XML SCHEMA COLLECTION dbo.AccessControlList;
GO

-- Create schema collection
CREATE XML SCHEMA COLLECTION dbo.AccessControlList AS N'
<xs:schema id="AccessControlList" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.acme.com/Authorization/2013/01" xmlns="http://www.acme.com/Authorization/2013/01">
    <xs:simpleType name="AccessPermissions">
        <xs:list>
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:enumeration value="Create" />
                    <xs:enumeration value="Read" />
                    <xs:enumeration value="Update" />
                    <xs:enumeration value="Delete" />
                    <xs:enumeration value="Execute" />
                    <xs:enumeration value="Fullcontrol" />
                    <xs:enumeration value="DenyCreate" />
                    <xs:enumeration value="DenyRead" />
                    <xs:enumeration value="DenyUpdate" />
                    <xs:enumeration value="DenyDelete" />
                    <xs:enumeration value="DenyExecute" />
                    <xs:enumeration value="FullDeny" />
                </xs:restriction>
            </xs:simpleType>
        </xs:list>
    </xs:simpleType>
    <xs:complexType name="AccessControlRecord">
        <xs:attribute name="Permissions" type="AccessPermissions" use="required" />
    </xs:complexType>
    <xs:complexType name="AccessControlList">
        <xs:sequence>
            <xs:element minOccurs="0" maxOccurs="unbounded" name="AccessControlRecord" type="AccessControlRecord" />
        </xs:sequence>
    </xs:complexType>
    <xs:element name="AccessControlList" nillable="true" type="AccessControlList" />
</xs:schema>
';
GO

-- Create table, insert test data, and display initial state of data
CREATE TABLE dbo.Widget
(
    WidgetId INT PRIMARY KEY IDENTITY(1,1),
    ACL XML(DOCUMENT dbo.AccessControlList)
);
INSERT INTO dbo.Widget
    ( ACL )
VALUES
    ( N'<AccessControlList xmlns="http://www.acme.com/Authorization/2013/01" >
            <AccessControlRecord Permissions="Fullcontrol" />
            <AccessControlRecord Permissions="DenyCreate DenyRead DenyUpdate DenyDelete" />
        </AccessControlList>' );
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
SELECT *
    ,Acr1Permissions = CAST(ACL AS XML).value('(/A:AccessControlList/A:AccessControlRecord)[1]/@Permissions', 'NVARCHAR(128)')
    ,Acr2Permissions = CAST(ACL AS XML).value('(/A:AccessControlList/A:AccessControlRecord)[2]/@Permissions', 'NVARCHAR(128)')
FROM dbo.Widget;

-- Setting a SINGLE value works fine
DECLARE @SingleValue NVARCHAR(100) = 'DenyCreate';
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
UPDATE dbo.Widget
SET ACL.modify('replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] with sql:variable("@SingleValue") cast as A:AccessPermissions ?')
FROM dbo.Widget;

-- Display values after
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
SELECT *
    ,Acr1Permissions = CAST(ACL AS XML).value('(/A:AccessControlList/A:AccessControlRecord)[1]/@Permissions', 'NVARCHAR(128)')
    ,Acr2Permissions = CAST(ACL AS XML).value('(/A:AccessControlList/A:AccessControlRecord)[2]/@Permissions', 'NVARCHAR(128)')
FROM dbo.Widget;

/* Setting MULTIPLE values *FAILS*
DECLARE @MultipleValues NVARCHAR(100) = 'DenyCreate DenyRead DenyUpdate DenyDelete';
WITH XMLNAMESPACES ( 'http://www.acme.com/Authorization/2013/01' AS A )
UPDATE dbo.Widget
SET ACL.modify('replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] with sql:variable("@MultipleValues") cast as A:AccessPermissions ?')
FROM dbo.Widget;
*/

Я получаю ту же ошибку, когда я пытаюсь установить несколько значений, используя "sql: column".

Я нашел этот ресурс (https://docs.microsoft.com/en-us/sql/xquery/type-casting-rules-in-xquery?view=sql-server-2017), в котором говорится, что приведение к типам списков или из них не разрешено; я надеюсь, что есть решение или обходной путь.

Возможно ли это с помощью SQLXML?Как?

Заранее спасибо

1 Ответ

0 голосов
/ 17 декабря 2018

Должен признать, мне никогда не приходилось сталкиваться с этим раньше ...

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

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

Но вы можете сделать

replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] 
with for $p in ("DenyCreate","DenyUpdate","DenyDelete") 
     return $p cast as A:AccessPermissions ?

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

Но я нашел единственный способ использовать это с внешней переменной - это динамический SQL.Так что это работает, но довольно уродливо:

DECLARE @MultipleValues VARCHAR(100)='DenyCreate DenyUpdate DenyDelete';

DECLARE @cmd NVARCHAR(MAX)=
'WITH XMLNAMESPACES ( ''http://www.acme.com/Authorization/2013/01'' AS A )
 UPDATE dbo.Widget
 SET ACL.modify(''replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1] with for $p in ("' + REPLACE(@MultipleValues,' ','","') + '") return $p cast as A:AccessPermissions ?'')
 FROM dbo.Widget;';

EXEC(@cmd);
...