搜索​​​​

清除过滤器
文章
姚 鑫 · 八月 22, 2022

第九章 配置数据库(一)

# 第九章 配置数据库(一) 数据库是使用数据库向导创建的 `IRIS.DAT` 文件。 `IRIS`数据库保存称为全局变量的多维数组中的数据和称为例程的可执行内容,以及类和表定义。 全局变量和例程包括方法、类、网页、SQL、BASIC和JavaScript文件 **注意:在 `Windows` 系统上,不要对 `IRIS.DAT` 数据库文件使用文件压缩。 (通过右键单击 `Windows` 资源管理器中的文件或文件夹并选择属性,然后选择高级,然后压缩内容以节省磁盘空间来压缩文件;压缩后,文件夹名称或文件名在 `Windows` 资源管理器中呈现为蓝色。)如果压缩`IRIS.DAT` 文件,它所属的实例将无法启动,并出现误导性错误。** `IRIS` 数据库根据需要动态扩展(假设有可用空间),但可以指定最大大小。如果使用默认的 `8KB` 块大小,数据库可以增长到 `32 TB`。 可以动态更改大多数数据库配置;可以在系统运行时创建和删除数据库以及修改数据库属性。 **注意:这些主题描述了使用管理门户手动配置数据库的过程。 `IRIS` 还包含可用于自动化数据库配置的编程工具。可以使用新选项卡类中的 `Config.Databases` 来创建和配置数据库;还可以使用 `^DATABASE` 命令行实用程序配置数据库。** **配置数据库的另一种方法是将 `CreateDatabase`、`ModifyDatabase` 或 `DeleteDatabase` 操作与配置合并结合使用。配置合并允许通过应用声明性合并文件来自定义 `IRIS` 实例,该文件指定要应用于该实例的设置和操作。** # Background `IRIS` 将数据——持久多维数组(`globals`)以及可执行代码(例程)——存储在一个或多个称为数据库的物理结构中。数据库由存储在本地操作系统中的一个或多个物理文件组成。一个 `IRIS` 系统可能(并且通常确实)有多个数据库。 每个 `IRIS` 系统都维护一个数据库缓存——一个本地共享内存缓冲区,用于缓存从物理数据库中检索到的数据。这种高速缓存大大减少了访问数据所需的昂贵 `I/O` 操作的数量,并提供了 `IRIS` 的许多性能优势。 `IRIS` 应用程序通过命名空间访问数据。命名空间提供存储在一个或多个物理数据库中的数据(全局变量和例程)的逻辑视图。一个 `IRIS` 系统可能(并且通常确实)有多个命名空间。 `IRIS` 将逻辑命名空间中可见的数据映射到一个或多个物理数据库。这种映射为应用程序提供了一种强大的机制,可以在不更改应用程序逻辑的情况下更改应用程序的物理部署。 在最简单的情况下,命名空间和数据库之间存在一一对应关系,但许多系统利用定义命名空间的能力来提供对多个数据库中数据的访问。例如,一个系统可以有多个命名空间,每个命名空间提供存储在一个或多个物理数据库中的数据的不同逻辑视图。 # 数据库注意事项 ## 数据库总限制 可以在单个 `IRIS` 实例中配置的数据库数量的绝对限制(如果有足够的存储空间)是 `15,998`。其他限制如下: - 数据库的目录信息不能超过 `256 KB`。这意味着,如果数据库目录名称的平均长度较长,则实例可以拥有较少的数据库总数。以下公式描述了这种关系: ```math maximum_DBs = 258048/ (avg_DB_path_length + 3) ``` 例如,如果所有数据库目录路径的格式为 `c:\InterSystems\IRIS\mgr\DBNNNN\`,则平均长度为 `33` 个字节。因此,最大数据库数为 `7,168`,计算如下:`258048/ (33 + 3) = 7168`。 - 镜像数据库在 `15,998` 的绝对限制中计数两次。如果实例上的所有数据库都进行了镜像,则有效限制为 `7,499` 个数据库。这是因为 `IRIS` 为镜像数据库创建了两个数据库定义;一个用于目录路径 (`c:\InterSystems\IRIS\mgr\DBNNNN\`),另一个用于镜像定义 (`:mirror:MIRRORNAME:MirrorDBName`)。 - 可以同时使用的数据库数量受操作系统对打开文件数量(每个进程或系统范围)的限制的限制。 `IRIS` 将大约一半的操作系统打开文件分配留给自己和设备使用。 ## 数据库配置注意事项 以下是配置数据库时要考虑的提示: - `IRIS` 提供了一个无缝选项,可以在多个物理数据库 (`IRIS.DAT`) 文件中传播数据。因此,可以根据需要构建具有多个数据库的应用程序或通过全局或下标级映射拆分数据。 - 根据可用于管理任务(如备份、恢复、完整性检查等)的基础设施,将数据库大小保持在可管理的范围内。 - 建议将流全局变量(如果将流存储在 `IRIS.DAT` 数据库文件中)全局映射到单独的数据库,并且将流数据库配置为大 (`64 KB`) 块大小。 - 根据工作负载,考虑替代(更大)块大小可能比默认的 `8 KB` 数据库块大小更有利。 ## 大数据块大小注意事项 除了 `IRIS` 支持的 `8 KB`(默认)块大小(始终启用)之外,还可以启用以下块大小: - `16 KB (16384)` - `32 KB (32768)` - `64 KB (65536)` 但是,在创建使用大块的数据库时应该谨慎,因为使用它们会影响系统的性能。 在启用和使用大的块大小之前,请考虑以下几点: - 如果应用程序工作负载主要由顺序插入或顺序读取/查询组成,那么大的块大小可以提高性能。 - 如果应用程序工作负载主要由随机插入或随机读取/查询组成,那么大的块大小可能会降低性能。 由于对于给定的数据库缓存总大小,较大的块大小会导致缓存更少的块,为了减少对随机数据库访问的影响,还应该考虑将更多的总内存用作数据库缓存。 - 对于索引类型的数据库,默认的块大小(`8 KB`)确保最佳性能; 较大的块大小可能会降低性能。 如果正在考虑为数据设置更大的块大小,那么应该考虑将索引全局变量映射到一个单独的`8 KB`块大小的数据库。 要创建一个使用不支持的块大小的数据库,请执行以下操作: 1. 使用启动设置页面(系统管理>附加设置>启动)的设置启用块大小,在配置参数文件引用的`DBSizesAllowed`条目中描述。 2. 在启动设置页面(系统管理>附加设置>启动),按照内存和启动设置中的描述,为启用的块大小配置数据库缓存。 3. 重新启动 4. 按照创建本地数据库中的说明创建数据库。 # 数据库兼容性注意事项 如创建本地数据库过程中所述,可以通过复制或移动 `IRIS.DAT` 文件将 `IRIS` 数据库复制或移动到创建它的实例之外的实例,或临时装载在另一个实例中创建的数据库在同一个系统上。还可以将数据库的备份(请参阅数据完整性指南的“备份和恢复”一章)恢复到其原始实例以外的实例。但是,为避免数据不兼容,必须满足以下要求: - 目标(新)实例必须使用相同的字符宽度(`8`位或`Unicode`; 请参阅安装指南中的新选项卡中的字符宽度设置),并使用相同的区域设置(请参阅使用管理门户的NLS设置页面)作为创建数据库的实例。 此要求的一个例外是使用基于 `ISO 8859 Latin-1` 字符集的区域设置的 `8` 位实例与使用相应宽字符区域设置的 `Unicode` 实例兼容。例如,使用 `enu8` 语言环境在 `8` 位实例中创建的数据库可以在使用 `enuw` 语言环境的 `Unicode` 实例中使用。 - 如果源实例和目标实例位于不同字节序的系统上,则数据库必须转换为目标实例的字节序后才能使用。 根据平台的不同,多字节数据存储在最低内存地址(即首先)中的最高有效字节或最低有效字节:当最高有效字节首先存储时,称为“大端;”当首先存储最低有效字节时,它被称为“小端”。 当使用在不同端序的系统上创建的现有`IRIS.DAT`定义数据库时,请在使用数据库之前使用`cvendian`实用程。
文章
Weiwei Gu · 六月 27, 2022

Globals 是管理数据的魔剑 : 第一部分

Globals,这些存储数据的魔剑,已经存在了一段时间,但是没有多少人能够有效地使用它们,也没有多少人知道这个超级武器。 如果你把Globals的东西用在它们真正能发挥作用的地方,其结果可能是惊人的,要么是性能的提高,要么是整体解决方案的大幅简化 (1, 2). Globals提供了一种特殊的存储和处理数据的方式,它与SQL表完全不同。它们在1966年首次出现在 M(UMPS)编程语言中, 该语言最初用于医学数据库。现在它仍然以同样的方式被使用,但也被其他一些以可靠性和高性能为首要任务的行业所采用:金融、交易等。 后来M(UMPS)演变为 Caché ObjectScript (COS). COS是由InterSystems公司开发的,作为M的一个超集. 其原始语言仍然被开发者社区所接受,并在一些实现中保持活力。在网络上有几个活跃的网址,比如:MUMPS Google group, Mumps User's group), effective ISO Standard等等 现代基于Globals的数据库支持交易、日志、复制、分区等。这意味着它们可以被用来构建现代的、可靠的、快速的分布式系统。 Gloabls并不将你限制于关系模型的范围内。它们让你可以自由地创建为特定任务优化的数据结构。对于许多应用来说,合理地使用好的Globals就如一颗真正的银子弹头,它所提供的速度是传统关系型应用的开发者所梦寐以求的。 作为一种存储数据的方法,globals可以在许多现代编程语言中使用,包括高级和低级语言。因此,本文将特别关注Globals本身,而不是它们曾经来自的语言。 Globals 是如何工作的 让我们先了解一下globals是如何工作的,它们有哪些优点。 我们可以从不同的角度来看待globals。在文章的这一部分,我们可将把它们看成是树形状结构或分层的数据存储空间。 简单地说,Globals是一个持久化的数组。一个自动保存在磁盘上的数组。 很难想象有什么比这更简单的方法来存储数据。在程序代码中(用COS/M语言编写),与普通关联数组的唯一区别是站在它们名字前面的^符号。 若将数据保存为Globals, 你可以不需要知道SQL,因为所有必要的命令都非常简单,在一个小时内就可以学会。 让我们从最简单的例子开始,一个有两个分支的单层的树形结构。例子是用COS(Caché ObjectScript) 写的。 Set ^a("+7926X") = "John Sidorov" Set ^a("+7916Y") = "Sergey Smith" 当数据被插入一个Global(Set命令)时,有3件事情会自动发生: 1.将数据保存到磁盘。2.编制索引。括号里的是下标,等号右边的是а节点值。3. 排序。数据是按一个键来排序的。下一次的遍历会把 "Sergey Smith "放到第一个位置,然后是 "John Sidorov"。当从global获得一个用户列表时,数据库不会在排序上花费时间。实际上你可以请求一个从任何键开始的排序列表,甚至是一个不存在的键(输出将从这个键之后的第一个真正的键开始)。所有这些操作都以惊人的速度进行。在我的个人系统(i5-3340,16GB,HDD WD 1TB Blue)上,我设法在一个进程中达到105万次插入/秒。在多核系统上,速度可以达到几千万次/秒 的插入。 当然,记录插入速度本身并不能说明什么。例如,我们可以将数据写入文本文件--根据传言,这就是Visa的处理方式。然而,通过globals,我们得到了一个结构化和索引化的存储,你可以在工作中享受其高速和易用性。 globals最大的优势是在其中插入新节点的速度。 数据在全局中总是有索引的。单层和深入的树形遍历总是非常快的。 让我们在global中添加一些二级和三级的分支看看: Set ^a("+7926X", "city") = "Moscow" Set ^a("+7926X", "city", "street") = "Req Square" Set ^a("+7926X", "age") = 25 Set ^a("+7916Y", "city") = "London" Set ^a("+7916Y", "city", "street") = "Baker Street" Set ^a("+7916Y", "age") = 36 显然,你可以使用globals建立多层级的“树”。由于每次插入后的自动索引,因而其对任何节点的访问几乎都是即时的。任何一级的树枝都可以按一个键进行排序。 正如你所看到的,数据可以被存储在键和值中。一个键的综合长度(所有索引的长度之和)可以达到511字节,而Caché中的值可以达到3.6MB的大小。树中的层数(维数)上限为31。 还有一件很酷的事情:你可以在不定义顶级节点的值的情况下建立一棵“树”。 Set ^b("a", "b", "c", "d") = 1 Set ^b("a", "b", "c", "e") = 2 Set ^b("a", "b", "f", "g") = 3 空的圆圈是没有值的节点。 为了更好地理解globals,让我们把它们与其他树进行比较:所谓“花园树”和“文件系统名称树”。 让我们把globals与最熟悉的层次结构进行比较:如下的Orchard tree-“生长在花园和田野中的普通树”,以及File system-文件系统。 我们可以看到,叶子和果实只生长在普通树木的枝干末端。文件系统--信息也被存储在树枝的末端,也被称为全文件名。 而下面这里是一个Global的数据结构: 不同: 1.内部节点:Global中的信息可以存储在每个节点中,而不是只存储在分支末端。2.外部节点:globals必须有定义的分支末端(有值的末端),这对文件系统和"花园树"来说不是强制性的。 关于内部节点,我们可以把global的结构看作是文件系统的名字树和花园树结构的超集。所以global的结构是一个更灵活的结构。 一般来说,global是一个结构化的树,支持在每个节点中保存数据。 为了更好地理解globals是如何工作的,让我们想象一下,如果文件系统的创建者使用与globals相同的方法来存储信息,会发生什么? 1. 如果一个文件夹中的最后一个文件被删除了,那么这个文件夹本身以及所有只包含这个被删除的文件夹的高层文件夹也会被删除。 2. 这样就根本不需要文件夹了。会有带子文件的文件和不带子文件的文件。如果你把它与普通的树作比较,每个分支都会变成一个果实。 3. 像README.txt这样的东西可能就不再需要了。所有你需要说的关于文件夹的内容都可以写在文件夹文件本身。一般来说,文件名和文件夹名是没有区别的(例如,/etc/readme可以是文件夹,也可以是文件),这意味着我们只需要操作文件就可以了。 4. 带有子文件夹和文件的文件夹可以更快地被删除。网络上有一些文章讲述了删除数百万个小文件是多么的耗时和困难(1, 2, 3). 然而,如果你创建一个基于Global的假的文件系统,它将只需要几秒钟甚至几分之一秒。当我在家里的电脑上测试删除子树时,我成功地从HDD(不是SDD)上的两级树上删除了96-341万个节点。值得一提的是,我们讨论的是删除Global树的一部分,而不是删除包含Global的整个文件。 子树的移除是globals的另一个优势:你不需要递归来做这个。它的速度快得令人难以置信。 在我们的树中,这可以通过一个Kill的命令来完成。 Kill ^a("+7926X") 下面是一个小表格,它可以让你更好地了解你可以在Global上执行的操作 Cache object script中与Globals有关的关键命令和功能 Set设置 设置(初始化)分支到一个节点(如果未定义)和节点值 Merge合并 复制一棵子树 Kill 删除一棵字树 ZKill 删除一个特定节点的值。源自该节点的子树不受影响。 $Query 对树进行全面深入的遍历 $Order 返回同一级别的下一个下标 $Data 检查一个节点是否被定义 $Increment 节点值的原子递增,以避免ACID的读和写。最新的建议是使用 $Sequence 来代替 感谢你的关注,我很乐意回答你的任何问题。 免责声明:本文反映了作者的个人观点,与InterSystems的官方立场无关。 让我们期待下一篇继续 "Globals 是存储数据的魔剑-树 :第二部分 (待翻译) 你将了解到哪些类型的数据可以显示在globals中,以及它们在哪些地方效果最好。 好文!
文章
姚 鑫 · 六月 11, 2021

