Как установить многозначный атрибут XML с использованием SQLXML-модификации
Я пытаюсь использовать функцию изменения 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 ответ
Должен признать, мне никогда не приходилось сталкиваться с этим раньше...
И я должен признать, что я не нашел легкого решения. Если вы найдете это, пожалуйста, дайте мне знать.
Даже работа с литералами приводит к той же проблеме: строка приводится к перечислению в целом, которое не соответствует ни одному из допустимых значений, поэтому возвращается как пустое.
Но вы можете сделать
replace value of (/A:AccessControlList/A:AccessControlRecord/@Permissions)[1]
with for $p in ("DenyCreate","DenyUpdate","DenyDelete")
return $p cast as A:AccessPermissions ?
Это будет использовать XQuery for
пробежаться по списку и вернуть каждое значение одно за другим, каждое приведено к нужному типу отдельно.
Но я нашел единственный способ использовать это с внешней переменной - это динамический 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);