第四章 添加命名空间声明

# 第四章 添加命名空间声明 # 添加命名空间声明 ## 默认行为 在`%XML.Writer`会自动插入命名空间声明,生成命名空间前缀,并在适当的地方应用前缀。例如,以下类定义: ```java Class Sample.Person Extends (%Persistent, %Populate, %XML.Adaptor) { Parameter NAMESPACE = "http://www.yaoxin.com"; } ``` 如果导出此类的多个对象,则会看到类似以下内容: ```java DHC-APP> w ##class(Demo.XmlDemo).Obj2Xml(1) yaoxin 111-11-1117 1990-04-25 889 Clinton Drive St Louis WI 78672 9619 Ash Avenue Ukiah AL 56589 濮氶懌 111-11-1115 Red Orange Yellow Green Red Orange Yellow 31 ``` 名称空间声明会自动添加到每个`` 元素。只将其添加到文档的根目录。 ## 手动添加声明 可以控制何时将命名空间引入XML输出。以下方法都会影响所写入的下一个元素(但不会影响该元素之后的任何元素)。为方便起见,其中几种方法添加了标准的`W3`名称空间。 通常使用这些方法将命名空间声明添加到文档的根元素;也就是说,在调用`RootObject()`或`RootElement()`之前调用其中一个或多个方法。 注意:这些方法都没有将任何元素分配给名称空间,并且这些名称空间永远不会作为默认名称空间添加。在生成特定元素时,需要指明它使用的名称空间,如后面的“编写根元素”和“生成XML元素”中所述。 ### AddNamespace() ```java method AddNamespace(namespace As %String, prefix As %String, schemaLocation As %String) as %Status ``` 添加指定的命名空间。这里,`Namespace`是要添加的名称空间,`Prefix`是该名称空间的可选前缀,`schemaLocation`是指示相应架构位置的可选URI。 如果未指定前缀,则会自动生成前缀(格式为S01、S02等)。 下面的示例显示了此方法的效果。首先,假设`Person`类被分配给一个名称空间(类参数中的`NAMESPACE`)。如果在未首先调用`AddNamespace()`方法的情况下生成此类实例的输出,则可能会收到如下所示的输出: ```java Love,Bart Y. ... ``` 或者,在编写根元素之前按如下方式调用`AddNamespace()`方法: ```java set status=writer.AddNamespace("http:///www.person.org","p") ``` 如果随后生成根元素,则输出如下所示: ```java ... ``` 或者,假设在调用`AddNamespace()`方法时指定了第三个参数,该参数提供了关联架构的位置: ```java set status=writer.AddNamespace("http:///www.person.org","p","http://www.MyCompany.com/schemas/person.xsd") ``` 在这种情况下,如果随后生成Root元素,则输出如下所示: ```java ... ``` ### AddInstanceNamespace() ```java method AddInstanceNamespace(prefix As %String) as %Status ``` 添加W3架构实例命名空间。这里的前缀是用于此命名空间的可选前缀。默认前缀为`XSI`。 ```java ... ``` ### AddSchemaNamespace() ```java method AddSchemaNamespace(prefix As %String) as %Status ``` 添加`W3`架构命名空间。这里的前缀是用于此命名空间的可选前缀。默认前缀为`s`。 ```java ... ``` ### AddSOAPNamespace() ```java method AddSOAPNamespace(soapPrefix As %String, schemaPrefix As %String, xsiPrefix As %String) as %Status ``` 添加`W3 SOAP`编码命名空间、`SOAP`架构命名空间和`SOAP`架构实例命名空间。此方法有三个可选参数:用于这些命名空间的前缀。默认前缀分别为`SOAP-Enc`、`s`和`XSI`。 ```java ... ``` ### AddSOAP12Namespace() ```java method AddSOAP12Namespace(soapPrefix As %String, schemaPrefix As %String, xsiPrefix As %String) as %Status ``` 添加`W3 SOAP 1.2`编码命名空间、`SOAP`架构命名空间和`SOAP`架构实例命名空间。 ```java ... ``` 可以使用这些方法中的多个方法。如果使用其中的多个命名空间,则受影响的元素将包含所有指定命名空间的声明。 # 编写根元素 每个XML文档必须恰好包含一个根元素。有两种方法可以创建此元素: - 根元素可能直接对应于一个启用了InterSystems IRIS XML的对象。 在本例中,使用`RootObject()`方法,该方法将指定的启用XML的对象作为根元素写入。输出包括该对象中包含的所有对象引用。根元素获取该对象的结构,不能插入其他元素您可以指定根元素的名称,也可以使用由启用XML的对象定义的默认值。 前面的示例使用了此技术。 - 根元素可能只是一组元素的包装器(可能是一组支持XML的对象)。 在本例中,使用`RootElement()`方法,该方法插入具有指定名称的根级元素。如果此文档缩进,此方法还会增加后续操作的缩进级别。 然后调用其他方法为根元素内的一个或多个元素生成输出。在根目录中,可以按照选择的任何顺序或逻辑包含所需的元素。之后,调用`EndRootElement()`方法关闭根元素。 在这两种情况下,都可以指定要用于根元素的命名空间,只有在启用了`XML`的类没有`Namespace`参数值的情况下才会应用该命名空间。 请记住,如果文档包含文档类型声明,则该`DTD`的名称必须与根元素的名称相同。
文章
姚 鑫 · 六月 12, 2021

第五章 生成XML元素

# 第五章 生成XML元素 # 生成XML元素 如果使用`RootElement()`启动文档的根元素,则负责生成该根元素内的每个元素。有三个选择: ## 将对象生成为元素 可以从InterSystems IRIS对象生成输出作为元素。在本例中,使用`object()`方法,该方法写入支持XML的对象。输出包括该对象中包含的所有对象引用。可以指定此元素的名称,也可以使用在对象中定义的默认值。 只能在`RootElement()`和`EndRootElement()`方法之间使用`object()`方法。 此示例为给定启用XML的类的所有已保存实例生成输出: ```java /// desc:将表里数据输出本地文件里 /// w ##class(PHA.TEST.Xml).WriteAll("Sample.Person") ClassMethod WriteTableAllToXml(cls As %String = "", directory As %String = "E:\temp\") { if '##class(%Dictionary.CompiledClass).%ExistsId(cls) { Write !, "类不存在或未编译" Quit } s check=$classmethod(cls, "%Extends", "%XML.Adaptor") If 'check { Write !, "类不扩展%XML.Adaptor" Quit } s filename = directory_"Person"_".xml" s writer = ##class(%XML.Writer).%New() s writer.Indent=1 s status = writer.OutputToFile(filename) if $$$ISERR(status) { do $System.Status.DisplayError(status) quit } s status=writer.RootElement("SampleOutput") if $$$ISERR(status) { do $System.Status.DisplayError(status) quit } //获取给定类范围内对象的ID s stmt = ##class(%SQL.Statement).%New() s status = stmt.%PrepareClassQuery(cls,"Extent") if $$$ISERR(status) { do $System.Status.DisplayError(status) quit } s rset = stmt.%Execute() while (rset.%Next()) { //对于每个ID,写入该对象 set objid = rset.%Get("ID") set obj = $CLASSMETHOD(cls,"%OpenId",objid) set status = writer.Object(obj) if $$$ISERR(status) {Do $System.Status.DisplayError(status) Quit}} d writer.EndRootElement() d writer.EndDocument() q "" } ``` 此方法的输出包含给定类的所有已保存对象,这些对象嵌套在根元素中。对于`Sample.Person`,输出如下: ```java Tillem,Robert Y. 967-54-9687 1961-11-27 3355 First Court Reston WY 11090 4922 Main Drive Newton NM 98073 Red 47 Waters,Ed X. 361-66-2801 1957-05-29 5947 Madison Drive ... ``` ## 手动构建元素 以手动构造XML元素。在本例中,使用`element()`方法,该方法使用提供的名称写入元素的开始标记。然后,可以编写内容、属性和子元素。使用`EndElement()`方法指示元素的结束。 相关方法如下: ### Element() ```java method Element(tag, namespace As %String) as %Status ``` 写入开始标记。可以为元素提供命名空间,只有在启用了XML的类没有`Namespace`参数的值时才会应用该命名空间。 ### WriteAttribute() ```java method WriteAttribute(name As %String, value As %String = "", namespace As %String, valueNamespace As %String = "", global As %Boolean = 0) as %Status ``` 写入属性。必须指定属性名称和值。参数命名空间是属性名称的命名空间。参数`valueNamespace`是属性值的名称空间;当值在XML模式名称空间中定义时使用。 对于GLOBAL,如果属性在关联的XML架构中是全局的,因此应该有前缀,请指定TRUE。 如果使用此方法,则必须在`Element()`(或`RootElement()`)之后直接使用它。 ### WriteChars() ```java method WriteChars(text) as %Status ``` 写入字符串,执行使该字符串适合作为元素内容所需的任何必要转义。参数必须`%String`类型或`%CharacterStream`类型。 ### WriteCData() ```java method WriteCData(text) as %Status ``` 参数必须`%String`类型或`%CharacterStream`类型。 ### WriteBase64() ```java method WriteBase64(binary) as %Status ``` 将指定的二进制字节编码为`base-64`,并将结果文本写入元素的内容。该参数的类型必须为`%Binary`或`%BinaryStream`。 ### WriteBinHex() ```java method WriteBinHex(binary) as %Status ``` 将指定的二进制字节编码为二进制,并将结果文本写入元素的内容。该参数的类型必须为`%Binary`或`%BinaryStream`。 ### EndElement() ```java method EndElement() as %Status ``` 结束可以与其匹配的元素。 只能在`RootElement()`和`EndRootElement()`方法之间使用这些方法。 注意:这里描述的方法旨在使能够向XML文档编写特定的逻辑片段,但在某些情况下,可能需要更多的控制。`%XML.Writer`类提供了一个附加方法`write()`,可以使用该方法编写任意字符串。有责任确保结果是格式良好的XML文档;不提供任何验证。 示例 下面是一个示例例程: ```java /// w ##class(Demo.XmlDemo).WriteObjXml() ClassMethod WriteObjXml() { set writer=##class(%XML.Writer).%New() set writer.Indent=1 set status=writer.OutputToDevice() if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.StartDocument() if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.RootElement("root") if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.Element("SampleElement") if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.WriteAttribute("Attribute","12345") if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.Element("subelement") if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.WriteChars("yao") if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.EndElement() if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.Element("subelement") if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.WriteChars("xin") if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.EndElement() if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.EndElement() if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.EndRootElement() if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} set status=writer.EndDocument() if $$$ISERR(status) {do $System.Status.DisplayError(status) quit} q "" } ``` ```java DHC-APP>w ##class(Demo.XmlDemo).WriteObjXml() yao xin ``` ### 使用%XMLL.Element 在前一节中,我们使用了Element()并指定了要生成的元素;我们还可以指定名称空间。在某些情况下,类中使用%XML.Element的实例,而不是使用元素名称。此类具有以下属性: - Local属性指定此元素是否为其父元素的本地元素,这会影响命名空间的控制。 - Namespace属性指定此元素的命名空间。 - Tagname属性指定此元素的名称。 这里还可以使用前面描述的WriteAttribute()方法。
文章
姚 鑫 · 七月 12, 2021

第一章 查询目录和驱动器

# 第一章 查询目录和驱动器 `%Library.File`(简称`%File`)为处理文件和目录提供了广泛的API。本文将介绍该API的主要功能。有关属性、方法和查询的规范列表,请参见类参考。 注意:如果指定了部分文件名或目录名,这些方法中的大多数都引用的项相对于包含正在使用的命名空间的默认全局数据库的目录。该目录在本文中称为“默认目录”。这条规则的任何例外都在文章中注明。 此外,仅当基础操作系统将文件名和目录名视为区分大小写时,这些方法才会将文件名或目录名视为区分大小写。也就是说,文件或目录名在Unix上区分大小写,但在Windows上不区分大小写。 # 查询目录和驱动器 ## 列出目录的内容 `FileSet`类查询列出目录的内容。此查询按顺序接受以下参数: 1. `directory` — 指定要检查的目录的名称。 2. `wildcards` 通配符 — 指定要匹配的文件名模式(如果有)。 3. `sortby` 排序依据 — 指定如何对结果进行排序。使用以下值之一: - `Name` 名称—文件的名称(默认) - `Type` 类型—项目类型 - `DateCreated` 创建日期—创建文件的日期和时间 - `DateModified` 日期修改—文件上次修改的日期和时间 - `Size` 大小—文件大小 4. `includedirs` —指定如何处理给定目录中的目录。如果此参数为真(1),查询将返回任何文件之前的所有目录,并且目录名忽略通配符参数。如果此参数为false (0),通配符参数适用于文件和目录。默认值为0。 5. `delimiter` 分隔符—指定通配符参数中通配符之间的分隔符。默认值为; 此查询返回的结果集提供了以下字段: - `Name` 名称—项目的完整路径名。 - `Type` 类型—项目的类型:`F`表示文件,`D`表示目录,`S`表示符号链接。 - `Size` 大小—文件大小,以字节为单位。对于目录和符号链接,此字段为空。 - `DateCreated` 创建日期—创建项目时的日期和时间,格式为`yyyy-mm-dd hh:mm:ss`。 - `DateModified` 日期修改—上次修改项目的日期和时间,格式为`yyyy-mm-dd hh:mm:ss`。 - `ItemName` 项目名称—项目的简称。对于文件,这是单独的文件名,没有目录。对于目录,这只是目录路径的最后一部分。 注意:Windows是目前唯一跟踪实际创建日期的平台。其他平台存储最后一次文件状态更改的日期。 下面是一个使用这个类查询的简单示例: ```java /// desc:查看目标路径所有文件。 /// w ##class(Demo.FileDemo).ShowDir("C:\InterSystems\Cache\mgr", "*.log", "Size") /// w ##class(Demo.FileDemo).ShowDir("E:\temp", "*.xml", "Size") ClassMethod ShowDir(dir As %String = "", wildcard As %String = "", sort As %String = "Name") { s stmt = ##class(%SQL.Statement).%New() s status = stmt.%PrepareClassQuery("%File", "FileSet") if $$$ISERR(status) { do $system.OBJ.DisplayError(status) quit } s resultSet = stmt.%Execute(dir, wildcard, sort) while resultSet.%Next() { w !, resultSet.%Get("Name") w " ", resultSet.%Get("Type") w " ", resultSet.%Get("Size") } q "" } ``` 从终端对指定目录运行此方法,筛选日志文件,并按文件大小排序,结果如下所示: ```java DHC-APP> w ##class(Demo.FileDemo).ShowDir("E:\temp", "*.xml", "Size") E:\temp\testPerson.xml F 117 E:\temp\samplePerson.xml F 327 E:\temp\xmlnewtest.xml F 351 E:\temp\Person.xml F 259854 E:\temp\tempPerson.xml F 259854 ``` 又例如,下面的方法递归检查目录及其所有子目录,并写出它找到的每个文件的名称: ```java /// w ##class(Demo.FileDemo).ShowFilesInDir("E:\temp") ClassMethod ShowFilesInDir(directory As %String = "") { s stmt = ##class(%SQL.Statement).%New() s status = stmt.%PrepareClassQuery("%File", "FileSet") if $$$ISERR(status) { d $system.OBJ.DisplayError(status) q } s resultSet = stmt.%Execute(directory) while resultSet.%Next() { s name = resultSet.%Get("Name") s type = resultSet.%Get("Type") if (type = "F") { w !, name } elseif (type = "D"){ d ..ShowFilesInDir(name) } } q "" } ``` 在默认目录下的终端中运行此方法会产生如下结果: ```java DHC-APP>w ##class(Demo.FileDemo).ShowFilesInDir("E:\temp") E:\temp\config.txt E:\temp\game.jpg E:\temp\Person.xml E:\temp\ppg.txt E:\temp\qcache.txt E:\temp\rfc7158.html E:\temp\rfc7158.txt E:\temp\samplePerson.xml E:\temp\SecurityXml.txt E:\temp\temp1.txt E:\temp\tempPerson.xml E:\temp\test\Tests.xml E:\temp\testPerson.xml E:\temp\Testzf.dll E:\temp\textReader.txt E:\temp\xmlnewtest.xml E:\temp\xmlXpath.txt E:\temp\yaoxin.txt E:\temp\yxtest.txt E:\temp\yxtest_Errors.log E:\temp\yxtest_Unsupported.log E:\temp\汉子转拼音global.gof ``` ## 列出驱动器或装载的文件系统 `Drivelist`类查询列出可用的驱动器(在Windows上)或已装载的文件系统(在Unix上)。此查询接受一个参数: 1. `fullyqualified`-如果此参数为1,则查询在每个Windows驱动器名称上都包含一个尾随反斜杠。对其他平台没有影响。默认值为0。 此查询返回的结果集提供了一个字段: - `Drive` 驱动器—驱动器的名称(在Windows上)或装载的文件系统的名称(在Unix上)。 以下示例显示了如何使用该查询: ```java /// w ##class(Demo.FileDemo).ShowDrives() ClassMethod ShowDrives() { s stmt = ##class(%SQL.Statement).%New() s status = stmt.%PrepareClassQuery("%File","DriveList") if $$$ISERR(status) { d $system.OBJ.DisplayError(status) q } s resultSet = stmt.%Execute(1) while resultSet.%Next() { w !, resultSet.%Get("Drive") } q "" } ``` 在终端中运行该方法会得到如下结果: ```java DHC-APP>w ##class(Demo.FileDemo).ShowDrives() c:\ d:\ e:\ g:\ ```
文章
姚 鑫 · 六月 20, 2021

第十三章 将XML文档表示为DOM

# 第十三章 将XML文档表示为DOM `%XML.Document`类和`%XML.Node`类使可以将任意XML文档表示为DOM(文档对象模型)。然后,可以导航此对象并对其进行修改。还可以创建一个新的DOM并将其添加到其中。 **注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码,InterSystems IRIS将使用本书前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。** # 将XML文档作为DOM打开 要打开现有XML文档以用作DOM,请执行以下操作: 1. 创建`%XML.Reader`的实例。 2. 也可以指定此实例的`Format`属性,以指定要导入的文件的格式。 **默认情况下, IRIS假定XML文件为文字格式。如果文件是SOAP编码格式,则必须指明这一点,以便可以正确读取该文件。** 除非使用`Correlate()`和`Next()`,否则此属性无效。 3. 请使用`%XML.Reader`的以下方法之一。 - `OpenFile()` — 打开一个文件。 - `OpenStream()` —打开一个流。 - `OpenString()` — 打开字符串。 - `OpenURL()` — 打开URL。 在每种情况下,都可以选择为该方法指定第二个参数,以重写`Format`属性的值。 4. 访问`Document`属性,它是一个DOM。此属性是`%XML.Document`实例,它提供了可用于查找有关整个文档的信息的方法。例如,`CountNamespace()`返回DOM使用的名称空间总数。 或者,如果流包含XML文档,调用`%XML.Document`的`GetDocumentFromStream()`方法。返回`%XML.Document`的实例。 ## 示例1:将文件转换为DOM 例如,下面的方法读取一个XML文件,并在表示该文档的返回`%XML.Document`的一个实例: ```java ClassMethod GetXMLDocFromFile(file) As %XML.Document { s reader = ##class(%XML.Reader).%New() s status = reader.OpenFile(file) if $$$ISERR(status) {d $System.Status.DisplayError(status) q $$$NULLOREF} s document = reader.Document q document } ``` ## 示例2:将对象转换为DOM 以下方法接受`OREF`,并在表示该对象中返回`%XML.Document`的实例。该方法假定`OREF`是启用XML的类的实例: ```java ClassMethod GetXMLDoc(object) As %XML.Document { //确保这是启用XML的类的实例 if '$IsObject(object){ w "参数不是对象" q $$$NULLOREF } s classname = $CLASSNAME(object) s isxml = $CLASSMETHOD(classname,"%Extends","%XML.Adaptor") if 'isxml { w "参数不是启用XML的类的实例" q $$$NULLOREF } //步骤1-将对象作为XML写入流 s writer = ##class(%XML.Writer).%New() s stream = ##class(%GlobalCharacterStream).%New() s status = writer.OutputToStream(stream) if $$$ISERR(status) {d $System.Status.DisplayError(status) q $$$NULLOREF} s status = writer.RootObject(object) if $$$ISERR(status) {d $System.Status.DisplayError(status) q $$$NULLOREF} //步骤2-从流中提取%XML.Document s status = ##class(%XML.Document).GetDocumentFromStream(stream,.document) if $$$ISERR(status) {d $System.Status.DisplayError(status) q $$$NULLOREF} quit document } ``` # 获取DOM的名称空间 当 IRIS读取XML文档并创建DOM时,它会标识文档中使用的所有名称空间,并为每个名称空间分配一个索引号。 在`%XML.Document`实例提供了以下方法,可以使用这些方法查找有关文档中命名空间的信息: ### CountNamespace() 返回文档中的命名空间数。 ### FindNamespace() 返回与给定命名空间对应的索引。 ### GetNamespace() 返回给定索引的XML命名空间URI。 下面的示例方法显示一个报表,其中显示文档中使用的命名空间: ```java ClassMethod ShowNamespaces(doc As %XML.Document) { s count = doc.CountNamespace() w !, "文档中的命名空间数: "_count for i = 1 : 1 : count { w !, "Namespace "_i_" is "_doc.GetNamespace(i) } } ``` # 导航DOM的节点 要访问文档的节点,可以使用两种不同的技术: - 使用`%XML.Document`实例的`GetNode()`方法。此方法接受一个整数,它指示从1开始的节点号。 - 调用`%XML.Document`实例的`GetDocumentElement()`方法。 此方法返回`%XML.Node`的实例,提供用于访问有关根节点的信息以及移动到其他节点的属性和方法。以下小节提供了有关使用`%XML.Node`的详细信息。 ## 移动到子节点或同级节点 要移动到子节点或同级节点,请使用`%XML.Node`实例的以下方法。: - `MoveToFirstChild()` - `MoveToLastChild()` - `MoveToNextSibling()` - `MoveToPreviousSibling()` 这些方法中的每一个都移动到另一个节点(如方法名称所示)。如果是,则该方法返回TRUE。如果不是,则返回False,焦点与调用该方法之前相同。 这些方法中的每一个都有一个可选参数`skipWhitespace`。如果此参数为真,则该方法将忽略任何空格。`SkipWhitespace`的默认值为false。 ## 移动到父节点 要移动到当前节点的父节点,请使用`%XML.Node`实例的`MoveToParent()`方法。 此方法接受一个可选参数`restrictDocumentNode`。如果此参数为真,则该方法不会移动到文档节点(根)。`restrictDocumentNode`的默认值为False。 ## 移动到特定节点 要移动到特定节点,可以设置`%XML.Node`实例的`NodeId`属性。例如: ```java set saveNode = node.NodeId //..... lots of processing //... // restore position set node.NodeId=saveNode ``` ## 使用id属性 在某些情况下,XML文档可能包括名为`id`的属性,该属性用于标识文档中的不同节点。例如: ```java Jack O'Neill Samantha Carter Daniel Jackson ``` 如果(如本例所示)文档使用名为`id`的属性,则可以使用它导航到该节点。为此,可以使用文档的`GetNodeById()`方法,该方法返回`%XML.Node`的一个实例。(请注意,与大多数其他导航方法不同,此方法可从`%XML.Document`,而不是`%XML.Node`。)
文章
Tete Zhang · 九月 14, 2022

集成平台消息相关的常见存储问题

从消息查看器看到清除周期以外的消息没有被正常清除 这种情况先抽查这些消息所处的会话中是否有未完成操作周期的消息(状态为除“Completed”“Error”“Discarded”之外的状态)。如有,且定期清除任务配置了“KeepIntegrity”,且该环境并不需要保留这些消息,可通过关闭清除任务中的“KeepIntegrity”配置清除这些会话和包含的消息。如果有这类消息,但是定期清除任务未配置“KeepIntegrity”,可能是定期清除任务的逻辑或消息数据问题导致清楚任务查找的时候没有覆盖这些消息,请联系WRC帮助排查具体原因。 有关定期清除任务的更多信息请参见文档 Purging Production Data | Managing Productions | InterSystems IRIS for Health 2022.1 从消息查看器看不到清除周期之外的消息,但是^%GSIZE显示有global占据了很大的磁盘空间 这种情况需要具体排查每个较大的global。可能有以下原因: 系统定义的global占用很大空间。您可以联系WRC帮助排查具体原因。 自定义的global占用很大空间。这可能是消息中嵌套的持久化数据或流数据,或者是和消息没有直接关系的独立的表里面的数据。请对创建这种global的代码进行复盘,找到创建逻辑并增加相应的数据管理逻辑。 孤立消息 (Orphan Messages) 孤立消息是指不存在配套Message Header的所有消息对象。 定期清除任务会根据Message Header里的时间信息和状态信息去判断一条消息是否符合清除条件。Message Header是在消息从一个组件发向另一个组件的时候被创建的。所以,当我们创建将被永久存储的对象之前,我们都要思考:这个对象会被保存吗?被保存后会被发送到另一个组件吗?如果不会被发送,该对象将不存在配套的Message Header,也就不会被定期清除。这种情况我们需要开发相应的自定义逻辑去定期管理该表中的数据,或确保该对象被发送到某个组件以创建Message Header。消息对象中嵌套对象或流的情况要尤其注意,对每一个嵌套的对象或流都要定义相对应的%OnDelete()删除逻辑。 在测试阶段我们可以做如下的测试: 测试前跑^%GSIZE报告并检查磁盘存储 跑一套测试消息 用清除任务删除系统上所有的消息(DaysToKeep=0) 跑^%GSIZE报告并检查磁盘存储 如果对比前后的^%GSIZE报告和磁盘空间之后,发现清除任务完成后没有遗留多余的数据,那么这就证明我们的逻辑中对消息及相关嵌套数据进行了很好的管理。反之如果发现了遗留数据,我们可以在研发测试阶段就对问题进行排查,尽量避免开放生产环境以后出现磁盘满或数据库过大的问题。 如果发现了环境中有孤立消息的问题,请联系WRC进行排查和消息清除管理。 HL7v2:孤立字段(Orphan Segment) HL7v2在数据库中的存储逻辑如下。 EnsLib.HL7.Message对象存在以下两个global里: ^EnsLib.H.MessageD ^EnsHL7.Segment 示例: HL7v2消息 (^EnsLib.H.MessageD global): 1: ^EnsLib.H.MessageD = 1257406 2: ^EnsLib.H.MessageD(1257406) = $lb("","","2.3:ORU_R01",0,"2019-06-03 15:28:38.819","2.3.1","C:\Support\inarchive\testoru.txt_2019-06-03_11.28.38.814","","") 3: ^EnsLib.H.MessageD(1257406,"segs") = 5 4: ^EnsLib.H.MessageD(1257406,"segs",1) = "11612,25" 5: ^EnsLib.H.MessageD(1257406,"segs",2) = "11612,26" 6: ^EnsLib.H.MessageD(1257406,"segs",3) = "11612,27" 7: ^EnsLib.H.MessageD(1257406,"segs",4) = "11612,28" 8: ^EnsLib.H.MessageD(1257406,"segs",5) = "11612,29" 其中, 125706是该HL7v2消息的Object ID。Global值"11612,25","11612,26"指向相应的HL7v2字段。 HL7v2字段 (^EnsHL7.Segment global): 1: ^EnsHL7.Segment(11612) = 30 2: ^EnsHL7.Segment(11612,25) = "|^~\&MSH|^~\&||GA0000||VAERS PROCESSOR|20010331605||ORU^R01|20010422GA03|T|2.3.1|||AL|" 3: ^EnsHL7.Segment(11612,25,0,1257406) = "" 其中,11612是创建该HL7v2消息的进程 ID (PID)。^EnsHL7.Segment(11612,25) 存储了该字段的具体数据。^EnsHL7.Segment(11612,25,0,1257406) 中的第四个值(1257406)是这个字段所属消息的Object ID。 从以上示例可以看出,HL7v2字段数据存储于^EnsHL7.Segment global。所以在^%GSIZE中看到^EnsHL7.Segment global比 ^EnsLib.H.MessageD global大是正常现象。使用平台自带的逻辑在最新版本上目前也没有已知问题会导致孤立字段。如果您持续观察^%GSIZE报告,发现^EnsHL7.Segment global的大小出现异常增长,可以联系WRC排查是否有孤立字段的情况。
文章
Claire Zheng · 十月 18, 2022

技能帖:更好地利用开发者社区的发帖功能!

各位开发者社区的同学们,大家好! 您想更好地获得帮助、讨论有趣的功能、发布公告或分享您的知识吗?在这篇文章中,我们将告诉你如何做到这一切。 我们将通过以下几部分来分享“如何做”: 一般发帖步骤 问题 文章或公告 讨论 一般发帖步骤 首先,你需要点击开发者社区网站顶部菜单中的“发布新帖”按钮: 之后,您将看到编辑器中显示创建一个问题、一则公告、一篇文章或一个讨论。不同类型的帖子有自己的一组必填字段和可选字段。 首先,让我们讨论所有类型的帖子的公共字段,然后继续讨论细节。 基本上,每篇文章都有一个标题*、正文*、组*、标签和一些额外的选项,你可以在其中添加调查或附加PDF文件。所有用星号(*)标记的文本字段都是必填项。因此,首先,你需要选择帖子的类型,可以像上面提到的问题,公告,文章或讨论。 接下来,请用最精确和简洁的方式表述你的问题的主要思想,并将其作为标题。 之后,在文章主体中,你可以写任何你想与他人分享的东西。在写文章的时候有两个选择。你可以使用编辑器“所见即所得”(WYSIWYG)模式或者Markdown。当然,这两种方法得到的结果是一样的。 vs. 在你写完文本后,你必须选择组,通常是InterSystems提供的技术、产品或服务。 在组字段之后,有一个标签字段,您可以在其中添加与文章内容相关的标签。有相当多的标签,所以请认真选择,因为其他成员会通过这些标签寻找或排序所需要的信息。 在标签下面,有一个链接可以查看更多选项。在那里,您可以附加一个pdf文档(例如,pdf格式的事件时间表)并提供您想要显示的名称。 你可以通过“更多选项”做的另一件事是添加投票。在字段中填写一个问题、可能的答案、选择持续时间等。 完成后,你可以预览你的帖子,看看它对其他人来说是什么样子,你可以保存它以便以后继续编辑,或立即发布它。 此外,您可以预约发布您的文章。只需点击向下箭头,选择安排帖子,并设置日期和时间。 一切都设置好后,只需点击安排帖子,就完成了。 基本上,这是创建帖子的常见功能。 问题 从它的名字来看,很明显,如果你需要别人的帮助,你应该选择这种类型的帖子。在这里,在开发者社区中,有很多专家,有些人可能已经遇到了相同的情况。所以不要犹豫,提出问题或回答问题吧:) 要寻求帮助,请阐明你的问题的主要思想,并将其作为一个标题写下来。接下来,请选择您正在使用的产品版本,因为不同版本具有不同的功能和类,一些建议可能对某些版本有用,而对其他版本无用。 更准确地说,您可以提供当前在$ZV文本框中使用的完整构建。要获得完整版本,可以打开Terminal并执行以下命令: write $ZV 在你正在使用的IDE中也可以执行相同的操作,或者你可以在管理门户(Management Portal)中看到: 其余字段与前面描述的相同。 文章或公告 要分享你的知识或发布公告,你应该分别选择一种类型的帖子——文章或公告。这些类型的帖子除了公共字段之外还有一些额外的字段。这些是上一篇文章、下一篇文章和打开Exchange应用程序链接。 因此,基本上,如果当前的文章/声明(或讨论的分支)链接到另一篇文章,您可以在“上一个公告”字段中添加链接,这样其他社区成员将在文章末尾看到以下相关文章块。 你不需要再打开上一篇文章去添加到下一篇文章的链接,它将自动链接。 添加完这些链接后,用户也可以通过使用链接文章右上角的导航按钮轻松地从一个帖子导航到另一个帖子。 如果你的帖子在Open Exchange上有一个项目链接到它,你可以在相应的字段中添加这个项目的链接。 讨论 要开始关于某个功能的对话,或者分享您使用该技术的经验并寻求反馈,您可以开始一个讨论。这种类型的文章有所有公共字段,也有到上一篇和下一篇的链接。 就这些! 这就是您在社区上开始发布一个新帖子时所需要知道的。 期待着看到您的精彩发帖:)
文章
姚 鑫 · 六月 4, 2023

第二十四章 开发Productions - ObjectScript Productions - 定义业务服务

# 第二十四章 开发Productions - ObjectScript Productions - 定义业务服务 本页介绍如何定义业务服务类。 提示: `IRIS® `提供使用特定入站适配器的专用业务服务类,其中之一可能适合需要。如果是这样,则不需要编程。有关部分列表,请参阅 `Introducing Interoperability Productions` 中的连接选项。 # 介绍 业务服务负责接受来自外部应用程序的请求到 `IRIS`。下图显示了它是如何工作的: 请注意,此图仅显示数据的输入流,而不是可选响应。 业务服务负责以下活动: - 等待特定的外部事件(例如来自应用程序的通知、收到 `TCP` 消息等)。 - 读取、解析和验证伴随此类事件的数据, - 如果需要,返回对外部应用程序的确认,表明已收到事件。 - 创建请求消息的实例并将其转发到适当的业务流程或业务操作以进行处理。 业务服务的目的通常是接收数据输入。在大多数情况下,业务服务有一个与之关联的入站适配器。但是,在某些情况下不需要适配器,因为应用程序能够将请求消息发送到服务中,或者因为业务服务已被编写为处理特定类型的外部调用,例如来自复合应用程序的调用。这种类型的业务服务称为无适配器业务服务。 当业务服务具有入站适配器时,它处于数据拉取(而不是推送)模式。在这种模式下,业务服务会定期轮询适配器,看它是否有数据。同时,如果适配器随时遇到输入数据,它会调用业务服务来处理输入。 当业务服务没有适配器时,它不会拉取数据。相反,客户端应用程序调用业务服务并告诉它处理输入(这是一种数据推送模式)。 # 关键原则 首先,务必阅读 `Programming in InterSystems IRIS`。 在业务服务中,可以访问关联适配器的属性和方法,这些适配器作为业务服务的 Adapter 属性提供。这意味着可以更改适配器的默认行为;这样做可能合适也可能不合适。记住封装原则很有用。封装的思想是适配器类应该负责技术特定的逻辑,而业务服务类应该负责生产特定的逻辑。 如果发现有必要在业务服务类中大量或频繁地改变适配器类的行为,那么创建适配器类的自定义子类可能更合适。请参阅不太常见的任务。 这个原则也适用于商业运作。 # 定义业务服务类 要创建一个业务服务类,定义一个类如下: - 类必须在(或子类)中扩展 `Ens.BusinessService`。 - 在类中,`ADAPTER` 参数必须等于此业务服务要使用的适配器类的名称。 提示:如果只是希望业务服务定期唤醒和运行而不关心 `IRIS` 外部的事件,请使用适配器类 `Ens.InboundAdapter`。 - 类必须实现 `OnProcessInput()` 方法,如实现 `OnProcessInput()` 方法中所述。 - 类可以添加或删除设置。请参阅添加和删除设置。 - 类可以实现任何或所有启动和拆卸方法。请参阅覆盖启动和停止行为。 - 类可以包含完成自身内部工作的方法。 有关业务服务类的示例,请参阅适配器指南。 # 实施 `OnProcessInput()` 方法 在业务服务类中, `OnProcessInput()` 方法可以具有以下通用签名: ```java Method OnProcessInput(pInput As %RegisteredObject, pOutput As %RegisteredObject) As %Status ``` 这里的`pInput`是适配器要发送给这个业务服务的输入对象,`pOutput`是输出对象。 首先查看选择的适配器类。 建议编辑 `OnProcessInput()` 方法签名以使用适配器所需的特定输入参数。 `OnProcessInput()` 方法应该执行以下部分或全部操作: 1. 可选地设置业务服务类的属性(在任何适当的时间)。最受关注的业务服务属性是 `%WaitForNextCallInterval`。它的值控制 `IRIS` 调用适配器的 `OnTask()` 方法的频率。 有关其他属性,请参阅 `Ens.BusinessService`的类参考。 2. 如有必要,验证输入对象。 3. 检查输入对象并决定如何使用它。 4. 创建请求消息类的实例,这将是业务服务发送的消息。 5. 对于请求消息,使用输入对象中的值适当地设置其属性。 6. 确定要将请求消息发送到哪里。当发送消息时,将需要在生产中使用业务主机的配置名称。 7. 将请求消息发送到生产(业务流程或业务操作)中的目的地。请参阅下一节。 8. 确保设置输出参数 (`pOutput`)。通常,将其设置为等于您收到的响应消息。此步骤是必需的。 9. 返回适当的状态。此步骤是必需的。
文章
Michael Lei · 十月 10, 2022

互操作性--创建和连接业务主机Business Host的步骤一二三

Hi 大家好, 我最近开始学习InterSystems IRIS 的互操作性,我发现官方文档对理解它的工作原理很有帮助,尽管我自己在实现它时仍有一些困难。在我的同事的帮助下,我成功地创建了一个系统的Demo,并从实践中学习。因此,我决定写一下文章,分享我得到的帮助,来帮助更多的其他人。 介绍 首先,让我们掌握一些基本概念: 互操作性 - 这个词的含义并不像它的发音那样复杂--它基本上是把各种信息从一个系统带到另一个系统的“魔术”。 业务主机 - 如果把互操作性比作是魔术,那么业务主机Business Host就是魔术师的魔法帽--业务主机里有能够识别和接收信息的业务服务Business Service/BS,并将其作为消息发送给业务流程BP或业务操作BO。业务操作执行所需的操作(顾名思义)并传递信息。业务流程控制着消息的流动:它们定义了消息的去向(基于你所选择的任何东西)以及它是如何被传递的。 适配器 - 适配器是一些我们可以用来识别和操作我们可能要处理的各种信息的类。在实践中,我们把它们作为参数和(可选)属性来访问其方法和属性 准备搭建Production 从简单的开始比较容易--让我们先想想服务和操作--比如说你有一个接收一种信息的服务,它很容易被我们唯一的操作所识别。 当生产的目的和它的部分非常清楚时,开发就比较容易。如果你愿意,画一张图或写下你希望它完成的步骤可能会有帮助。 例如: 首先要问自己, "需要做什么?" - 在我的演示中,我需要操作一个SQL表--我要把一本书的标题和作者等信息,并将其插入到一个表中。 "那么,我需要Production做什么呢?- 它必须接收一个包含书名和作者的信息,并执行一个SQL INSERT。 "好的,如何让这件事发生?- 业务服务(BS)将接收标题和作者,将其传递给业务操作(BO)。BO执行SQL代码。 "现在我有了信息将遵循的路径,我需要理解信息。它是什么?" - 我有很多方法可以发送数据。我可以在一个文件中发送,或一个REST应用程序,甚至一个电子邮件。让我们选择文件来开始简单。我的BS将接收文件,读取它并将其信息发送给BO。BO执行查询。 开始干活 你可以从你对最有信心的代码部分开始。 业务服务 Business Service/BS 我是从业务服务BS开始的. 现在我很清楚,我需要实现Request(请求)类,以便它存储数据和将被发送到的操作。 业务操作Business Operation/BO 请求Request Request类很简单。我只用它来存储一些数据。%XML.Adaptor是为了在管理门户上显示Request以进行错误管理。 业务操作Business Operation BO有一个消息地图,为每一种到达的消息提供足够的方向。例如,如果在BS中,在SendRequestSync方法和Request参数中,我使用了不同的类型,如 "Demo.Books.BO.SearchTable.Request",我可以用这种消息类型创建另一个MapItem,引用一个Search方法。 方法Method 在这里,你实现了操作应该做的任何事情. 管理门户设置 最后,为了让事情顺利进行,请遵循以下步骤: 管理门户 > 互操作性 > 列表 > 生产 > 新建(Management Portal > Interoperability > List > Productions > New) 门户网站将用Production生产信息创建一个类。 然后,你用你创建的类添加一个服务,并设置文件路径(你将把输入文件放在哪里)和工作路径。 另外,用你创建的类添加一个操作,必要时指定其设置。 几点观察 业务操作可以接收一个同步请求,这意味着服务只有在BO检索到它的信息后才会响应,因为服务的响应取决于BO的响应,例如,如果它必须在表操作中执行一个搜索。因为Production生产只执行INSERT操作,BS只需要发送信息,BO就会INSERT;不需要响应,所以我们可以有一个异步请求。 讨论例如文件和SQL适配器等适配器的规格超出了本文的范围,本文的目的是对编码和步骤进行概述,以便更好地理解实际工作。 欢迎与我联系--我很乐意提供任何可能的帮助!
文章
姚 鑫 · 十一月 6, 2021

第六十八章 SQL命令 SAVEPOINT

# 第六十八章 SQL命令 SAVEPOINT 在事务中标记一个点。 # 大纲 ```java SAVEPOINT pointname ``` ## 参数 - `pointname` - 保存点的名称,指定为标识符。 # 描述 `SAVEPOINT`语句标记事务中的一个点。建立保存点使能够执行事务回滚到保存点,撤消在此期间完成的所有工作并释放在此期间获得的所有锁。在长期运行的事务或具有内部控制结构的事务中,通常希望能够回滚事务的一部分,而不撤消在事务期间提交的所有工作。 保存点的建立会递增`$TLEVEL`事务级别计数器。回滚到保存点会将`$TLEVEL`事务级别计数器递减到紧接在保存点之前的值。可以在一个事务内建立最多`255`个保存点。超过这个保存点数量会导致`SQLCODE-400`致命错误,这是在SQL执行期间捕获的`` 异常。终端提示符将当前事务级别显示为提示符的`TLn:`前缀,其中`n`是介于`1`和`255`之间的整数,表示当前$TLEVEL计数。 每个保存点都与一个保存点名称相关联,这是一个唯一的标识符。保存点名称不区分大小写。保存点名称可以是分隔的标识符。 - 如果指定的保存点没有点名,或者指定的点名不是有效的标识符或SQL保留字,则会发出运行时`SQLCODE-301`错误。 - 如果指定点名称以`“SYS”`开头的保存点,则会发出运行时`SQLCODE-302`错误。这些保存点名称是保留的。 保存点名称不区分大小写;因此`resetpt`,` ResetPt`和`“RESETPT”`是相同的点名。此重复项是在回滚到保存点期间检测到的,而不是在保存点期间检测到的。当指定具有重复点名的`SAVEPOINT`语句时, IRIS会递增事务级别计数器,就像点名是唯一的一样。但是,最近的点名称会覆盖保存点名称表中所有先前重复的值。因此,当指定回滚到保存点点名时, IRIS会回滚到具有该点名称的最近建立的保存点,并相应地递减事务级别计数器。但是,如果再次指定回滚到同名的保存点点名,则会生成`SQLCODE-375`错误,并显示`%msg:Cannot Rollback to Unestabled SavePoint‘name’`,整个事务将回滚,`$TLEVEL`计数恢复为`0`。 ## 使用保存点 嵌入式SQL、动态SQL、ODBC和JDBC支持`SAVEPOINT`语句。在`JDBC`中,`connection.setSavepoint(Pointname)`设置一个保存点,`connection.roll back(Pointname)`回滚到指定的保存点。 如果已建立保存点,请执行以下操作: - 回滚到保存点点名将回滚自指定保存点以来所做的工作,删除该保存点和所有中间保存点,并将`$TLEVEL`事务级别计数器递减删除的保存点数量。如果`pointname`不存在或已经回滚,此命令将回滚整个事务,将`$TLEVEL`重置为`0`,并释放所有锁。 - 回滚回滚当前事务期间完成的所有工作,回滚自`START TRANSACTION`以来完成的工作。它将`$TLEVEL`事务级别计数器重置为零,并释放所有锁。请注意,常规回滚会忽略保存点。 - `COMMIT`提交在当前事务期间完成的所有工作。它将`$TLEVEL`事务级别计数器重置为零,并释放所有锁。请注意,提交操作会忽略保存点。 在事务内发出第二个`START TRANSACTION`对保存点或`$TLEVEL`事务级别计数器没有影响。 如果事务操作未能成功完成,则会发出`SQLCODE-400`错误。 # 示例 以下嵌入式`SQL`示例创建具有两个保存点的事务: ```java ClassMethod Savepoint() { n SQLCODE,%ROWCOUNT,%ROWID &sql( START TRANSACTION ) &sql( DELETE FROM Sample.Person WHERE Name = NULL ) if SQLCODE = 100 { w !,"没有要删除的空名称记录" } elseif SQLCODE '= 0 { &sql(ROLLBACK) } else { w !,%ROWCOUNT," 已删除Null Name记录" } &sql( SAVEPOINT svpt_age1 ) &sql( DELETE FROM Sample.Person WHERE Age = NULL ) if SQLCODE = 100 { w !,"没有要删除的空年龄记录" } elseif SQLCODE '= 0 { &sql(ROLLBACK TO SAVEPOINT svpt_age1) } else { w !,%ROWCOUNT," 删除空年龄记录" } &sql( SAVEPOINT svpt_age2 ) &sql( DELETE FROM Sample.Person WHERE Age > 65 ) if SQLCODE = 0 { &sql(COMMIT) } elseif SQLCODE = 100 { &sql(COMMIT) } else { &sql( ROLLBACK TO SAVEPOINT svpt_age2 ) w !,"退休年龄删除失败" } &sql(COMMIT) &sql(COMMIT) } ``` # ObjectScript和SQL事务 **使用`TSTART`和`TCOMMIT`的`ObjectScript`事务处理与使用SQL语句`START transaction`、`SAVEPOINT`和`COMMIT`的SQL事务处理不同,也不兼容。 ObjectScript和InterSystems SQL都提供了对嵌套事务的有限支持。 ObjectScript事务处理不与`SQL`锁控制变量交互; 特别需要关注的是`SQL`锁升级变量。 应用程序不应该尝试混合这两种事务处理类型。** 如果事务涉及`SQL`更新语句,则事务应该由`SQL START transaction`语句启动,并使用`SQL COMMIT`语句提交。 使用`TSTART/TCOMMIT`嵌套的方法可以包含在事务中,只要它们不初始化事务。 方法和存储过程通常不应该使用SQL事务控制语句,除非按照设计,它们是事务的主控制器。
文章
Kelly Huang · 七月 12, 2023

当 GPT 与 FHIR 碰撞出火花:利用Open API 的规范力量

FHIR 通过提供标准化数据模型来构建医疗保健应用程序并促进不同医疗保健系统之间的数据交换,彻底改变了医疗保健行业。由于 FHIR 标准基于现代 API 驱动的方法,因此移动和 Web 开发人员更容易使用它。然而,与 FHIR API 交互仍然具有挑战性,尤其是在使用自然语言查询数据时。 隆重推出FHIR - AI 和 OpenAPI 链应用程序,该解决方案允许用户使用自然语言查询与 FHIR API 进行交互。该应用程序使用OpenAI 、 LangChain和Streamlit构建,简化了查询 FHIR API 的过程并使其更加用户友好。 FHIR OpenAPI 规范是什么? OpenAPI 规范(以前称为 Swagger,目前是OpenAPI Initiative的一部分)已成为软件开发领域的重要工具,使开发人员能够更有效地设计、记录 API 并与 API 交互。 OpenAPI 规范定义了一种标准的机器可读格式来描述 RESTful API,提供了一种清晰一致的方式来理解其功能并有效地使用它们。 在医疗保健领域,FHIR 成为数据交换和互操作性的领先标准。为了增强FHIR的互操作能力, HL7正式记录了FHIR OpenAPI规范,使开发人员能够将FHIR资源和操作无缝集成到他们的软件解决方案中。 FHIR OpenAPI 规范的优点: 标准化 API 描述:OpenAPI 规范提供 FHIR 资源、操作和交互的全面且标准化的描述。开发人员可以轻松了解基于 FHIR 的 API 的结构和功能,从而更轻松地构建集成并与医疗保健系统交互。 促进互操作性:促进开发人员之间的协作,推动 FHIR 标准和最佳实践的采用。该规范提供了一种通用语言和框架,用于讨论基于 FHIR 的集成和实现,促进开发人员之间的协作。 增强的文档和测试:交互式文档和测试套件,以便更好地理解和验证。开发人员可以创建详细的API文档,使其他开发人员更容易理解和使用基于FHIR的API。基于规范的测试套件可以对API集成进行全面的测试和验证,确保医疗数据交换的可靠性和准确性。 改进的开发人员体验:自动生成客户端库和 SDK 以实现无缝集成。这简化了集成过程,并减少了将 FHIR 功能合并到应用程序中所需的时间和精力 FHIR、OpenAI 和 OpenAPI Chain 如何协同工作? FHIR - AI 和 OpenAPI Chain应用程序利用 LangChain 来加载和解析 OpenAPI 规范( OpenAPI Chain )。之后,根据这些规范,通过 OpenAI 给出的提示链旨在理解自然语言查询并将其转换为适当的 FHIR API 请求。用户可以用简单的语言提出问题,应用程序将与所选的 FHIR API 交互以检索相关信息。 例如,用户可能会问:“患者 John Doe (ID 111) 的最新血压读数是多少?”然后,应用程序会将此查询转换为 FHIR API 请求,获取所需的数据,并以易于理解的格式将其呈现给用户。 FHIR - AI 和 OpenAPI 链的优势 用户友好的交互:通过允许用户使用自然语言查询与 FHIR API 交互,该应用程序使非技术用户可以更轻松地访问和分析医疗保健数据。 提高效率:该应用程序简化了查询 FHIR API 的过程,减少了获取相关信息所需的时间和精力。此外,它还有可能减少从应用程序中查找任何特定信息的点击次数(花费的时间)。 可定制:FHIR 标准简化了从任何 FHIR 服务器检索一致数据的过程,从而可以轻松定制。它可以轻松配置为与任何 FHIR API 无缝集成,为不同的医疗保健数据需求提供灵活且适应性强的解决方案。 FHIR 入门 - AI 和 OpenAPI 链 要开始使用 FHIR - AI 和 OpenAPI Chain 应用程序,请按照以下步骤操作: 从OpenAI Platform获取 OpenAI API 密钥。 获取 FHIR 服务器 API 端点。您可以使用自己的示例 FHIR 服务器(需要未经身份验证的访问),也可以按照InterSystems IRIS FHIR 学习平台中给出的说明创建临时示例服务器。 在线试用该应用程序或使用提供的说明在本地进行设置。 通过集成人工智能和自然语言处理功能,FHIR - AI 和 OpenAPI Chain 应用程序提供了一种与 FHIR API 交互的更直观的方式,使所有技术背景的用户都更容易访问和分析医疗数据。 如果您发现我们的应用程序很有前途,请在大奖赛中投票! 如果您能想到使用此实现的任何潜在应用程序,请随时在讨论线程中分享它们。 @Ikram Shah 致敬原创作者~
文章
jieliang liu · 五月 22

在IRIS中基于XSLT实现互联互通临床文档到 FHIR 资源的转换

基于 XSLT 的互联互通临床文档到 FHIR 资源转换 国家卫健委互联互通成熟度评测中的临床共享文档,作为医疗信息交换的重要载体,采用了XML标准的文档格式。随着医疗信息化的发展,FHIR(Fast Healthcare Interoperability Resources)作为新一代医疗信息交换标准,因其简洁性、灵活性和RESTful架构,逐渐成为医疗数据交换的理想选择。将共享文档文档转换为FHIR资源,能够有效促进不同医疗系统间的数据互通,提升医疗信息的利用价值。 XSLT(可扩展样式表语言转换)是一种用于将XML文档转换为其他XML文档或文本格式的语言。在医疗数据转换场景中,XSLT凭借其强大的XML处理能力,成为共享文档到FHIR转换的理想工具。 我们知道共享文档文档是一种结构化的XML文档,通常包含以下主要部分: - 文档头(Document Header):包含文档元数据,如文档类型、创建时间、作者等 - 临床数据部分(Clinical Sections):按章节组织的临床信息,如问题列表、用药记录、检查报告等 - 数据条目(Entries):具体的临床数据项,如诊断、药物、检验结果等 FHIR则采用了资源导向的设计理念,每个临床概念都被建模为独立的资源,通过RESTful API进行访问。FHIR资源具有以下特点: - 模块化设计:每个资源专注于特定的临床领域,如Patient(患者)、Condition(疾病)、MedicationRequest(用药申请)等 - 灵活的数据结构:支持复杂的数据类型和嵌套结构 - 丰富的语义表达:通过代码系统和扩展机制提供标准化的术语支持 互联互通共享文档到FHIR的转换并非简单的格式转换,而是需要在保持临床语义的前提下,将共享文档的文档结构映射到FHIR的资源模型。这需要深入理解两种标准的数据模型和语义。 在Intersystems IRIS中,我们内嵌了我司创建的SDA医疗数据模型,此模型也是以xml为结构,并且在IRIS中已经开箱即用地实现了SDA模型到FHIR资源的转化,所以在IRIS互联互通套件中把共享文档向FHIR资源转化的思路就变成了由互联互通共享文档->SDA文档->FHIR资源的转化流程。(关于我司SDA数据模型的介绍参见https://docs.intersystems.com/irisforhealth20251/csp/docbook/Doc.View.cls?KEY=HXSDA_ch_sda#HXSDA_sda_structure) 下面是一个使用XSLT将互联互通文档中的信息转换为SDA再使用IRIS开箱即用功能转为FHIR资源的示例: 在互联互通套件中,我们已经实现了由互联互通文档向SDA转化的xslt,这些xslt在https://gitee.com/jspark/CCHDist 代码仓库的https://gitee.com/jspark/CCHDist/tree/master/image-iris/src/hccns/Setting/HCC 部分可以找到,文件名为HCC2SDA.xsl,其内引用的其他xlst模版也在相同文件夹下。 互联互通文档转化为SDA文档的示例代码: Method HCCToSDA(pHCC As %Stream.Object, ByRef pSDA As %Stream.Object) As %Status { Set tSC = $$$OK Try { If $ISOBJECT(pSDA) &&(pSDA.%IsA("%Stream.Object")) { } Else { Set tSC = $$$ERROR(-10000,"Input parameter pSDA is not a stream") Quit } // transfter to SDA first Set tSlash = $Case($system.Version.GetOS(),"Windows":"\",:"/") Set tXSL="file:///"_$SYSTEM.Util.InstallDirectory()_"csp"_tSlash_"xslt"_tSlash_"HCC"_tSlash_"HCC2SDA.xsl" Set tSC = ..Transformer.Transform(pHCC,tXSL,.tSDA) Quit:($$$ISERR(tSC)) D pSDA.CopyFrom(tSDA) // store SDA for debug purpose if (..Debug=1) { Set ..FileGUID = $Case($system.Version.GetOS(),"Windows":"\",:"/")_$SYSTEM.Util.CreateGUID() Set tSDAFile=##class(%Stream.FileCharacter).%New() Set tSC=tSDAFile.LinkToFile(..DebugPath_..FileGUID_".XML") Set tSDAFile.TranslateTable="UTF8" Do tSDAFile.Write(pSDA.Read()) Do tSDAFile.%Save(),tSDAFile.%Close() Kill tSDAFile } } Catch ex { Set tSC=ex.AsStatus() } Quit tSC } 由SDA转化为FHIR资源的示例代码: Method SDAToFHIR(pSDA As %Stream.Object, ByRef pFHIR As %Stream.Object) As %Status { If ($ISOBJECT(pSDA) && pSDA.%IsA("%Stream.Object")) {} Else { Quit $$$ERROR(-10000,"Input parameter pSDA is not a stream") } Set tSC = $$$OK Try { // transfer SDA to FHIR Set tFHIR = ##class(HS.FHIR.DTL.Util.API.Transform.SDA3ToFHIR).TransformStream(pSDA,"HS.SDA3.Container","R4") If ('$ISOBJECT(tFHIR)) { K tFHIR Set tSC = $$$ERROR(-10000,"SDA transfer to FHIR error!") Quit } Else { Set:($Get(pFHIR)="") pFHIR=##class(%Stream.TmpCharacter).%New() Do pFHIR.Write(tFHIR.bundle.%ToJSON()) Do tFHIR.%Close() // store SDA for debug purpose if ..Debug=1 { Set tFHIRFile=##class(%Stream.FileCharacter).%New() Set tSC=tFHIRFile.LinkToFile(..DebugPath_..FileGUID_".JSON") Set tFHIRFile.TranslateTable="UTF8" Do pFHIR.Rewind() Do tFHIRFile.Write(pFHIR.Read()) D tFHIRFile.%Save(),tFHIRFile.%Close() K tFHIRFile } } } Catch ex { Set tSC=ex.AsStatus() } Quit tSC } 综合起来互联互通文档转化为FHIR资源的示例,这里pFHIR的输出就是得到的fhir资源: Method HCCToFHIR(pHCC, ByRef pFHIR) As %Status { If ($ISOBJECT(pHCC) && pHCC.%IsA("%Stream.Object")) {} Else { Quit $$$ERROR(-10000,"Input parameter pHCC is not a stream") } // transfter to SDA first Set tSDA = ##class(%Stream.GlobalCharacter).%New() Set tSC = ..HCCToSDA(pHCC,.tSDA) Quit:($$$ISERR(tSC)) tSC // store SDA for debug purpose Set tSC = ..SDAToFHIR(tSDA,.pFHIR) Quit tSC } 在我们的IRIS互联互通套件里实现了由共享文档生成再到FHIR资源的转化的全部流程,https://gitee.com/jspark/CCHDist 可以在这个代码仓库中找到IRIS互联互通套件的代码实现以及相关介绍文档。
文章
Claire Zheng · 六月 19

2025.1 打造时尚现代的互操作性用户体验

互操作性用户界面现在包括可以在所有互操作性产品中使用的 DTL 编辑器和生产配置应用程序的现代化用户体验。您可以在现代化视图与标准视图之间切换。所有其他互操作性屏幕仍采用标准用户界面。请注意,仅对这两个应用程序进行了更改,我们在下面确定了当前可用的功能。 要在升级前试用新屏幕,您可以点击这里,从我们的社区工具包网页中下载 2025.1 版:https://evaluation.intersystems.com/Eval/。请观看“学习服务”中的简短教程构建集成:一种新的用户体验,了解对这些屏幕进行的用户增强! 生产配置 - 配置任务简介 生产配置:在以下版本的生产配置中受支持: 创建/编辑/复制/删除主机 停止/启动主机 编辑生产设置 停止/启动生产 源代码控制集成:支持上述配置功能的源代码控制集成。 分屏 视图:用户可以直接从“生产配置”屏幕打开“规则编辑器”和“DTL 编辑器”,在分屏视图中查看和编辑产品中包含的规则和转换。 增强的筛选功能:使用顶部的搜索框,您可以搜索和筛选各种业务组件,包括多种类别、DTL 和子转换。 使用左侧边栏可以独立于主面板进行搜索,查看各种主机和类别中的搜索结果。 批量编辑主机类别:通过从生产配置中添加主机,您可以为生产添加新类别或编辑现有类别。 可展开路由器:可以展开路由器,内联查看所有规则、转换和连接。 重新设计的主机连接:现在,在选择业务主机时,将呈现直接连接和间接连接,您可以查看消息能够采用的完整路径。 将鼠标悬停在任何出站或入站主机上可以进一步区分连接。如果开启仅显示连接的主机开关,将仅筛选所选主机及其连接。 DTL 编辑器 - DTL 工具简介 源代码控制集成:支持源代码控制集成。 VS Code 集成:用户可以在其 VS Code IDE 中查看此版本的 DTL 编辑器。 嵌入式 Python 支持:此版本的 DTL 编辑器现在支持嵌入式 Python。 DTL 测试:可以在此版本的 DTL 编辑器中使用 DTL 测试实用工具。 切换面板布局:DTL 编辑器支持侧面到侧面和顶部到底部布局。 点击顶部功能区的布局按钮可以体验此功能。 撤消/重做:用户可以使用撤消/重做按钮撤消和重做所有尚未保存为代码的操作。 “生成空段”参数:GENERATEEMPTYSEGMENTS 参数可用于为缺失的字段生成空段。 子转换查看:用户可以点击眼睛图标,在新选项卡中打开子转换 DTL,查看子转换。 滚动: 单独滚动:将光标放置在 DTL 的左右两部分(源和目标)其中之一的上方,并用滚轮或触控板垂直移动各段,可以单独滚动各个部分。 联合滚动:将光标放置在图的中间,可以联合滚动源部分和目标部分。 字段自动补全:自动补全适用于:“源”、“目标”和“条件”字段以及源类、源文档类型、目标类、目标文档类型。 顺序编号:使用可视化编辑器,您可以打开和关闭查看每个段的序数和完整路径表达式的功能。 轻松引用:当操作编辑器中的某个字段获得焦点时,在图形化编辑器中双击某个段会在操作编辑器中的当前光标位置插入相应的段引用。 同步:点击可视化编辑器中的一个元素,可以在操作编辑器中高亮显示相应的行。 📣号召性用语📣 如果您有任何反馈,请通过以下途径提供给我们: ✨跨所有互操作性的新功能:在 Ideas 门户中输入想法,或在 InterSystems Ideas 门户中参与其他想法。 对于新想法,请在您的帖子上添加“互操作性”标签或对列表中已提出的功能进行投票! 💻跨所有互操作性的一般用户体验反馈:请在下面输入您的反馈或参与其他评论。 🗒对现代化应用程序的建议/反馈(如上所述):请在下面输入您的反馈或参与其他评论。 请考虑利用 Global Masters 的机会,与团队进行互动,参与不公开的指导反馈会议,并获得积分! 点击此处,通过 Global Masters 报名参加这些会议。 如果您希望以私人形式提供任何其他反馈,请通过电子邮件将您的想法或问题发送至:ux@intersystems.com
文章
姚 鑫 · 三月 29, 2021

第十三章 使用动态SQL(七)

# 第十三章 使用动态SQL(七) # SQL元数据 动态SQL提供以下类型的元数据: - 在“准备”之后,描述查询类型的元数据。 - 在“准备”之后,描述查询中选择项的元数据(“列”和“扩展列信息”)。 - 在准备之后,描述查询参数的元数据:参数,`:var`参数和常量。 (语句参数,形式参数和对象) - 执行之后,描述查询结果集的元数据。在执行Prepare操作(`%Prepare()`,`%PrepareClassQuery()`或`%ExecDirect()`)之后,可以使用`%SQL.StatementMetadata`属性值。 - 可以直接为最新的`%Prepare()`返回`%SQL.Statement`元数据属性。 - 可以返回包含`%SQL.StatementMetadata`属性的oref的`%SQL.Statement%Metadata`属性。这使可以返回多个准备操作的元数据。 `SELECT`或`CALL`语句返回所有这些元数据。 `INSERT`,`UPDATE`或`DELETE`返回语句类型元数据和形式参数。 ## 语句类型元数据 使用`%SQL.Statement`类进行`Prepare`之后,可以使用`%SQL.StatementMetadata statementType`属性来确定准备哪种类型的SQL语句,如以下示例所示。本示例使用`%SQL.Statement%Metadata`属性来保存和比较两`个Prepare`操作的元数据: ```java /// d ##class(PHA.TEST.SQL).MetaData() ClassMethod MetaData() { SET tStatement = ##class(%SQL.Statement).%New() SET myquery1 = "SELECT TOP ? Name,Age,AVG(Age),CURRENT_DATE FROM Sample.Person" SET myquery2 = "CALL Sample.SP_Sample_By_Name(?)" SET qStatus = tStatement.%Prepare(myquery1) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET meta1 = tStatement.%Metadata SET qStatus = tStatement.%Prepare(myquery2) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET meta2 = tStatement.%Metadata WRITE "语句类型query 1: ",meta1.statementType,! WRITE "语句类型query 2: ",meta2.statementType,! WRITE "End of metadata" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).MetaData() 语句类型query 1: 1 语句类型query 2: 45 End of metadata ``` `statementType`属性的“类引用”条目列出了语句类型整数代码。最常见的代码是1(`SELECT`查询)和45(`CALL`到存储的查询)。 可以使用`%GetImplementationDetails()`实例方法返回相同的信息,如成功准备的结果中所述。 执行查询后,可以从结果集中返回语句类型名称(例如`SELECT`)。 ## 选择项目Select-item元数据 使用`%SQL.Statement`类准备`SELECT`或`CALL`语句之后,可以通过显示所有元数据或指定各个元数据项来返回有关查询中指定的每个选择项列的元数据。此列元数据包括ODBC数据类型信息,以及客户端类型和InterSystems Objects属性的起源以及类类型信息。 以下示例返回最近准备的查询中指定的列数: ```java /// d ##class(PHA.TEST.SQL).MetaData1() ClassMethod MetaData1() { SET myquery = "SELECT %ID AS id,Name,DOB,Age,AVG(Age),CURRENT_DATE,Home_State FROM Sample.Person" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} WRITE "Number of columns=",tStatement.%Metadata.columnCount,! WRITE "End of metadata" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).MetaData1() Number of columns=7 End of metadata ``` 以下示例返回列名称(或列别名),ODBC数据类型,最大数据长度(精度),以及每个`SELECT`项目字段的比例: ```java /// d ##class(PHA.TEST.SQL).MetaData2() ClassMethod MetaData2() { SET $NAMESPACE="SAMPLES" SET myquery=2 SET myquery(1)="SELECT Name AS VendorName,LastPayDate,MinPayment,NetDays," SET myquery(2)="AVG(MinPayment),$HOROLOG,%TABLENAME FROM Sample.Vendor" SET rset = ##class(%SQL.Statement).%New() SET qStatus = rset.%Prepare(.myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET x=rset.%Metadata.columns.Count() SET x=1 WHILE rset.%Metadata.columns.GetAt(x) { SET column=rset.%Metadata.columns.GetAt(x) WRITE !,x," ",column.colName," 是数据类型 ",column.ODBCType WRITE " 大小为 ",column.precision," 规模 = ",column.scale SET x=x+1 } WRITE !,"End of metadata" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).MetaData2() 1 VendorName 是数据类型 12 大小为 50 规模 = 0 2 LastPayDate 是数据类型 9 大小为 10 规模 = 0 3 MinPayment 是数据类型 8 大小为 6 规模 = 0 4 NetDays 是数据类型 4 大小为 3 规模 = 0 5 Aggregate_5 是数据类型 8 大小为 20 规模 = 0 6 Expression_6 是数据类型 12 大小为 255 规模 = 0 7 Literal_7 是数据类型 12 大小为 13 规模 = 0 End of metadata ``` 下面的示例使用`%SQL.StatementMetadata%Display()`实例方法显示所有列元数据: ```java /// d ##class(PHA.TEST.SQL).MetaData3() ClassMethod MetaData3() { SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare("SELECT %ID AS id,Name,DOB,Age,AVG(Age),CURRENT_DATE,Home_State FROM Sample.Person") IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} DO tStatement.%Metadata.%Display() WRITE !,"End of metadata" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).MetaData3() Columns (SQLRESULTCOL, property 'columns'): Column Name Type Prec Scale Null Label Table Schema CType ----------- ---- ---- ----- ---- ------------ ------------ ------------ ----- id 4 10 0 0 id Person Sample 5 Name 12 50 0 0 Name Person Sample 10 DOB 9 10 0 1 DOB Person Sample 2 Age 4 10 0 1 Age Person Sample 5 Aggregate_5 2 20 8 1 Aggregate_5 14 Expression_6 9 11 0 2 Expression_6 2 Home_State 12 2 0 1 Home_State Person Sample 10 Extended Column Info (SQLRESULTCOL) Flags: 1:AutoIncrement,2:CaseSensitive,3:Currency,4:ReadOnly,5:RowVersion, 6:Unique,7:Aliased,8:Expression,9:Hidden,10:Identity,11:KeyColumn, 12:RowId Column Name Linked Prop Type Class Flags ------------ --------------------- --------------------- ----------------------- id Sample.Person Y,N,N,Y,N,Y,Y,N,N,Y,Y,Y Name Sample.Person.Name %Library.String N,N,N,N,N,N,N,N,N,N,N,N DOB Sample.Person.DOB %Library.Date N,N,N,N,N,N,N,N,N,N,N,N Age Sample.Person.Age %Library.Integer N,N,N,N,N,N,N,N,N,N,N,N Aggregate_5 %Library.Numeric N,N,N,Y,N,N,Y,N,N,N,N,N Expression_6 %Library.Date N,N,N,Y,N,N,Y,Y,N,N,N,N Home_State Sample.Address.State %Library.String N,N,N,N,N,N,N,N,N,N,N,N Statement Parameters (property 'parameters'): Nbr. Type precision scale nullable colName columntype ---- ---- --------- ----- -------- ------------ ---------- Formal Parameters (property 'formalParameters'): Nbr. Type precision scale nullable colName columntype ---- ---- --------- ----- -------- ------------ ---------- Objects: Col Column Name Extent ExportCall --- ----------- ----------------- ----------------------------- 1 id Sample.Person ##class(Sample.Person).%SQLQuickLoad ``` 这将返回所选字段的两个表列表。第一列元数据表列出了列定义信息: 显示标题 | `%SQL.StatementColumn`属性 | 描述 ---|---|--- Column Name | colName |列的SQL名称。如果为该列提供了别名,则会在此处列出该列的别名,而不是字段名称。名称和别名将被截断为12个字符。对于表达式,聚合,文字,主机变量或子查询,列出了分配的`“ Expression_n”`,`“ Aggregate_n”`,`“ Literal_n”`,`“ HostVar_n”`或`“ Subquery_n”`标签(`n`为`SELECT`项序列号)。如果为表达式,聚合,文字,主机变量或子查询分配了别名,则在此处列出该别名。 Type| ODBCType |ODBC数据类型的整数代码。请注意,这些ODBC数据类型代码与CType数据类型代码不同。 Prec| precision|精度或最大长度(以字符为单位)。日期,时间,PosixTime和TimeStamp数据类型中描述了TIME数据类型的精度和小数位元数据。 Scale| scale| 小数位数的最大数目。对于整数或非数值返回0。日期,时间,PosixTime和TimeStamp数据类型中描述了`TIME`数据类型的精度和小数位元数据。 Null| isNullable| 一个整数值,指示是否将列定义为`Non-NULL(0)`,或者是否允许`NULL(1)`。 RowID返回0。如果`SELECT`项是可能导致`NULL`的聚合或子查询,或者如果它指定`NULL`文字,则该项设置为1。如果`SELECT`项是表达式或主机变量,则设置此项到2(无法确定)。 Label| label| 列名或列别名(与列名相同)。 Table| tableName| SQL表名称。即使为表指定了别名,也始终在此处列出实际的表名。如果`SELECT`项是表达式或聚合,则不会列出任何表名。如果`SELECT`项是子查询,则列出子查询表名称。 Schema| schemaName|表的架构名称。如果未指定架构名称,则返回系统范围的默认架构。如果`SELECT`项是表达式或聚合,则不会列出任何模式名称。如果SELECT项是子查询,则不会列出任何架构名称。 CType| clientType| 客户端数据类型的整数代码。 第二列元数据表列出了扩展列信息。扩展列信息表列出了具有十二个布尔标志(SQLRESULTCOL)的每一列,这些标志被指定为Y(是)或N(否): 显示标题 | `%SQL.StatementColumn`属性 | 描述 ---|---|--- 1: AutoIncrement| isAutoIncrement| TRowID和IDENTITY字段返回Y。 2: CaseSensitive| isCaseSensitive |具有`%EXACT`归类的字符串数据类型字段返回Y。引用`%SerialObject`嵌入式对象的属性返回Y。 3: Currency| isCurrency| 使用%Library.Currency数据类型定义的字段,例如`MONEY`数据类型。 4: ReadOnly |isReadOnly|表达式,聚合,文字,`HostVar`或子查询返回Y。RowID,IDENTITY和RowVersion字段返回Y。 5: RowVersion| isRowVersion|RowVersion字段返回Y。 6: Unique| isUnique| 定义为具有唯一值约束的字段。 RowID和IDENTITY字段返回Y。 7: Aliased| isAliased| 系统为非字段选择项提供别名。因此,无论用户是否通过指定列别名替换了系统别名,表达式,聚合,文字,HostVar或子查询都将返回Y。此标志不受用户指定的列别名的影响。 8: Expression| isExpression| 表达式返回Y。 9: Hidden| isHidden| 如果使用`%PUBLICROWID`或`SqlRowIdPrivate = 0`(默认值)定义表,则RowID字段返回N。否则,RowID字段返回Y。引用`%SerialObject`嵌入式对象的属性返回Y。 10: Identity| isIdentity| 定义为IDENTITY字段的字段返回Y。如果未隐藏RowID,则RowID字段返回Y。 11: KeyColumn| isKeyColumn| 定义为主键字段或外键约束目标的字段。 RowID字段返回Y。 12: RowID| isRowId |ROWID和Identity字段返回Y. 扩展列信息元数据表列出了每个选定字段的列名称(SQL名称或列别名),链接属性(链接的持久性类属性)和类型类(数据类型类)。请注意,链接属性列出了持久性类名(不是SQL表名)和属性名(不是列别名)。 - 对于普通表字段(`SELECT Name FROM Sample.Person`): `Linked Prop=Sample.Person.Name, Type Class=%Library.String`. - 对于表格的RowID (`SELECT %ID FROM Sample.Person`): `Linked Prop= [none], Type Class=Sample.Person`. - 对于表达式,聚合,文字,`HostVar`或子查询 (`SELECT COUNT(Name) FROM Sample.Person`): `Linked Prop= [none], Type Class=%Library.BigInt`. - 供参考`%Serial Object`嵌入式对象属性 (`SELECT Home_State FROM Sample.Person`). `Linked Prop=Sample.Address.State, Type Class=%Library.String.` - 对于引用`%SerialObject`嵌入式对象的字段(`SELECT Home FROM Sample.Person`). `Linked Prop=Sample.Person.Home, Type Class=Sample.Address`. 在此示例中,`Sample.Person`中的`Home_State`字段引用`%SerialObject`类`Sample.Address`的`State`属性。 下面的示例返回带有一个形式参数(也就是语句参数)的被调用存储过程的元数据: ```java /// d ##class(PHA.TEST.SQL).MetaData4() ClassMethod MetaData4() { SET $NAMESPACE="SAMPLES" SET mysql = "CALL Sample.SP_Sample_By_Name(?)" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(.mysql) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } DO tStatement.%Metadata.%Display() WRITE !,"End of metadata" } ``` 它不仅返回列(字段)信息,还返回语句参数,形式参数和对象的值。 以下示例返回具有三个形式参数的的元数据。这三个参数之一用问号(`?`)指定,使其成为语句参数: ```java /// d ##class(PHA.TEST.SQL).MetaData5() ClassMethod MetaData5() { SET $NAMESPACE="SAMPLES" SET mycall = "CALL personsets(?,'MA')" SET tStatement = ##class(%SQL.Statement).%New(0,"sample") SET qStatus = tStatement.%Prepare(mycall) IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} DO tStatement.%Metadata.%Display() WRITE !,"End of metadata" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQL).MetaData5() Columns (SQLRESULTCOL, property 'columns'): Column Name Type Prec Scale Null Label Table Schema CType ----------- ---- ---- ----- ---- ------------ ------------ ------------ ----- Extended Column Info (SQLRESULTCOL) Flags: 1:AutoIncrement,2:CaseSensitive,3:Currency,4:ReadOnly,5:RowVersion, 6:Unique,7:Aliased,8:Expression,9:Hidden,10:Identity,11:KeyColumn, 12:RowId Column Name Linked Prop Type Class Flags ------------ --------------------- --------------------- ----------------------- Statement Parameters (property 'parameters'): Nbr. Type precision scale nullable colName columntype ---- ---- --------- ----- -------- ------------ ---------- 1 12 50 0 2 name 1 Formal Parameters (property 'formalParameters'): Nbr. Type precision scale nullable colName columntype ---- ---- --------- ----- -------- ------------ ---------- 1 4 4 0 2 _isc_sp_ret_val 5 2 12 50 0 2 name 1 3 12 50 0 2 state 1 Objects: Col Column Name Extent ExportCall --- ----------- ----------------- ----------------------------- End of metadata ``` 请注意,此元数据不返回任何列信息,但是“语句参数”,“形式参数”列表包含列名称和数据类型。 ## Query参数元数据 使用`%SQL.Statement`类进行`Prepare`之后,您可以返回有关查询参数的元数据:输入参数(指定为问号(`?`)),输入主机变量(指定为`:varname`)和常量(文字值)。可以返回以下元数据: - `?`参数`:parameterCount`属性 - ODBC数据类型为`?`参数`:%SQL.StatementMetadata%Display()`实例方法“语句参数”列表。 - ?,v(:var)和c(常量)参数的列表:`%GetImplementationDetails()`实例方法,如成功准备的结果中所述。 - ?,v(:var)和c(常量)参数的ODBC数据类型:`formalParameters`属性。 `%SQL.StatementMetadata%Display()`实例方法“形式参数”列表。 - 查询文本,其中显示以下参数:`%GetImplementationDetails()`实例方法,如成功准备结果中所述。 语句元数据`%Display()`方法列出了“语句参数”和“形式参数”。对于每个参数,它列出了顺序参数号,ODBC数据类型,精度,小数位数,该参数是否可为空(2表示始终提供一个值)及其对应的属性名称(colName)和列类型。 请注意,某些ODBC数据类型以负整数形式返回。 下面的示例按顺序返回每个查询参数(`?`,`:var`和常量)的ODBC数据类型。请注意,`TOP`参数以数据类型12(`VARCHAR`)而不是数据类型4(`INTEGER`)返回,因为可以指定`TOP ALL`: ```java /// d ##class(PHA.TEST.SQL).MetaData6() ClassMethod MetaData6() { SET myquery = 4 SET myquery(1) = "SELECT TOP ? Name,DOB,Age+10 " SET myquery(2) = "FROM Sample.Person" SET myquery(3) = "WHERE %ID BETWEEN :startid :endid AND DOB=?" SET myquery(4) = "ORDER BY $PIECE(Name,',',?)" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(.myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET prepmeta = tStatement.%Metadata WRITE "Number of ? parameters=",prepmeta.parameterCount,! SET formalobj = prepmeta.formalParameters SET i=1 WHILE formalobj.GetAt(i) { SET prop=formalobj.GetAt(i) WRITE prop.colName," type= ",prop.ODBCType,! SET i=i+1 } WRITE "End of metadata" } ``` 执行`Execute`之后,无法从查询结果集元数据中获取参数元数据。在结果集中,所有参数均已解析。因此`parameterCount = 0`,`formalParameters`不包含任何数据。 ## Query结果集元数据 使用`%SQL.Statemen`t类执行`Execute`之后,可以通过调用返回结果集元数据: - `%SQL.StatementResult`类的属性。 - `%SQL.StatementResult%GetMetadata()`方法,访问`%SQL.StatementMetadata`类属性。 ### %SQL.StatementResult属性 执行查询操作后,`%SQL.StatementResult`返回: - `%StatementType`属性返回与最近执行的SQL语句相对应的整数代码。以下是这些整数代码的部分列表:`1 = SELECT; 2 = INSERT; 3 = UPDATE; 4 = DELETE or TRUNCATE TABLE; 9 = CREATE TABLE; 15 = CREATE INDEX; 45 = CALL`. - `%StatementTypeName`计算的属性基于`%StatementType`返回最近执行的SQL语句的命令名称。此名称以大写字母返回。请注意,`TRUNCATE TABLE`操作将作为`DELETE`返回。即使执行了更新操作,`INSERT OR UPDATE`也将作为`INSERT`返回。 - `%ResultColumnCount`属性返回结果集行中的列数。 下面的示例显示这些属性: ```java /// d ##class(PHA.TEST.SQL).MetaData7() ClassMethod MetaData7() { SET myquery = "SELECT TOP ? Name,DOB,Age FROM Sample.Person WHERE Age > ?" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute(10,55) IF rset.%SQLCODE=0 { WRITE "Statement type=",rset.%StatementType,! WRITE "Statement name=",rset.%StatementTypeName,! WRITE "Column count=",rset.%ResultColumnCount,! WRITE "End of metadata" } ELSE { WRITE !,"SQLCODE=",rset.%SQLCODE," ",rset.%Message } } ``` ### %SQL.StatementResult %GetMetadata() 执行之后,可以使用`%SQL.StatementResult %GetMetadata()`方法访问`%SQL.StatementMetadata`类属性。这些是在Prepare之后由`%SQL.Statement%Metadata`属性访问的相同属性。 以下示例显示了属性: ```java /// d ##class(PHA.TEST.SQL).MetaData8() ClassMethod MetaData8() { SET myquery=2 SET myquery(1)="SELECT Name AS VendorName,LastPayDate,MinPayment,NetDays," SET myquery(2)="AVG(MinPayment),$HOROLOG,%TABLENAME FROM Sample.Vendor" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus = tStatement.%Prepare(.myquery) IF qStatus'=1 { WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT } SET rset = tStatement.%Execute() IF rset.%SQLCODE=0 { SET rsmeta=rset.%GetMetadata() SET x=rsmeta.columns.Count() SET x=1 WHILE rsmeta.columns.GetAt(x) { SET column=rsmeta.columns.GetAt(x) WRITE !,x," ",column.colName," is data type ",column.ODBCType WRITE " with a size of ",column.precision," and scale = ",column.scale SET x=x+1 } } ELSE { WRITE !,"SQLCODE=",rset.%SQLCODE," ",rset.%Message } WRITE !,"End of metadata" } ``` 请注意,结果集元数据不提供参数元数据。这是因为`Execute`操作会解析所有参数。因此,在结果集中,`parameterCount = 0`,而`formalParameters`不包含任何数据。 # 审核动态SQL InterSystems IRIS支持动态SQL语句的可选审核。启用%System /%SQL / DynamicStatement系统审核事件时,将执行动态SQL审核。默认情况下,未启用此系统审核事件。 如果启用%System /%SQL / DynamicStatement,则系统将自动审核在系统范围内执行的每个`%SQL.Statement`动态语句。审核将信息记录在审核数据库中。 要查看审核数据库,请依次转到管理门户,系统管理,选择安全性,审核,然后查看审核数据库。可以将“事件名称”过滤器设置为DynamicStatement,以将View Audit Database限制为Dynamic SQL语句。审核数据库列出了时间(本地时间戳),用户,PID(进程ID)和事件的描述。说明指定动态SQL语句的类型。例如,SQL SELECT语句(`%SQL.Statement`)或SQL CREATE VIEW语句(`%SQL.Statement`)。 通过选择事件的详细信息链接,可以列出其他信息,包括事件数据。事件数据包括执行的SQL语句和该语句的任何参数的值。例如: ```java SELECT TOP ? Name , Age FROM Sample . MyTest WHERE Name %STARTSWITH ? /*#OPTIONS {"DynamicSQLTypeList":",1"} */ Parameter values: %CallArgs(1)=5 %CallArgs(2)="Fred" ``` 事件数据的总长度(包括语句和参数)为3,632,952个字符。如果该语句和参数长于3632952,则事件数据将被截断。 InterSystems IRIS还支持ODBC和JDBC语句的审核(事件名称= XDBCStatement),以及嵌入式SQL语句的审核(事件名称= EmbeddedStatement)。