搜索​​​​

清除过滤器
文章
姚 鑫 · 四月 23, 2021

第五章 优化查询性能(二)

# 第五章 优化查询性能(二) # 使用索引 **索引通过维护常见请求数据的排序子集,提供了一种优化查询的机制。 确定哪些字段应该被索引需要一些思考:太少或错误的索引和关键查询将运行太慢; 太多的索引会降低插入和更新性能(因为必须设置或更新索引值)。** ## 什么索引 要确定添加索引是否会提高查询性能,请从管理门户SQL接口运行查询,并在性能中注意全局引用的数量。 添加索引,然后重新运行查询,注意全局引用的数量。 一个有用的索引应该减少全局引用的数量。 可以通过在`WHERE`子句或`ON`子句条件前使用`%NOINDEX`关键字来防止使用索引。 应该为联接中指定的字段(属性)编制索引。左外部联接从左表开始,然后查看右表;因此,应该为右表中的字段建立索引。在下面的示例中,应该为`T2.f2`编制索引: ```sql FROM Table1 AS T1 LEFT OUTER JOIN Table2 AS T2 ON T1.f1 = T2.f2 ``` 内部联接应该在两个`ON`子句字段上都有索引。 执行“显示计划”,然后找到第一张map。 如果查询计划中的第一个项目是`“Read master map”`,或者查询计划调用的模块的第一个项目是`“Read master map”`,则查询的第一个映射是主映射,而不是索引映射。 因为主映射读取数据本身,而不是数据索引,这总是表明查询计划效率低下。 除非表相对较小,否则应该创建一个索引,以便在重新运行该查询时,查询计划的第一个映射表示“读取索引映射”。 应该索引在`WHERE`子句`equal`条件中指定的字段。 可能希望索引在`WHERE`子句范围条件中指定的字段,以及`GROUP BY`和`ORDER BY`子句中指定的字段。 在某些情况下,基于范围条件的索引可能会使查询变慢。如果绝大多数行满足指定的范围条件,则可能会发生这种情况。例如,如果将`QUERY`子句`WHERE Date < CURRENT_DATE` 用于大多数记录来自以前日期的数据库,则在`DATE`上编制索引实际上可能会降低查询速度。这是因为查询优化器假定范围条件将返回相对较少的行数,并针对此情况进行优化。可以通过在范围条件前面加上`%noindex`来确定是否发生这种情况,然后再次运行查询。 如果使用索引字段执行比较,则比较中指定的字段的排序规则类型应与其在相应索引中的排序规则类型相同。例如,`SELECT`的`WHERE`子句或联接的`ON`子句中的`Name`字段应该与为`Name`字段定义的索引具有相同的排序规则。如果字段排序规则和索引排序规则之间存在不匹配,则索引可能效率较低或可能根本不使用。 ![image](/sites/default/files/inline/images/1_39.png) ![image](/sites/default/files/inline/images/2_21.png) ## 索引配置选项 以下系统范围的配置方法可用于优化查询中索引的使用: - 要将主键用作`IDKey`索引,请设置`$SYSTEM.SQL.Util.SetOption()`方法,如下所示 `SET status=$SYSTEM.SQL.Util.SetOption("DDLPKeyNotIDKey",0,.oldval)`. 默认为`1` - 要将索引用于`SELECT DISTINCT`查询,请设置`$SYSTEM.SQL.Util.SetOption()`方法,如下所示: `SET status=$SYSTEM.SQL.Util.SetOption("FastDistinct",1,.oldval)`. 默认为`1` ## 索引使用情况分析 可以使用以下任一方法按SQL缓存查询分析索引使用情况: - 管理门户索引分析器SQL性能工具。 - `%SYS.PTools.UtilSQLAnalysis`方法`indexUsage()`、`tableScans()`、`tempIndices()`、`joinIndices()`和`outlierIndices()`。、 ## 索引分析 可以使用以下任一方法从管理门户分析SQL查询的索引使用情况: - 选择系统资源管理器,选择工具,选择SQL性能工具,然后选择索引分析器。 - 选择系统资源管理器,选择SQL,然后从工具下拉菜单中选择索引分析器。 索引分析器提供当前命名空间的SQL语句计数显示和五个索引分析报告选项。 ![image](/sites/default/files/inline/images/3_17.png) ![image](/sites/default/files/inline/images/4_13.png) ![image](/sites/default/files/inline/images/5_6.png) ### SQL语句计数 在SQL索引分析器的顶部有一个对命名空间中的所有SQL语句进行计数的选项。按收集SQL语句按钮。SQL索引分析器显示“正在收集SQL语句...”当计票进行时,然后“完成!”当清点完毕后。SQL语句分为三类进行计数:缓存查询计数、类方法计数和类查询计数。这些计数针对整个当前命名空间,不受架构选择选项的影响。 对应的方法是`%SYS.PTools.UtilSQLAnalysis`类中的`getSQLStmts()`。 可以使用清除语句按钮删除当前命名空间中收集的所有语句。该按钮调用`clearSQLStatements()`方法。 ### 报告选项 可以检查当前命名空间中选定架构的缓存查询报告,也可以(通过不选择架构)检查当前命名空间中所有缓存查询的报告。可以在此分析中跳过或包括系统类查询、`INSERT`语句和/或`IDKEY`索引。“架构选择”和“跳过选项”复选框是用户自定义的。 指数分析报告选项包括: - 索引使用:此选项获取当前名称空间中的所有缓存查询,为每个查询生成显示计划,并记录每个查询使用每个索引的次数以及名称空间中所有查询对每个索引的总使用量。这可用于显示未使用的索引,以便可以删除或修改这些索引以使其更有用。结果集从最少使用的索引到最常使用的索引排序。 对应的方法是`%SYS.PTools.UtilSQLAnalysis`类中的`indexUsage()`。要导出此方法生成的分析数据,请使用`exportIUAnalysis()`方法。 - 使用表扫描的查询:此选项标识当前名称空间中执行表扫描的所有查询。如果可能,应避免表扫描。表扫描并不总是可以避免的,但是如果一个表有大量的表扫描,那么应该检查为该表定义的索引。通常,表扫描列表和临时索引列表会重叠;修复其中一个会删除另一个。结果集按从最大块计数到最小块计数的顺序列出表格。提供了显示计划链接以显示对帐单文本和查询计划。 对应的方法是`%SYS.PTools.UtilSQLAnalysis`类中的`tableScans()`。要导出此方法生成的分析数据,请使用`exportTSAnalysis()`方法。 - 带临时索引的查询:此选项标识当前名称空间中构建临时索引以解析SQL的所有查询。有时,使用临时索引会有所帮助并提高性能,例如,基于范围条件构建一个小索引,然后InterSystems IRIS可以使用该索引按顺序读取主映射。有时,临时索引只是不同索引的子集,可能非常有效。其他情况下,临时索引会降低性能,例如,扫描`master may`以在具有条件的特性上构建临时索引。这种情况表明缺少所需的索引;应该向与临时索引匹配的类添加索引。结果集按从最大块计数到最小块计数的顺序列出表格。提供了显示计划链接以显示对帐单文本和查询计划。 对应的方法是`%SYS.PTools.UtilSQLAnalysis`类中的`tempIndices()`。要导出此方法生成的分析数据,请使用`exportTIAnalysis()`方法。 - 缺少联接索引的查询:此选项检查当前名称空间中具有联接的所有查询,并确定是否定义了支持该联接的索引。它将可用于支持联接的索引从0(不存在索引)排序到4(索引完全支持联接)。外部联接需要一个单向索引。内联接需要双向索引。默认情况下,结果集只包含`JoinIndexFlag 21 AND %NOINDEX E.Age < 65 ``` ![image](/sites/default/files/inline/images/6_5.png)
文章
Claire Zheng · 八月 17, 2021

FHIR标准和国际基于FHIR的互联互通实践(7):国际互联互通实践

国际互联互通的需求是在不断增长,这跟咱们国内的情况是非常类似的。这些年美国在互联互通领域的政策跟实践还是不少的,比如大家可能听到过包括“有意义的使用(Meaningful Use)”,“21世纪治愈法案(21st Century Cures Act)”,还有更多的政策上的驱动。这里先介绍一下“有意义的使用(Meaningful Use)”。 “有意义的使用(Meaningful Use)”其实源于2009年奥巴马签署的美国复兴与投资法案。“有意义的使用(Meaningful Use)”是它的俗称,标准的名称应该是“电子健康档案激励计划”。电子健康档案激励计划提出了很多使用标准,这些标准促进了认证电子病历的使用,分成了三个阶段:从2011年开始是第一阶段,第一阶段使用电子健康档案的技术来做数据的获取共享。之后在2014年做了第二个阶段,第二阶段来强调护理协调和患者信息的交流。第三个阶段是从2017年开始,它的目标是做一些更高级应用,例如电子处方临床学的支持。不过在“有意义的使用(Meaningful Use)”的过程中间,遇到了很多问题,因为它专注在电子病历的认证和使用上,很多用户抱怨已经被认证过的电子病历的能力是不足的。 所以在2018年4月份的时候, CMS将“有意义的使用(Meaningful Use)”,从“电子档案激励计划”改成了“促进互操作性计划”。在2018年的时候,“有意义的使用(Meaningful Use)”相当于是终结了。其替代者,也就是“促进互通的计划”,它现在还在执行。 上图是2020年的评分的标准,大家可以看到红框里面标出来的都是主要的评分项,基本上都集中在互联互通的能力之上的。现在美国正在执行的这些互操作性的政策,其实主要是美国的21世纪治愈法案(21st Century Cures Act),这个法案下有很多的子法律条款,提出了更具体、更可操作、更有计划性的互操作路线。 我挑几个给大家做个介绍。 首先第一个就是可信交换框架和共同协议草案,它设定了4个目标。这4个目标都是针对于提高医疗结果为目标的,它在这个目标里定义了未来的分阶段来进行设定目标,未来4年的目标就是建立所谓的“学习健康体系”。“学习健康体系”实现了临床、科研、公卫、患者个人、医保等各个利益方的互操作能力的健康信息体系。要实现这个目标,美国有一些具体的行动。行动之一就是ONC发布了一个“美国互操作核心数据集(U.S. Core Data for Interoperability)”。这个数据集跟咱们卫生数据集有点像,但是它包含的是数据类、数据元以及规范,建议了术语绑定的数据集。 这个数据集其实挺小的。这个数据集目前现在有两个版本,第一版本已经发布了。 上图包括了尚未发布的第二版的核心数据集,总共只有18个种类,这些数据集比我们的数据集要小得多。美国联邦政府现在要求所有的医疗机构都必须以HL7 FHIR R4的标准来共享交换核心数据集里面所有的患者数据。 还有另外一个法案,最新的“互操作能力和患者访问法规(The Interoperability and Patient Access final rule [CMS-9115-F])”。法规要求每个医院和保险公司都要开放针对于患者个人的数据查询的能力,需要使用FHIR的API,使用FHIR 4.0.1的标准来作为数据交换的基础,患者可以自己自由选择第三方的应用来查看、分享自己的数据。 这个法规从今年(2021年)1月1号开始实施的,目标要在三年之内能够完成。新一届的美国政府还要求所有的机构全都要全面的支持FHIR R4的API和FHIR的美国核心 Profile(总共25个Profile)。所以 FHIR在美国处于一个被快速采纳的阶段。 当然还有其他的机构,除了美国政策驱动之外,行业标准开发组织也在积极采用FHIR,例如说IHE。目前IHE总共开发了251个场景用例,现在基于FHIR已经达到了41个。IHE不仅在使用FHIR资源作为内容的格式,它也在使用FHIR的API来替代原来的互操作实现的方式。 FHIR在科研领域也大展身手。目前用于前瞻性临床研究的这些临床数据,其实很大程度上还是人工收集和分析出来的,需要手动查看临床数据图表、收集这些数据,增加了研究时间并且创造了很多出错的机会。通过FHIR标准,能够用FHIR标准化的协议和API使数据分析流程更加现代化。 “火神计划(Project Vulcan)”就是针对于科研发展的计划,这个计划的目标是将临床研究跟临床护理能够连接起来,让整个利益相关方能够聚集在一起,以弥合临床护理和临床研究之间的现有差距,战略性地连接行业合作,最大限度地利用集体资源,并提供集成的工具和资源。 这个项目在2019年9月份启动,目前有三个方向: 第一个就是建立表型数据的交换标准,其目标是使用FHIR在各种环境中使用和交换匿名化的患者病历级信息,这需要对FHIR资源进行进一步开发; 第二个是通过FHIR获取电子病历系统的数据,并直接生成科研所需数据的能力。当前正在和FDA协作处理药品数据; 第三个是采用FHIR资源描述科研活动计划。当前目标是将“临床数据交换标准联盟”的ODM-XML格式的活动计划转换为FHIR标准。 “火神加速器计划(HL7 Accelerator Program: Project Vulcan)”是一个比较典型的、能够体现出美国的标准合作应用模式的计划。 这个模式是将各个利益方都纳入进来。在支持科研和药品研发的计划中间包含了很多利益方,比如说标准开发组织HL7国际,政府代表FDA,科研机构(比如约翰逊霍普金斯医学院),当然还有行业团体(比如临床研究组织协会),很多的技术厂商(包括InterSystems公司)。通过多方参与来加速标准在科研和药品研发中间的落地。 其他国家也有很多启动了FHIR标准的互联互通的计划——例如沙特,沙特采用FHIR来进行排班计划,德国试图建立FHIR文档仓库上的所有能力,英国在使用FHIR来管理儿童的健康预警。 除此之外,有一个“全球数字健康伙伴关系”联盟,这是一个由30个国家和地区,以及世卫组织组成的合作的项目,中国香港特别行政区也在里面。它为全球参与者提供了交流数据共享、电子健康记录、电子处方、患者访问等最佳实践的机会。上图汇总了这些参与方使用互操作标准的情况。可以看到FHIR的标准的采纳度已经达到了17个,很快就会赶上最流行的V2,V2现在是19个。 以上全球对于互操作标准的采纳以及FHIR使用情况的简单介绍,如需了解更多,欢迎留言与我们交流!
文章
姚 鑫 · 七月 9, 2021

第三十二章 XML基础知识概念

# 第三十二章 XML基础知识概念 ## attribute 以下形式的名值对: ```java ID="QD5690" ``` 属性位于元素中,如下所示,一个元素可以有任意数量的属性。 ```xml Cromley,Marcia N. ``` ## CDATA区域 表示不应该验证的文本,如下所示: ```xml ``` 一个`CDATA`(字符数据)区段不能包含字符串`]]`>,因为这个字符串标志着区段的结束。 这也意味着`CDATA`区段不能嵌套。 注意,`CDATA`部分的内容必须符合为XML文档指定的编码,XML文档的其余部分也是如此。 ## comment 不是XML文档主数据的一部分的插入说明。 注释是这样的: ```xml ``` ## content model 对XML元素的可能内容的抽象描述。 可能的内容模型如下: - 空内容模型(不允许有子元素或文本节点) - 简单内容模型(只允许文本节点) - 复杂内容模型(只有子元素) - 混合内容模型(允许子元素和文本节点) 在所有情况下,元素可能有也可能没有属性; 短语内容模型不涉及元素中属性的存在或不存在。 ## default namespace 给定上下文中任何非限定元素所属的名称空间。 添加的默认名称空间没有前缀。 例如: ```xml Isaacs,Rob G. 1981-01-29 ``` 因为这个名称空间声明没有使用前缀,所以``、``和``元素都属于这个名称空间。 注意,下面的XML没有使用默认名称空间,它实际上等同于前面的示例: ```xml Isaacs,Rob G. 1981-01-29 ``` ## DOM 文档对象模型(DOM)是表示XML和相关格式的对象模型。 ## DTD(文档类型定义) 包含在XML文档或外部文件中的一系列文本指令。 它定义了可以在文档中使用的所有有效元素和属性。 dtd本身不使用XML语法。 ## element 一个元素通常由两个标记(一个开始标记和一个结束标记)组成,可能包含文本和其他元素。 元素的内容是这两个标记之间的所有内容,包括文本和任何子元素。 下面是一个完整的XML元素,包含开始标记、文本内容和结束标记: ```xml Cromley,Marcia N. ``` 一个元素可以有任意数量的属性和任意数量的子元素。 空元素可以包含一个开始标记和一个结束标记,也可以只包含一个标记。 下面的例子是等价的: ```xml ``` 在实践中,元素很可能引用数据记录的不同部分,例如 ```xml Barnes,Gerry 1981-04-23 ``` ## entity (在XML文件中)表示一个或多个字符的文本单元。 一个实体有以下结构: ```xml &characters; ``` ## global element 全局元素和局部元素的概念适用于使用名称空间的文档。 全局元素的名称与局部元素的名称放在一个单独的符号空间中。 全局元素是其类型具有全局作用域的元素,即其类型在相应XML模式的顶层定义的元素。 作为``元素的子元素的元素声明被认为是全局声明。 任何其他元素声明都是局部元素,除非它通过ref属性引用全局声明,这实际上使它成为全局元素。 属性可以是全局的,也可以是局部的。 ## local element 不是全局的XML元素。 局部元素不显式属于任何名称空间,除非元素是限定的。 参见限定元素和全局元素。 ## namespace 名称空间是为标识符定义域的惟一字符串,以便基于xml的应用程序不会混淆一种类型的文档和另一种类型的文档。 它通常以URL(统一资源位置)的形式给出一个URI(统一资源指示器),它可能与实际的web地址对应,也可能不对应。 例如,`“http://www.w3.org”`是一个名称空间。 使用以下语法之一包含命名空间声明: ```xml xmlns="your_namespace_here" pre:xmlns="your_namespace_here" ``` 在这两种情况下,名称空间只在插入名称空间声明的上下文中使用。 在后一种情况下,名称空间与给定的前缀(pre)相关联。 当且仅当元素或属性也有此前缀时,元素或属性就属于该名称空间。 例如: ```xml Ravazzolo,Roberta X. 1943-10-24 ``` 命名空间声明使用`s01`前缀。 ``元素也使用了这个前缀,所以这个元素属于这个名称空间。 但是,``和``元素并不显式地属于任何命名空间。 ## 处理指令(PI) 一种指令(在序言中),旨在告诉应用程序如何使用XML文档或如何处理它。 一个例子; 这将样式表与文档关联起来。 ```xml ``` ## prolog XML文档中根元素之前的部分。 序言以XML声明(指示使用的XML版本)开始,然后可能包括DTD声明或模式声明以及处理指令。 (从技术上讲,不需要`DTD`或模式。 此外,从技术上讲,可以将两者放在同一个文件中。) ## root, root element, document element 每个XML文档都要求在最外层只有一个元素。 这称为根元素、根元素或文档元素。 根元素在序言之后。 ## qualified 如果显式地将元素或属性分配给名称空间,则该元素或属性是限定的。 考虑下面的例子,其中``的元素和属性是不限定的: ```xml Frost,Sally O. 1957-03-11 ``` 在这里,名称空间声明使用`s01`前缀。 没有默认的命名空间。 ``元素也使用了这个前缀,因此该元素属于这个名称空间。 ``和``元素或``属性没有前缀,因此它们不显式属于任何名称空间。 相反,考虑以下情况,其中``的元素和属性是限定的: ```xml Frost,Sally O. 1957-03-11 ``` 在本例中,``元素定义了一个默认名称空间,该名称空间应用于子元素和属性。 注意:XML模式属性`elementFormDefault`属性和`attributeFormDefault`属性控制在给定的模式中元素和属性是否被限定。 在InterSystems IRIS XML支持中,使用类参数来指定元素是否限定。 ## schema 一种为一组XML文档指定元信息的文档,可作为DTD的替代。 与DTD一样,可以使用模式来验证特定XML文档的内容。 对于某些应用程序,XML模式提供了与`dtd`相比的几个优势,包括: - XML模式是有效的XML文档,因此更容易开发操作模式的工具。 - XML模式可以指定一组更丰富的特性,并包含值的类型信息。 形式上,模式文档是符合W3 XML模式规范的XML文档(在`https://www.w3.org/XML/Schema`)。 它遵守XML规则,并使用一些额外的语法。 通常,文件的扩展名是`.xsd`。 ## style sheet 用XSLT编写的文档,描述如何将给定的XML文档转换为另一个XML或其他“人类可读”的文档。 ## text node 包含在开始元素和相应结束元素之间的一个或多个字符。 例如: ```xml sample text node ``` ## type 对数据解释的限制。 在XML模式中,每个元素和属性的定义对应于一个类型。 类型可以是简单的,也可以是复杂的。 每个属性都有一个简单类型。 简单类型还表示没有属性和子元素(只有文本节点)的元素。 复杂类型表示其他元素。 下面的模式片段展示了一些类型定义: ```xml ``` ## unqualified 如果没有显式地将元素或属性分配给名称空间,则该元素或属性是非限定的。 ## well-formed XML 遵循XML规则的XML文档或片段,例如有一个结束标记来匹配一个开始标记。 ## XML declaration 指示给定文档中使用的XML版本(以及可选的字符集)的语句。 如果包含,它必须是文档中的第一行。 一个例子: ```xml ``` ## XPath XPath (XML路径语言)是一种基于XML的表达式语言,用于从XML文档中获取数据。 结果可以是标量,也可以是原始文档的XML子树。 ## XSLT XSLT(可扩展样式表语言转换)是一种基于XML的语言,用于描述如何将给定的XML文档转换为另一个XML或其他“人类可读的”文档。
文章
Lilian Huang · 十月 24, 2022

自动部署自适应分析Adaptive Analytics

当我们使用IRIS时,我们通常有能力快速的部署一个现成使用的BI基础模块(数据、分析立方体和IRIS BI仪表盘)。当我们开始使用Adaptive Analytics时,我们通常希望有同样的功能。Adaptive Analytics拥有我们需要的所有工具。文档中包含了对如何使用开放的网络API的描述。用户界面和引擎之间的所有交互也都是通过内部的Web API发生的,并且可以被发射出来。 有必要将这两个过程自动化:在容器中部署Adaptive Analytics和直接部署到服务器系统。为此,最简单的方法是使用bash脚本来处理API。我们唯一需要的第三方应用程序是一个名为jq的JSON文件解析器。你可以使用以下命令来安装它: apt update apt install -y jq 首先,我们需要登录以获得一个API访问令牌。这个令牌也适用于引擎本身的方法。我们必须将访问令牌保存在一个变量中,因为现在我们几乎在每个请求中都需要它。对于一个标准的登录和密码admin/admin,该命令将看起来像这样: TOKEN=$(curl -u admin:admin --location --request GET 'http ://localhost:10500/default/auth') 接下来,我们需要一个活跃的引擎来与API交互。如果没有许可证检查,引擎是不可用的,所以我们要为它提供许可证。Web API没有这个选项,所以我们必须使用引擎命令: curl --location --request PUT 'http://127.0.0.1:10502/license' --header 'Content-Type: application/json' --data-binary "@$license" 许可证变量包含许可证文件的路径。当我们在不同的平台和不同容量的PC上做一些测试时,我们注意到一个奇特的现象。在系统启动脚本完成后,Adaptive Analytics可能会出现这样的情况:服务还没有启动,但初始化脚本已经将控制权交给了我们的脚本。为了确保引擎启动和运行,我们在一个循环中组织发送许可证,直到我们收到许可证已被接受的消息。这样做的代码看起来像这样: RESPONSE="1" TIME=1 echo "Waiting for engine ......." while [ "$RESPONSE" != "200 OK" ] do for license in /root/license/* do RESPONSE =$(curl --location --request PUT 'http://127.0.0.1:10502/license' --header 'Content-Type: application/json' --data-binary "@$license" | jq -r '.status.message') sleep 1s done echo "$TIME seconds waiting" TIME=$(($TIME + 1)) done echo "Engine started" 打印时间变量对于调试启动问题很有用。正如你所看到的,我们在一个特定的文件夹中循环浏览文件,以便不被文件名所束缚。我们将在未来再次使用这种方法。 现在我们可以与web API互动,我们可以将我们的项目上传到Adaptive Analytics。我们使用下面的代码将所有放在指定文件夹中的项目发送到引擎: for cube in /root/cubes/* do sleep 1s curl --location --request POST "http://localhost:10500/api/1.0/org/default/"Authorization: Bearer $TOKEN" --header "Content-Type: application/xml" --data-binary "@$cube" sleep 5s done 如果我们希望我们的项目可以被BI系统使用,我们必须发布它们。幸运的是,在API中也有这样的方法。由于项目在导入时得到一个随机的唯一ID,首先,我们应该把这些ID解析成一个变量: PROJECTS_ID=$(curl --location --request GET "http://localhost:10500/api/1.0/org/default/projects" -- header "Authorization: Bearer $TOKEN" | jq -r '.response[].id') 然后我们需要遍历变量中的所有项目并发布它们。 for project in $PROJECTS_ID do curl --location --request POST "http://localhost:10500/api/1.0/org/default/project/$project/publish" --header "Authorization: Bearer $TOKEN" sleep 1s done 现在我们需要告诉Adaptive Analytics如何连接到IRIS,以及应该如何命名连接,以便我们的项目能够接收它们。在API中有一个方法,但是它有不同的URL地址。事实证明,这是一个有文档记录的引擎方法。所以如果我们想使用它,我们需要访问一个不同的端口。该方法接受JSON文件形式的连接信息,但不可能从Adaptive Analytics以相同的格式导出它。API根据请求返回给我们的JSON文件有一些额外/缺失的字段。以下是用于IRIS连接的JSON文件模板: { "platformType": "iris", "name": "name_you_want", "connectionId": "name_you_want", "overrideConnectionId":true, "extraProperties": { "namespace": "Your_namespase ", "udafMode": "customer_managed" }, "aggregateSchema": "ATSCALE", "readOnly":false, "isImpersonationEnabled": false, "isCanaryAlwaysEnabled": false, "isPartialAggHitEnabled": false, "subgroups": [ { " name": "iris", "hosts": "iris", "port": 1972, "connectorType": "iris", "username": "Your_username", "password":"Your_password", "isKerberosClientEnabled": false , "queryRoles": [ "large_user_query_role", "small_user_query_role", "system_query_role" ], "extraProperties": {}, "connectionGroupsExtraProps": {} } ] } 值得一提的是“udafMode”参数:如果在IRIS中安装了UDAF,我们可以将其设置为“customer_managed”,如果没有安装UDAF,则设置为“none”。要了解更多关于UDAF是什么、为什么需要它以及在安装它时可能会遇到什么缺陷的信息,请查看这篇()文章。 当我们为所有连接准备好这样的文件时,我们应该从文件夹中加载它们: for connection in /root/connections/* do sleep 1s curl --location --request POST "http://localhost:10502/connection-groups/orgId/default" - -header "Authorization: Bearer $TOKEN" --header "Content-Type: application/json" --data-binary "@$connection" sleep 5s done 接下来,我们希望有机会将Logi Report Designer连接到Adaptive Analytics实例。这就是为什么我们需要模拟Adaptive Analytics初始化的第一个步骤之一。因此,我们为hive连接设置端口: sleep 1s curl --location --request POST "http://localhost:10502/organizations/orgId/default" --header "Authorization : Bearer $TOKEN" --header "Content-Type: application/json" --data-raw '{"hiveServer2Port": 11111}' 为了不超过IRIS社区版本中的连接限制,我们必须限制Adaptive Analytics的同时连接数量。根据我们的经验,为了防止在测试场景中超过限制,在5个连接中保留3个是值得的。我们使用下面的代码模拟更改设置: for settings_list in /root/settings/* do curl --location --request PATCH 'http://localhost:10502/settings' --header "Authorization: Bearer $TOKEN" --header "Content-Type: application/json" --data-binary "@$settings_list" done 在文件夹下的JSON文件中,写入如下内容: { "name": "bulkUpdate", "elements": [ { "name": "connection.pool.group.maxConnections", "value": "3" }, { "name": "connection.pool.user.maxConnections", "value": "3 " } ] } 根据我使用Adaptive Analytics的经验,这种限制对性能影响不大。 修改设置后,需要重新启动引擎: curl --location --request POST 'http://localhost:10500/api/1.0/referrerOrg/default/support/service-control/engine/restart' 现在我们有了自动准备Adaptive Analytics工作所需的一切。为方便您,我们已将上述脚本发表在: https://github.com/intersystems-community/dc-analytics/blob/master/atscale-server/entrypoint.sh 在这个存储库中,您还可以找到使用聚合更新计划的脚本。聚合是在IRIS中配置UDAF时由Adaptive Analytics生成的特殊表。这些表存储聚合查询的缓存结果,从而加快了BI系统的工作速度。Adaptive Analytics有一个更新聚合的内部逻辑,但是自己控制这个过程要方便得多。 我们曾经遇到过这样的情况:这些表存储的数据已经过时好几天了。相应地,Adaptive Analytics返回的值与真实值明显不同。为了避免这种情况,我们在更新BI系统中的数据之前设置了每日的聚合更新。 您可以在Adaptive Analytics的web界面中按每个立方体配置更新。然后,您将能够使用脚本将时间表导出和导入到另一个实例,或者使用导出的时间表文件作为备份。如果不想单独配置每个数据集,还可以在应用程序中找到一个脚本,将所有数据集设置为相同的更新计划。 最后一件便于自动化的事情是在开发阶段从Adaptive Analytics备份项目本身。为了设置这种自动化,我们编写了另一个OEX应用程序,它每天一次将所选项目保存到Git存储库中。 最后一件便于自动化的事情是在开发阶段从Adaptive Analytics备份项目本身。为了设置这种自动化,我们编写了另一个OEX应用程序OEX application,它每天一次将所选项目保存到Git存储库中。 初学者指南描述了在新报表文件中创建小部件的过程。如果使用“插入”选项卡或“组件”窗口,也可以将图表或表格插入到现有的报表中。
文章
Michael Lei · 四月 9

Open AI 与 IRIS 集成 - 文件管理

人工智能不仅限于通过带有说明的文本生成图像,或通过简单的指示创建叙事。您还可以制作图片的变体,或为已有图片添加特殊背景。此外,您还可以获得音频转录,无论其语言和说话者的语速如何。让我们来分析一下文件管理是如何工作的。 问题描述 在分析 OpenAI 有关需要将文件作为输入值的方法的信息时,必须使用 multipart/form-data 提供参数。 在 IRIS 中,我们知道如何使用 JSON 内容创建对 POST 方法的调用。但在这种情况下,使用带有 Base64 格式文件内容的参数并不实用。 要在多址/表单数据(multipart/form-data)中包含文件内容,必须使用%Net.MIMEPart.类。 要在我们的调用中包含文件,应创建一个与类对象 %Net.MIMEPart 相关联的 Content-Disposition 标头 set content = ##class(%Net.MIMEPart).%New() set contentDisposition = "form-data; name="_$CHAR(34)_"image"_$CHAR(34) set contentDisposition = contentDisposition_"; filename="_$CHAR(34)_fileName_$CHAR(34) do content.SetHeader("Content-Disposition",contentDisposition) 由于我们使用请求类来保留进程的值,因此我们必须将 Base64 内容转换为流,以构成内容的主体。 我们可以使用StreamUtils实用程序将 Base64 转换为流。 注意:"pImage"变量包含文件内容的 Base64 字符串。 Do ##class(HS.Util.StreamUtils).Base64Encode(pImage, .tStream) Set content.Body = tStream 不过,在 2023 年全球峰会上,我有幸从 InterSystems 专家那里学到了一个更好的技巧。他告诉我,这种执行方法比 StreamUtils 更有效,因为 StreamUtils 最后会循环读取字符串并记录到 Stream 中。这个解决方案就像使用 JSON 并将其转换为 Stream 的 Get 一样简单。 set contentfile = {} set contentfile.file = pImage set content.Body = contentfile.%Get("file",,"stream<base64") 在调用中包含了所需的所有参数后,我们就可以创建一个新的 MIMEPart 类来封装部件了。 Set rootMIME = ##class(%Net.MIMEPart).%New() do rootMIME.Parts.Insert(content) set writer = ##class(%Net.MIMEWriter).%New() set tSC = writer.OutputToStream(tHttpRequest.EntityBody) set tSC = writer.WriteMIMEBody(rootMIME) Set tContentType = "multipart/form-data; boundary="_rootMIME.Boundary set tSC = ..Adapter.SendFormDataArray(.tHttpResponse, "POST", tHttpRequest,,,url) 这就是我们如何将文件内容发送到我们在 OpenAI 中需要的方法。 Image files图像文件 图像方法允许您发送图片并进行变化。由于所有插图都必须是 PNG 格式,因此当我们以 Base64 格式指明文件内容时,文件名会随机生成,并带有 PNG 扩展名。下面是一个如何更改照片的示例。 Original Variation 正如你所看到的,程序以自己的方式解释指令。它认为公司的标志是一个圆圈,所以用另一个圆圈代替了它。它还发现办公室有一扇玻璃门,于是用另一扇玻璃门代替,但暂时用砖墙代替。此外,它还修改了衬衫的颜色,并改变了男子手臂的位置。此外,OpenIA 还允许您通过提供一个蒙版来编辑图像,蒙版上有您想要插入提示内容的区域。利用同一幅图像,我应用了一个去掉图像背景的蒙版。 Original Mask 当我要求它把我传送到牙买加海滩时,得到了如下结果: 现在,下次见到亲朋好友时,您就可以炫耀自己的假期了 😊 Image图像 Endpoint: POST https://api.openai.com/v1/images/variations 它允许你对已有的图像进行修改。由于它不需要提示您要如何修改,因此我们必须相信人工智能的品味,它会如何解释这张图片。此外,我们还可以定义大小和返回结果的方式,无论是通过链接还是 Base64 格式的内容。 输入参数如下: image: 必选 在这里,您要提及要转换的图像文件。 n: 可选. 默认为 1 在此区域,您可以决定生成图像的最大数量。(使用 1 到 10 之间的数字)。 size: 可选. 默认 1024x1024 定义图像大小,其数值必需为 “256x256”, “512x512”, 或者 “1024x1024”. response_format: 可选.默认是“url” 这个参数是关于您希望如何返回生成图像的格式。此处的值应为 "url "或 "b64_json"。 Endpoint: POST https://api.openai.com/v1/images/edits 它可以让你修改现有的图片,根据掩码文件,按照提示创建图片。此外,我们还可以指定尺寸和返回结果的方式,无论是通过链接还是 Base64 格式的内容。输入参数如下: image: 必选 如上. mask: 必选 这部分是关于所应用的蒙版图像文件. n: 可选,默认 1 如上 size: 可选,默认 1024x1024 如上 response_format: 可选. 默认是 “url” 如上 Audio files声音文件 OpenAI 管理的不仅仅是图像。我们还可以使用音频文件来获取所提供录音的转录或翻译。这种方法使用 Whisper 模型,可以区分专有名词、品牌和俚语,从而提供正确的转录和翻译。例如,将 "微型机器 "作为一个品牌来谈论,与将 "微型机器 "作为一个普通名词翻译成西班牙语是不一样的。下面的例子是对 80 年代一个著名广告插播的转录: 因此,指示 Whisper 为我们转录音频的结果如下: { "text": "This is the Micromachine Man presenting the most midget miniature motorcade of micromachines. Each one has dramatic details, terrific trim, precision paint jobs, plus incredible micromachine pocket playsets. There's a police station, fire station, restaurant, service station, and more. Perfect pocket portables to take anyplace. And there are many miniature playsets to play with and each one comes with its own special edition micromachine vehicle and fun fantastic features that miraculously move. Raise the boat lift at the airport, marina, man the gun turret at the army base, clean your car at the car wash, raise the toll bridge. And these playsets fit together to form a micromachine world. Micromachine pocket playsets, so tremendously tiny, so perfectly precise, so dazzlingly detailed, you'll want to pocket them all. Micromachines and micromachine pocket playsets sold separately from Galoob. The smaller they are, the better they are." } 多么神奇! 你觉得呢? 之所以能取得上述成果,是因为 Whisper 模型接受了训练。我们可以从 OpenAI 页面提供的下图中看到一些相关信息。 更多信息可以访问 https://openai.com/research/whisper 请记住,告知程序文件名至关重要,因为服务需要知道它正在处理的文件类型(如 WAV、MP3、OGG 等)。由于我们在调用中只包含 Base64 内容,因此还必须指明文件扩展名,以便用随机文本和建议的扩展名创建文件名。例如,St.OpenAi.Msg.Audio.AudioRequest 消息的 "类型 "属性可显示音频的种类: MP3、OGG、WAV、FLAC 等。 Endpoint: https://api.openai.com/v1/audio/transcriptions 通过这种方法,您可以将音频内容转录为有声语言。 输入参数如下: file: 必要 在这里,您可以指定要转录的音频文件(而不是文件名)。它支持以下格式: FLAC、MP3、MP4、MPEG、MPGA、M4A、OGG、WAV 或 WEBM model: 必要 用于转录的模型。目前只有 "whisper-1 "可用 language: 可选. 默认是音频语言. 如果指定的话,根据 ISO-639-1,可以提高准确率和延时. prompt: 可选. 这是一段可选的文字,用于引导模型的风格或延续上一段音频。此处的信息必须与音频语言一致。. response_format. 可选,默认为 “json”. 在这一部分中,您要明确转录输出的格式。请使用以下选项之一: "json"、"text"、"verbose_json"。 temperature: 可选,默认为 0. 采样温度应介于 0 和 1 之间。 0.8 等较高值会使输出更加随机,而 0.2 等较低值则会使输出更加集中和确定。如果设置为 0,模型将使用对数似然自动提高温度,直到达到特定阈值。 本方法的文档请参考 https://platform.openai.com/docs/api-reference/audio/createTranscription<. Endpoint: https://api.openai.com/v1/audio/translations 此方法可将音频内容翻译成英语。输入参数如下: file: 必要 它是您要翻译的音频文件(而不是文件名)。它支持以下格式: FLAC、MP3、MP4、MPEG、MPGA、M4A、OGG、WAV 或 WEBM model: 必要. 如上. prompt: 可选l. 这是一段可选的文字,用于引导模型的风格或延续上一段音频。此处的信息必须使用英语。 response_format. 可选. 默认是 “json”. 在这里,您可以用以下选项之一决定转录输出的格式: "json"、"text"、"verbose_json"。 temperature: 可选. 默认为 0. 如上 更多文档请查阅 https://platform.openai.com/docs/api-reference/audio/createTranscription. 下一步? 由于 OpenAi 在不断发展,下一次迭代将是将文本转换为音频的方法,以及其他一些新功能。如果您喜欢这篇文章,请记得点个 "赞"。
文章
jieliang liu · 一月 27, 2021

在Caché中使用正则表达式

1.关于本文 就像Caché模式匹配一样,正则表达式也可以在Caché中用来识别文本数据中的模式--只是表达能力更强。本文简要介绍了正则表达式,以及在Caché中如何使用它。这里提供的信息基于各种来源,最值得拜读的是Jeffrey Friedl的《掌握正则表达式》一书,当然还有Caché在线文档。本文无意讨论正则表达式的所有可能性和细节。如果你想了解更多,请参考第5章中列出的信息来源。 使用模式进行文本处理有时会变得很复杂。在处理正则表达式时,我们通常有几种实体:我们正在搜索模式的文本、模式本身(正则表达式)和匹配(文本中与模式匹配的部分)。为了便于区分这些实体,本文档中使用了以下约定。 文本样本以单色字体单独列出,不加引号。 This is a "text string" in which we want to find "something". 除非不明确,否则正文中的正则表达式会以灰色背景显示,如本例。\".*?\". 需要时用不同的颜色突出显示匹配。 这是一个"text string",我们要在其中找到"something"。 代码样本会显示在如下的文本框里: set t=" This is a ""text string"" in which we want to find ""something " set r="\"".*?\""" w $locate(t,r,,tMatch) 2.一些历史(和一些小事)。 在20世纪40年代初,神经生理学家开发了人类神经系统的模型。几年后,一位数学家用一种代数来描述这些模型,他称之为"正则集"。这种代数的符号被命名为"正则表达式"。 1965年,正则表达式第一次在计算机的范畴内被提及。随着qed,一个作为UNIX操作系统一部分的编辑器,正则表达式开始传播。该编辑器后来的版本提供了一个命令序列g/正则表达式/p(全局、正则表达式、打印),在所有文本行中搜索匹配的正则表达式并输出结果。这个命令序列最终成为独立的UNIX命令行程序"grep"。 今天,许多编程语言都存在正则表达式(RegEx)的各种实现(见3.3节)。 3.Regex 101 就像Caché模式匹配一样,正则表达式也可以用来识别文本数据中的模式--只是表达能力更强。下面的章节概述了正则表达式的组成部分,它们的评估和一些可用的引擎,然后在第4章中详细介绍如何使用。 3.1.正则表达式的组成部分 3.1.1.Regex元字符 以下字符在正则表达式中具有特殊意义。 . ( ) [ ] \ ^ $ | 如果你需要将它们作为字面数使用,你需要使用反斜杠来转义。你也可以使用 \Q <literal sequence> \E显式指定字面序列。 3.1.2.文字 普通文本和转义字符被视为字面,例如:。 abc abc \f 换页 \n 换行 \r 回车 \v 标签 \0+三位数(如:0101) 八进制数Caché (ICU)中使用的regex引擎支持八进制数,最高可达\0377(十进制系统为255)。当你从另一个引擎迁移正则表达式时,请确保你了解它如何处理八进制数。 \x+两位数(如:x41) 十六进制数Caché库确实提供了更多处理十六进制数的选项,请参考文档(链接可以在5.8节找到)。 3.1.3.锚 使用锚点,你可以匹配文本/字符串中的位置,例如:。 \A 字符串的开始 \Z 字符串的末端 ^ 文本或行的开始 $ 文末或行末 \b 字词边界 \B 不字界 < 词的开头 #> 词尾 和一些RegEx引擎的行为有所不同,例如,对构成单词的确切定义以及哪些字符被视为单词定界符。 3.1.4.量词 使用正则表达式量词,你可以指定前面的元素可能出现的频率来进行匹配。 {x}正好出现x次 {x,y}最小x,最大y的出现次数。 * 0或更多;相当于{0,}。 +1或更多;相当于{1,}。 ? 0或1 量词很"贪婪",它们会尽可能多地抓取字符。假设我们有下面的文本字符串,想找到带引号的文本。 This is "a text" with "four quotes". 由于选择器的贪婪性质,正则表达式"/".*/"会找到太多的文本。 This is "a text" with "four quotes". 在这个例子中,正则表达式.*试图捕捉尽可能多的位于一对引号之间的字符。然而,由于点选择器 ( .) 也匹配引号,我们没有得到我们想要的结果。 通过一些regex引擎(包括Caché使用的引擎),你可以通过添加一个问号来控制量化符的贪婪程度。因此,正则表达式"\".*?\"现在可以匹配引号中的两部分文本--这正是我们要找的。 This is "a text" with "four quotes". 3.1.5.字符类(范围) 方括号用于指定字符的范围或字符集,例如 [a-zA-Z0-9] 或 [abcd] - 在 regex 中,这被称为字符类。一个范围可以匹配单个字符,所以范围定义中的字符顺序无关紧要--[dbac]返回的匹配结果与[abcd]相同。 要排除一个字符范围,只需在字符范围定义前面加上^(在方括号内!)。[^abc] 匹配除了a, b或c以外的任何字符. 一些regex引擎确实提供了预先定义的字符类(POSIX),例如。 [:alnum:] [a-zA-z0-9] [:alpha:] [a-zA-Z] [:blank:] [\t] … 3.1.6.Groups (组) 正则表达式的部分内容可以使用对括号进行分组。这对于将量化符应用于一组选择符,以及从同一regex内(反向引用)和从调用正则表达式的Caché对象脚本代码(捕获缓冲区)中引用分组都很有用。组可以被嵌套。 下面的regex匹配由一个三位数组成的字符串,后面是一个破折号,然后是3对大写字母和一个数字,后面是一个破折号,然后是与第一部分相同的三位数。 ([0-9]{3})-([A-Z][[0-9]){3}-\1 这个例子展示了如何使用反向引用(见下文)不仅匹配结构,而且匹配内容:反向引用(紫色)告诉引擎在结尾处寻找与开头处相同的三位数数字(黄色)。它还演示了如何将量词应用于更复杂的结构(绿色)。 上面的regex将匹配以下字符串。 123-D1E2F3-123 在这些上面是不匹配的。 123-D1E2F3-456(最后三位数与前三位数不同) 123-1DE2F3-123(中间部分不包含三个字母/数字对) 123-D1E2-123(中间部分只包含两个字母/数字对) 组也会填充所谓的捕获缓冲区(见4.5.1节)。这是一个非常强大的功能,它允许同时匹配和提取信息。 3.1.7. Alternations(交替) 使用管道字符来指定alternations,例如skyfall|done。这允许匹配更复杂的表达式,如3.1.5节中描述的字符类。 3.1.8.回溯引用 后面的引用允许您引用以前定义的组(括号中的选择器)。下面的例子显示了一个正则表达式,它匹配三个必须相等的连续字符。 ([a-zA-Z])/1/1 后面的引用由\x指定,而x代表第x个括号中的表达式。 3.1.9.优先规则 []在()之前 +和? 在序列前:ab等于a(b*),而不是(ab)*。 序列在alternation前:ab|c等于(ab)|c,而不是a(b|c) 3.2.一些理论 正则表达式的评估通常采用以下两种方法之一来实现(这里描述是简化的,请参考第5章中提到的文献进行深入讨论)。 文本驱动(DFA - Deterministic Finite Automaton) 引擎逐字逐句地检查输入文本,并尝试匹配它目前所拥有的内容。当它真正到达输入文本的结尾时,它宣布成功。 Regex-driven (NFA - Non-deterministic Finite Automaton) 引擎会逐一检查正则表达式,并尝试将其应用到文本中。当它真正到达(并匹配)最后一个标记时,它宣布成功。 方法1是确定性的,执行时间只取决于输入文本的长度。正则表达式中选择符的顺序不影响执行时间。 方法2是非决定性的,引擎会遍历正则表达式中选择符的所有组合,直到找到匹配或遇到错误。因此,当它没有找到匹配项时,这种方法特别慢(因为它必须遍历所有可能的组合)。选择符的顺序确实对执行时间有影响。但是,这种方法允许回溯和捕获缓冲区。 3.3.Regex引擎 目前有很多不同的regex引擎,有些是编程语言或操作系统的内置部分,有些是几乎可以在任何地方使用的库。以下是一些regex引擎,按评估方法分组。 DFA: grep, awk, lex. NFA:Perl、Tcl、Python、Emacs、sed、vi、ICU。 下表是各种编程语言和库中可用的regex功能的比较。 详情请点击这里:https://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines 4.RegEx和Caché InterSystems Caché使用ICU库来处理正则表达式,Caché在线文档描述了它的许多功能。请参考ICU库的在线文档以了解完整的细节(包括诸如回溯引用等)--ICU的链接可以在5.8节中找到。以下章节旨在为您快速介绍如何使用它。 4.4.$match()和$locate() 在Caché ObjectScript (COS)中,两个函数$match()和$locate()提供了对ICU库提供的大部分regex功能的直接访问。$match(String, Regex) 在输入的字符串中搜索指定的Regex模式。当它找到一个匹配的模式时,它返回1,否则它返回0。 例如: w $match("baaacd",".*(a)\1/1.*") 返回1。 w $match("baacd",".*(a)\1/1.*") 返回0。 $locate(String,Regex,Start,End,Value) 就像$match()一样,以指定的regex模式搜索输入字符串。然而,$locate()给你更多的控制权,它返回更多的信息。在Start中,你可以告诉$locate应该在哪个位置开始搜索输入字符串中的模式。当$locate()找到一个匹配时,它会返回匹配的第一个字符的位置,并将End设置为匹配后的下一个字符位置。匹配的内容会在Value中返回。 如果$locate()没有找到匹配的对象,它将返回0,并且不触及End和Value(如果指定)的内容。End和Value是以引用的形式传递的,所以如果你重复使用它(例如在循环中)要小心。 例如: w $locate("abcdexyz",".d.",1,e,x) 返回3,e设为6,x设为"cde" $locate()执行模式匹配,并且可以同时返回第一个匹配的内容,如果需要提取所有匹配的内容,可以在循环中反复调用$locate(),也可以使用%Regex.Matcher提供的方法。 如果需要提取所有匹配的内容,你可以在一个循环中重复调用$locate(),或者你可以使用%Regex.Matcher提供的方法(见下一节)。 4.5.%Regex.Matcher. %Regex.Matcher提供了ICU库的regex功能,就像$match()和$locate()一样。然而,%Regex.Matcher还提供了一些高级功能,使更复杂的任务变得非常容易使用。下面的章节将重新审视捕获缓冲区,看看用正则表达式替换字符串的可能性以及控制运行时行为的方法。 4.5.1.捕获缓冲区(Buffers) 正如我们在关于组、回溯引用和$locate()的章节中已经看到的,正则表达式允许你同时搜索文本中的模式并返回匹配的内容。它的工作原理是将您想要提取的模式的部分放在一对括号中(组)。匹配成功后,捕获缓冲区包含所有匹配的组的内容。请注意,这与$locate()通过其值参数提供的内容略有不同:$locate()返回整个匹配本身的内容,而捕获缓冲区则让您访问匹配的部分内容(组)。 要使用它,你需要创建一个 %Regex.Matcher 类的对象,并将正则表达式和输入字符串传递给它。然后你可以调用%Regex.Matcher提供的方法来执行实际工作。 例1(简单组): set m=##class(%Regex.Matcher).%New("(a|b).*(de)", "abcdeabcde") w m.Locate()返回1 w m.Group(1) 返回 a w m.Group(2) 返回 de 例2(嵌套组和回溯引用)。 set m=##class(%Regex.Matcher).%New("((a|b).*?(de))(\1)", "abcdeabcde") w m.Match()返回1 w m.GroupCount返回4 w m.Group(1) 返回 abcde。 w m.Group(2) 返回 a w m.Group(3) 返回 de w m.Group(4) 返回 abcde。 (注意嵌套组的顺序--因为开头的括号标志着一个组的开始,所以内部组的索引号比外部组的索引号高) 如前所述,捕获缓冲区是一个非常强大的功能,因为它们允许您同时匹配模式和提取匹配的内容。如果没有正则表达式,您必须在第一步中找到匹配的内容(例如使用模式匹配操作符),并在第二步中根据一些标准提取匹配的内容(或部分内容)。 如果您需要对模式中的部分进行分组(例如对该部分应用量化符),但又不想用匹配部分的内容来填充捕获缓冲区,您可以通过在组前加上问号和冒号的方式将组定义为"非捕获"或"害羞",如下面的例子3。 例3。 set m=##class(%Regex.Matcher).%New("((a|b).*?(?:de))(\1)","abcdeabcde") w m.Match()返回1 w m.Group(1) 返回 abcde。 w m.Group(2) 返回 a w m.Group(3) 返回 abcde。 w m.Group(4) 返回 <REGULAR EXPRESSION>zGroupGet+3^%Regex.Matcher.1。 4.5.2.替换 %Regex.Matcher也提供了立即替换匹配内容的方法。ReplaceAll()和ReplaceFirst()。 set m=##class(%Regex.Matcher).%New(".c.","abcdeabcde") w m.ReplaceAll("xxxx") 返回 axxxxeaxxxxe。 w m.ReplaceFirst("xxxx") 返回 axxxxeabcde。 你也可以在替换字符串中引用组。如果我们在上一个例子的模式中添加一个组,我们可以通过在替换字符串中包含$1来引用它的内容。 set m=##class(%Regex.Matcher).%New(".(c).","abcdeabcde").","abcdeabcde") w m.ReplaceFirst("xx$1xx") 返回 axxcxxeabcde。 使用$0在替换字符串中包含匹配的全部内容。 w m.ReplaceFirst("xx$0xx") 返回 axxbcdxxeabcde。 4.5.3.操作限制(OperationLimit) 在3.2节中,我们了解了评估正则表达式的两种方法(DFA和NFA)。Caché中使用的正则表达式引擎是一个非确定性的有限自动机(NFA)。因此,对给定输入字符串评估各种正则表达式的持续时间可能会有所不同。[1] 您可以使用%Regex.Matcher对象的OperationLimit属性来限制执行单元的数量(所谓的簇)。执行一个簇的确切持续时间取决于你的环境。通常情况下,一个簇的执行持续时间是非常少的几毫秒。默认情况下,OperationLimit被设置为0(无限制)。 4.6.真实世界的例子:从Perl到Caché的迁移。 本节介绍了从Perl迁移到Caché的过程中与正则表达式有关的部分。Perl 脚本实际上由几十个或多或少复杂的正则表达式组成,这些正则表达式被用来匹配和提取内容。 如果Caché中没有regex功能,迁移项目就会变成一项重大工作。然而,Caché中的regex功能是可用的,Perl脚本中的正则表达式几乎可以在Caché中使用,而不需要任何改变。 下面是Perl脚本的一部分。 将正则表达式从 Perl 移到 Caché 的唯一改动是 /i 修饰符(使 regex 不区分大小写)--这必须从 regex 的结尾移到开头。 在Perl中,捕获缓冲区的内容被复制到特殊的变量中(在上面的Perl代码中是$1和$2)。在Perl项目中,几乎所有的正则表达式都使用了这种机制。为了类似于这种机制,我们在Caché对象脚本中写了一个简单的包装方法。它使用 %Regex.Matcher 对文本字符串评估正则表达式,并将捕获缓冲区的内容以列表的形式返回($lb())。 由此产生的Caché对象脚本代码如下。 如果...RegexMatch( tVCSFullName。 "(?i)[\\\/]([^\\^\/]+)[\\\/]ProjectDB[\\\/](.+)[\\\/]archives[\\\/]", .tCaptureBufferList) { set tDomainPrefix=$zcvt($lg(tCaptureBufferList,1), "U") set tDomain=$zcvt($lg(tCaptureBufferList,2), "U") } … Classmethod RegexMatch(pString as %String, pRegex as %String, Output pCaptureBuffer="") { #Dim tRetVal as %Boolean=0。 set m=##class(%Regex.Matcher).%New(pRegex,pString) set m.Locate() { set tRetVal=1 for i=1:1:m.GroupCount { set pCaptureBuffer=pCaptureBuffer_$lb(m.Group(i)) } } quit tRetVal } 5.参考资料 5.7.一般资料 概括信息和教程。 http://www.regular-expressions.info/engine.html 教程和实例。 http://www.sitepoint.com/demystifying-regex-with-practical-examples/ 几种regex引擎的比较。 https://en.wikipedia.org/wiki/Comparison_of_regular_expression_engines 常用表达式cheat sheet。 https://www.cheatography.com/davechild/cheat-sheets/regular-expressions/pdf/ 书。 Jeffrey E. F. Friedl:"掌握正则表达式"(见http://regex.info/book.html) 5.8.Caché在线文件 关于Caché中正则表达式的用法概述。 http://docs.intersystems.com/latest/csp/docbook/ DocBook.UI.Page.cls?KEY=GCOS_regexp。 $match()的文档。 http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_fmatch $locate()的文档。 http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_flocate %Regex.Matcher的类引用。 http://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?APP=1&LIBRARY=%25SYS&CLASSNAME=%25Regex.Matcher 5.9.ICU 如上所述,InterSystems Caché使用ICU引擎。全面的文档可在网上查阅。 http://userguide.icu-project.org/strings/regexp http://userguide.icu-project.org/strings/regexp#TOC-Regular-Expression-Metacharacters http://userguide.icu-project.org/strings/regexp#TOC-Regular-Expression-Operators http://userguide.icu-project.org/strings/regexp#TOC-Replacement-Text http://userguide.icu-project.org/strings/regexp#TOC-Flag-Options 5.10.工具 有许多工具支持开发人员创建正则表达式--其中一些是免费的,另一些则有商业许可。我个人的选择是RegexBuddy(http://www.regexbuddy.com/)--它提供了一套全面的交互式和可视化功能,可以创建和测试不同风味的正则表达式。 thanks for sharing!
文章
jieliang liu · 一月 7, 2021

精华文章---在 Windows 主机上运行的 Hyper-V Ubuntu 虚拟机中配置 Docker 使用环境

这次我想谈一谈不专门针对 InterSystems IRIS 的东西,不过如果你想使用 Docker,并且你工作环境是安装了 Windows 10 专业版或企业版的 PC 或笔记本电脑,那么我认为这个很重要。 你可能知道,容器技术基本上来自于 Linux 世界,如今在 Linux 主机上发挥出最大潜能。 那些平常使用 Windows 的人会看到,Microsoft 和 Docker 在过去的几年做出了重要的努力,让我们可以在 Windows 系统上以非常简单的方式运行基于 Linux 映像的容器... 但是生产系统不支持这种方式,这是个大问题,如果我们要将持久性数据保留在主机系统中的容器之外,这样做非常不可靠... 这主要是由于 Windows 和 Linux 文件系统之间的巨大差异导致的。 最终,_Docker for Windows 自身使用了一个小型 linux 虚拟机 (_MobiLinux_) 来运行容器... 此操作对于 Windows 用户是透明的,而且效果完美,只要你不需要你的数据库比容器存活的时间更长... 好了,我们进入正题,很多时候为了避免出现问题和简化操作,我们需要一个完整的 Linux 系统,而且如果我们的服务器基于 Windows,那么唯一的方法就是通过虚拟机来实现。 至少在 Windows 中的 WSL2 发布之前是这样,但发布后就是另一回事了,不过它要变得足够强大稳定肯定还需要一些时间。 在本文中,我将一步一步告诉你如何在 Windows 服务器中的 Ubuntu 系统上安装一个能使用 Docker 容器进行工作的环境。 我们开始吧... 1. 启用 Hyper-V 如果尚未启用,则转到添加 `Windows 功能`并启用 Hyper-V。 你将需要重启(图片上的文本是西班牙语,但这就是我当前的区域设置。 如果你不懂堂吉诃德的语言,我希望加上说明能帮助你“解密”😉) ![](/sites/default/files/inline/images/images/image(424).png)   2. 在 Hyper-V 上创建一个 Ubuntu 虚拟机 我认为创建虚拟机 (VM) 没有更简单的方法了。 只需打开 `Hyper-V 管理器`的窗口,然后转到选项快速创建...(屏幕的右上角),使用已经提供的任一 Ubuntu 版本来创建你的虚拟机(你可以下载任何其他 Linux 的 iso 文件,创建不同发行版的虚拟机)。 在我的示例中,我选择了最新的 Ubuntu 版本:19.10。 不过,你在这里看到的一切内容也都适用于 18.04。 在 15 或 20 分钟内,具体取决于你下载映像花费的时间,新的虚拟机就创建完毕并准备就绪。 重要: 保持选项默认交换机 不变。这将保证你可以从主机和虚拟机访问互联网。 ![](/sites/default/files/inline/images/images/vm_ubuntu_network_start_defaultswitch_eth0.jpg) 3. 创建本地子网 使用虚拟机经常遇到的问题之一与网络配置有关... 有时有效,有时无效,或者连接 Wi-Fi 时有效,但连接网线就无效,或者是相反情况;或者如果我在 Windows 主机中建立一个 VPN,那么在虚拟机中就无法访问互联网,或者是虚拟机 (Linux) 和主机 (Windows) 之间的通信中断... 总之,非常让人抓狂! 这使得我在使用笔记本电脑进行开发、小型快速演示或展示时无法信任我的环境,而在这些场景下访问互联网很可能不如确保在主机与虚拟机之间进行可靠通信来得重要。 在 Windows 主机和虚拟机之间共享一个临时本地子网,可以解决这个问题。 要让它们互相通信,使用该子网就可以了。 你只需要为主机和虚拟机分配特定 IP 即可。 通过以下步骤可以很容易实现。 只需转到虚拟交换机管理器...,你可以在 `Hyper-V 管理器`中找到: ![](/sites/default/files/inline/images/images/image(425).png) 然后,转到选项新建虚拟交换机(之后就像虚拟机的新网卡一样): ![](/sites/default/files/inline/images/images/image(461).png) 确保将其定义为_内部网络_,选择我们想要的名称,其他选项保持默认 ![](/sites/default/files/inline/images/images/image(427).png) 现在,如果转到 _`Windows 控制面板 --> 网络和共享中心`_,我们会看到那里已经有了我们刚才创建的交换机: ![](/sites/default/files/inline/images/images/image(429).png)   4. 配置主机和虚拟机共享的本地子网 此时,你可以完成新的本地网络的配置。 为此,将光标放在连接 _Mi Nuevo Conmutador LOCAL_ 上,单击并转到属性,再转到 IPv4 协议,以便分配一个固定 IP 地址: ![](/sites/default/files/inline/images/images/image(449).png)   重要:在此处分配的 IP 将是主机 (Windows) 在该本地子网中的 IP。   5. 将新的本地网络链接并配置到虚拟机 现在回到 `Hyper-V 管理器`。 如果虚拟机正在运行,将其停止。 停止后,转到其配置并添加新的内部虚拟交换机: ![](/sites/default/files/inline/images/images/image(431).png) _(注意:在图片上可以看到另一个交换机 Hyper-V Conmutador INTERNO。 它用于我的另一个子网。 此配置中不需要它)_ 单击“添加”后,你只需选择先前创建的交换机: ![](/sites/default/files/inline/images/images/image(432).png) 好了,完成此操作后,依次单击“应用”、“接受”... 一切就绪!你只需启动并再次登录虚拟机即可完成内部连接的配置。 为此,在虚拟机启动后,单击网络图标(右上角),你将看到两个网络:_eth0_ 和 _eth1_。 _eth1_ 目前显示为断开连接: ![](/sites/default/files/inline/images/images/image(450).png) 进入以太网 (eht1) 的配置,并为此本地子网分配一个固定 IP,例如:_155.100.101.1_,子网掩码:_255.255.255.0_ ![](/sites/default/files/inline/images/images/image(452).png) 这样就完成了。 你的虚拟机标识为 IP 155.100.101.1,与主机共享同一子网。 7. 允许从虚拟机访问 Windows 10 你可能会发现 Windows 10 默认不允许其他服务器连接,对于 Windows 系统来说,你刚刚创建的虚拟机正是一个可能存在危险的外部服务器。因此,必须在防火墙中添加规则,才能从这些虚拟机连接到主机。 如何操作? 非常简单,只需在 `Windows 控制面板`中查找 `Windows Defender 防火墙`,转到高级配置,然后创建一条新的*入站规则*: ![](/sites/default/files/inline/images/images/image(451).png) 你可以设置一个端口或者一个或多个端口范围...(也可以设置针对所有端口的规则)... ![](/sites/default/files/inline/images/images/image(453).png) 我们需要的操作是_允许连接_... ![](/sites/default/files/inline/images/images/image(454).png) 用于_所有网络类型_... ![](/sites/default/files/inline/images/images/image(455).png) 为规则指定名称... ![](/sites/default/files/inline/images/images/image(456).png) **这里很重要**,指定名称后要立即再次打开新创建的规则的属性并*限制应用程序范围*,以便只应用于本地子网内的连接... ![](/sites/default/files/inline/images/images/image(457).png) 8. 就绪。 在新的 Ubuntu 虚拟机中安装 Docker 和任何其他应用程序 完成整个安装过程后,新虚拟机即就绪且为最新,并可以访问互联网等等。 你可以安装所需的应用程序... 至少要安装 Docker,这是一开始就有的想法,如果你需要连接公司网络,还可以安装 VPN 客户端,还有 VS Code、Eclipse+Atelier 等等。 具体来说,要在虚拟机中安装 Docker,可以按照以下说明进行操作: 确保 Docker 运行时正在工作,下载一些测试映像等等... 仅此而已。 这样... _**你已完成所有工作!**_,现在你将能够在 Ubuntu 虚拟机中无限制(除了硬件能力限制)运行容器,你可以从 Windows 10 主机、浏览器或应用程序连接到虚拟机,以及反过来从 Ubuntu 虚拟机连接到 Windows 10 主机。 所有使用你在共享本地子网中设置的 IP 地址的操作都将有效,无论是否建立 VPN,是通过 Wi-fi 适配器还是通过以太网电缆接入互联网。 啊... 最后一个建议。 如果要在 Windows 10 和虚拟机之间交换文件,一个非常有用且简单的选项是使用 [WinSCP](https://winscp.net/eng/download.php)。 它是免费的,而且非常好用。 当然,还有其他配置,但这是我使用的配置,已经证明是比较可靠的。 希望你也觉得它有用。 如果我帮助你避免了令人头疼的问题,这篇文章就值了。 编码愉快!     
文章
姚 鑫 · 四月 27, 2021

第七章 解释SQL查询计划

# 第七章 解释SQL查询计划 本章介绍由`ShowPlan`生成的InterSystems SQL查询访问计划中使用的语言和术语。 # 存储在映射中的表 SQL表存储为一组映射。 每个表都有一个包含表中所有数据的主映射; 表还可以有其他的映射,如索引映射和位图。 每个映射可以被描绘成一个多维全局,其中一些字段的数据在一个或多个下标中,其余字段存储在节点值中。 下标控制要访问的数据。 - 对于主映射,`RowID`或`IDKEY`字段通常用作映射下标。 - 对于索引映射,通常将其他字段用作前导下标,将`RowID/IDKEY`字段用作附加的较低级别的下标。 - 对于位图,可以将位图层视为附加的RowID下标级别。但是,位图只能用于为正整数的`RowID`。 # 发展计划 编译SQL查询会生成一组指令来访问和返回查询指定的数据。 这些指令表示为`. int`例程中的ObjectScript代码。 指令及其执行顺序受到SQL编译器中有关查询中涉及的表的结构和内容的数据的影响。 编译器尝试使用表大小和可用索引等信息,以使指令集尽可能高效。 查询访问计划(`ShowPlan`)是对结果指令集的可读翻译。 查询的作者可以使用这个查询访问计划来查看将如何访问数据。 虽然SQL编译器试图最有效地利用查询指定的数据,但有时查询的作者对存储的数据的某些方面的了解要比编译器清楚得多。 在这种情况下,作者可以利用查询计划修改原始查询,为查询编译器提供更多的信息或更多的指导。 # 阅读计划 `“ShowPlan”`的结果是一系列关于访问和显示查询中指定的数据的处理的语句。 下面提供了关于如何解释`ShowPlan`语句的信息。 ## 访问映射 一个查询计划可以访问多个表。 当访问一个表时,计划可以访问单个映射(索引或主映射)、两个映射(索引映射后面跟着主映射),或者,对于多索引计划,可以访问多个映射。 在通过映射访问数据时,计划指示使用的下标。 它还指示实际的下标值是什么:一个给定值、一组给定值、一个值范围,或该下标在表中显示的所有值。 选择哪一个取决于查询中指定的条件。 显然,访问单个或几个下标值要比访问该下标级别上的所有值快得多。 ## 条件和表达式 当查询运行时,将测试查询指定的各种条件。 除了前面提到的某些限制下标的条件外,`ShowPlan`输出没有显式地指示条件的测试。 尽早测试条件总是最好的。 测试各种条件的最佳地点可以从计划细节中推断出来。 类似地,`ShowPlan`不详细描述表达式和子表达式的计算。 除了简单之外,主要原因是在大多数数据库环境中,表和索引访问构成了处理的更重要方面; 检索表数据的成本占总体查询成本的主要地位,因为磁盘访问速度仍然比CPU处理慢几个数量级。 ## 循环 当访问一个表中的数据时,经常需要迭代地检查多个行。 这样的访问是通过一个循环来指示的。 每一次传递要执行的指令称为循环体。 它们可以通过缩进直观地显示出来。 涉及多个表的数据库访问通常需要循环中的循环。 在这种情况下,每个循环级别都通过与前一个级别相比的进一步缩进表示。 ## 临时文件 ### 定义 查询计划还可能指示需要构建和使用中间临时文件(`TEMP-FILE`)。这是本地数组中的“临时”区域。它用于保存临时结果以用于各种目的,如排序。就像映射一样,临时文件有一个或多个下标,可能还有节点数据。 ### 使用 一些临时文件包含处理单个表的数据。在这种情况下,可以将构建临时文件视为对该表中的数据进行预处理。在读取这样的临时文件之后,可以访问源表的主映射,也可以不访问源表的主映射。在其他情况下,临时文件可能包含处理多个表的结果。在其他情况下,临时文件用于存储分组的聚合值、检查DISTINCT等。 ## 模块 临时文件的构建,以及其他处理,可以委托给一个称为模块的独立工作单元。 每个模块都被命名。 当列出单独的模块时,该计划将指明调用每个模块的位置。 当模块执行结束时,处理将在模块调用之后的下一条语句中继续进行。 ## 发送给处理的查询 对于通过ODBC或JDBC网关连接链接的外部表,该计划显示发送到远程SQL gateway connection的查询文本,以从远程表检索所请求的数据。 对于并行查询处理和分片,该计划显示发送到并行处理或在分片上处理的各种查询。 还将显示用于每个查询的计划。 ## 子查询、连接和联合 给定查询中的一些子查询(和视图)也可以单独处理。 它们的计划在单独的子查询部分中指定。 在计划中没有指明子查询部分被调用的精确位置。 这是因为它们经常作为条件或表达式处理的一部分被调用。 对于指定`OUTER JOIN`的查询,如果没有找到匹配的行,该计划可能指示可能生成的`null`行,以满足外部连接语义的要求。 对于`UNION`,该计划可能指示将来自不同`UNION`子查询的结果行组合到一个单独的模块中,在该模块中可以对这些结果行进行进一步处理。 # 计划分析 在分析给定查询的计划时,应用程序开发人员有时可能会觉得不同的计划会更有效率。 应用程序开发人员有多种方法来影响计划。 首先,计划将受到在包含实际应用程序数据的环境中正确运行调优表的影响。 在类源定义中手动定义一些`Tune Table`通常计算的值——例如表`EXTENTSIZE`、字段`SELECTIVITY`和映射`BlockCount`——也可以用于实现所需的计划。 此外,分析计划可能表明对类定义的某些更改可能导致更有效的计划,例如: ## 添加一个索引 在某些情况下(尽管不总是),使用一个临时文件进行预处理可能意味着向原始表添加一个与临时文件具有相同或类似结构的索引将消除构建临时文件的需要。 从查询计划中删除这个处理步骤显然可以使查询运行得更快,但这必须与更新表时维护索引所需的工作量进行平衡。 ## 添加字段到索引数据 当计划显示正在使用的索引,然后是对主映射的访问时,这意味着将查询中使用的主映射字段添加到索引节点数据可能会为该查询生成更快的计划。 同样,这必须与额外的更新时间以及添加到处理使用该索引的其他查询的额外时间进行平衡,因为索引会更大,因此需要更多的读取时间。 ## 添加连接索引 当计划显示以特定顺序连接两个表时(例如,首先检索`t1`,然后使用连接条件`t1.a=t2.b`连接到`t2`),可能相反的表顺序会产生一个更快的计划。例如,如果`t2`有额外的条件,可以显著限制符合条件的行数。 在这种情况下,在`t1`上添加一个t1索引。 a将允许这样一个连接顺序。
文章
Michael Lei · 七月 18, 2022

翻译文章--Angular 14 新特性介绍

Hi 大家好! 我是 Sergei Sarkisian,在InterSystems 做Angular 前端7年。Angular是非常流行的框架,我们的开发人员、客户和合作伙伴经常选择它来开发他们的应用程序。 我会写一系列的文章,涵盖Angular的不同方面:概念、方法、最佳实践、高级主题等等。这个系列的文章将针对那些已经熟悉Angular的人,不会涉及基本概念。由于我正在构建文章的路线图,我想从突出最近的Angular版本中的一些重要功能开始。 ## 严格类型化表单 这可能是近几年来Angular最受欢迎的功能。有了Angular 14,开发者现在可以在Angular Reactive Forms中使用TypeScript的所有严格类型检查功能。 表单控制Formcontrol 类现在是通用的,并接受它所持有的值的类型。 ```typescript /* Before Angular 14 */ const untypedControl = new FormControl(true); untypedControl.setValue(100); // value is set, no errors // Now const strictlyTypedControl = new FormControl(true); strictlyTypedControl.setValue(100); // you will receive the type checking error message here // Also in Angular 14 const strictlyTypedControl = new FormControl(true); strictlyTypedControl.setValue(100); // you will receive the type checking error message here ``` 正如你所见,第一个和最后一个例子几乎是一样的,但有不同的结果。这是因为在Angular 14中,新的FormControl类从开发者提供的初始值中推断出类型。因此,如果提供了`true`的值,Angular就为这个FormControl设置`boolean | null`的类型。`.reset()`方法需要可置空的值,如果没有提供值,就会置空这些值。 一个旧的、没有定义类型的FormControl类被转换为`UntypedFormControl`(对`UntypedFormGroup`、`UntypedFormArray`和`UntypedFormBuilder`来说也是如此),它实际上是`FormControl`的别名。如果你从以前的Angular版本升级,你所有提到的`FormControl`类将被Angular CLI替换为`UntypedFormControl`类。 Untyped* 类通常用以实现特定目标: 1. 保持应用程序的工作方式与从以前的版本过渡之前完全一样(记住,新的FormControl将从初始值推断出类型) 2. 确保所有的`FormControl`的使用都是有意的。所以你需要自己将任何UntypedFormControl改为`FormControl`。 3. 为了给开发者提供更多的灵活性(我们将在下面介绍这个问题) 记住,如果你的初始值是 "null",那么你将需要明确指定FormControl类型。另外,在TypeScript中有一个错误,如果你的初始值是 "false",也需要这样做。 对于表单组,你也可以定义接口,并把这个接口作为表单组的类型传递。在这种情况下,TypeScript将推断出FormGroup中的所有类型。 ```typescript interface LoginForm { email: FormControl; password?: FormControl; } const login = new FormGroup({ email: new FormControl('', {nonNullable: true}), password: new FormControl('', {nonNullable: true}), }); ``` FormBuilder的方法`.group()`现在有了通用属性,可以接受你预定义的接口,就像上面的例子中我们手动创建了FormGroup。 ```typescript interface LoginForm { email: FormControl; password?: FormControl; } const fb = new FormBuilder(); const login = fb.group({ email: '', password: '', }); ``` 由于我们的接口只有原始的nonNullable类型,它可以用新的 "nonNullable "表单生成器属性来简化(它包含 "NonNullable FormBuilder表单生成器 "类实例,也可以直接创建): ```typescript const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); ``` ❗ 请注意,如果你使用nonNullable的FormBuilder或者你在FormControl中设置了nonNullable的选项,那么当你调用`.reset()`方法时,它将使用初始FormControl值作为重置值。 另外,非常重要的一点是,`this.form.value`中的所有属性都将被标记为可选属性。像这样: ```typescript const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); // login.value // { // email?: string; // password?: string; // } ``` 发生这种情况是因为当你禁用表单组FormGroup内的任何表单控件FromControl时,这个表单控件的值将从`form.value`中删除。 ```typescript const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); login.get('email').disable(); console.log(login.value); // { // password: '' // } ``` 要获得整个表单对象,你应该使用`.getRawValue()`方法:: ```typescript const fb = new FormBuilder(); const login = fb.nonNullable.group({ email: '', password: '', }); login.get('email').disable(); console.log(login.getRawValue()); // { // email: '', // password: '' // } ``` 严格类型化表单的优势: 1. 任何返回FormControl / FormGroup值的属性和方法现在都是严格类型的。例如:`value`,`getRawValue()`,`valueChanges`. 2. 任何改变表单控件值的方法现在都是类型安全的:`setValue()`, `patchValue()`, `updateValue()` 3. 表单控件现在是严格类型化的。它也适用于表单组的`.get()`方法。这也将防止你在编译时发生访问不存在的情况. ### 新的 FormRecord 类 新的 "表单组 "类的缺点是它失去了它的动态性质。一旦定义了,你将不能在运行中添加或删除表单控件。 为了解决这个问题,Angular提出了新的类--`FormRecord'。`FormRecord`实际上与`FormGroup`相同,但它是动态的,所有的表单控件都应该有相同的类型。. ```typescript folders: new FormRecord({ home: new FormControl(true, { nonNullable: true }), music: new FormControl(false, { nonNullable: true }) }); // Add new FormContol to the group this.foldersForm.get('folders').addControl('videos', new FormControl(false, { nonNullable: true })); // This will throw compilation error as control has different type this.foldersForm.get('folders').addControl('books', new FormControl('Some string', { nonNullable: true })); ``` 正如你所看到的,这里有另一个限制 - 所有的FormControls必须是相同的类型。如果你真的需要动态和异质的FormGroup,你应该使用`UntypedFormGroup`类来定义你的表单 ## 无模块的 (独立standalone) 组件 这个特性仍然被标记为实验性的,但它是一个有趣的功能。它允许你定义组件、指令和管道,而不把它们包含在任何模块中。 这个概念还没有完全准备好,但我们已经能够在没有ngModules的情况下建立一个应用程序。 要定义一个独立的组件,你需要使用Component组件/Pipe管道/Directive Decorator指令装饰器中新的`standalone'属性: ```typescript @Component({ selector: 'app-table', standalone: true, templateUrl: './table.component.html' }) export class TableComponent { } ``` 在这种情况下,这个组件不能在任何NgModule中声明。但它可以在NgModules和其他独立组件中被导入。 每个独立的组件/管道/指令现在都有机制可以直接在Decorator装饰器中导入它的依赖项: ```typescript @Component({ standalone: true, selector: 'photo-gallery', // an existing module is imported directly into a standalone component // CommonModule imported directly to use standard Angular directives like *ngIf // the standalone component declared above also imported directly imports: [CommonModule, MatButtonModule, TableComponent], template: ` ... Next Page `, }) export class PhotoGalleryComponent { } ``` 正如我上面提到的,你可以在任何现有的ngModule中导入独立的组件。不再需要导入整个共享模块,我们可以只导入我们真正需要的东西。这也是一个开始使用新的独立组件的好策略: ```typescript @NgModule({ declarations: [AppComponent], imports: [BrowserModule, HttpClientModule, TableComponent], // import our standalone TableComponent bootstrap: [AppComponent] }) export class AppModule {} ``` 你可以通过输入Angular CLI创建独立的组件: ```bash ng g component --standalone user ``` ### Bootstrap 无模块的应用 如果你想摆脱你的应用程序中的所有ngModules,你将需要以不同的方式启动你的应用程序。Angular有新的函数,你需要在main.ts文件中调用这个函数: ```typescript bootstrapApplication(AppComponent); ``` 这个函数的第二个参数将允许你定义你在你的应用程序中需要的提供者。由于大多数提供者通常存在于模块中,Angular(目前)需要为它们使用一个新的`importProvidersFrom`提取函数: ```typescript bootstrapApplication(AppComponent, { providers: [importProvidersFrom(HttpClientModule)] }); ``` ### 懒人加载独立组件的路线: Angular有新的懒人-加载路由函数`loadComponent`,它的存在正是为了加载独立的组件: ```typescript { path: 'home', loadComponent: () => import('./home/home.component').then(m => m.HomeComponent) } ``` `loadChildren`现在不仅允许懒人加载ngModule,而且还允许直接从路由文件中加载子路由: ```typescript { path: 'home', loadChildren: () => import('./home/home.routes').then(c => c.HomeRoutes) } ``` ### 关于本文的一些注意事项 - 独立组件的功能仍处于实验阶段。它在未来会变得更好,因为它将移到Vite builder而不是Webpack,更好的工具,更快的构建时间,更强大的应用架构,更容易的测试等等。但现在这些东西都没有了,所以我们没有得到整个包,但至少我们可以开始用新的Angular范式开发我们的应用程序。 - IDE和Angular工具还没有完全准备好静态地分析新的独立实体。因为你需要在每个独立实体中导入所有的依赖关系,万一你漏掉了什么,编译器也会漏掉它,并在运行时让你失败。这一点会随着时间的推移而得到改善,但现在需要开发人员更加关注导入。 - 目前Angular中没有全局导入(例如在Vue中),所以你需要在每个独立实体中完全导入每个依赖。我希望这个问题能在未来的版本中得到解决,因为在我看来,这个功能的主要目标是减少模板,让事情变得更简单。 # 先写到这,谢谢大家!
文章
姚 鑫 · 十一月 11, 2021

第七十三章 SQL命令 SET OPTION

# 第七十三章 SQL命令 SET OPTION 设置执行选项。 # 大纲 ```java SET OPTION option_keyword = value ``` # 描述 `SET OPTION`语句用于设置执行选项,如编译模式、`SQL`配置设置和控制日期、时间和数字约定的区域设置。 每个`set option`语句只能设置一个关键字选项。 `SET OPTION`支持以下选项: - `AUTO_PARALLEL_THRESHOLD - `COMPILEMODE` - `DEFAULT_SCHEMA`` - `EXACT_DISTINCT` - `LOCK_ESCALATION_THRESHOLD` - `LOCK_TIMEOUT` - `PKEY_IS_IDKEY` - `SUPPORT_DELIMITED_IDENTIFIERS` - `Locale Options (date, time, and numeric conventions)` `SET OPTION`可以在动态SQL(包括`SQL Shell`)和嵌入式SQL中使用。 为了`SQL`兼容性,IRIS会解析其他`SET OPTION`参数(这里没有文档),但不执行任何操作。 因为`SET OPTION`的准备和执行速度很快,而且通常只运行一次,所以`IRIS`不会在`ODBC`、`JDBC`或动态SQL中为`SET OPTION`创建缓存查询。 IRIS支持下列选项: ## `AUTO_PARALLEL_THRESHOLD` `AUTO_PARALLEL_THRESHOLD`选项被设置为一个整数`n`,用于确定当启用自动并行处理时是否应该对查询应用并行处理。 由于与并行处理相关的性能成本,因此需要为并行处理的优势确定一个阈值。 `n`越高,SQL查询使用并行处理执行的可能性就越低。 默认为`3200`。 这是一个系统范围的设置。 值n大致对应于所访问的映射中发生并行处理所需的最小元组数量。 当自动并行被禁用时,`AUTO_PARALLEL_THRESHOLD`选项没有作用。 也可以使用`$SYSTEM.SQL.Util.SetOption()`方法`AutoParallelThreshold`选项设置该选项。 ## COMPILEMODE `COMPILEMODE`选项将当前名称空间的编译模式设置为`DEFERRED`、`IMMEDIATE`、`INSTALL`或`NOCHECK`。 默认为`IMMEDIATE`。 从`DEFERRED`编译模式更改为`IMMEDIATE`编译模式会导致`DEFERRED compile Queue`中的任何类立即被编译。 如果所有类编译都成功,IRIS将`SQLCODE`设置为0。 如果有任何错误,`SQLCODE`设置为`-400`。 类编译错误记录在`^mtemp2 ("Deferred Compile Mode","Error")`中。 如果将`SQLCODE`设置为`-400`,则应该查看此全局结构以获得更精确的错误消息。 `INSTALL`编译模式类似于`DEFERRED`编译模式,但它应该只用于表中没有数据的`DDL`安装。 `NOCHECK`编译模式与`IMMEDIATE`编译模式类似,只是在编译时忽略了以下约束:如果一个表被删除, IRIS不检查引用被删除表的其他表中的外键约束。 如果添加了外键约束, IRIS不会检查现有数据以确保它对这个外键有效。 如果添加了`NOT NULL`约束, IRIS不会检查现有数据是否为`NULL`,也不会指定字段的默认值。 如果删除了`UNIQUE`或`Primary Key`约束 IRIS不会检查该表或其他表中的外键是否引用了被删除的键。 也可以使用`$SYSTEM.SQL.Util.SetOption()`方法`CompileMode`选项设置该选项。 ## DEFAULT_SCHEMA `DEFAULT_SCHEMA`选项为所有名称空间设置系统范围的默认模式。 在显式更改之前,此默认值将保持有效。 默认模式名用于为所有未限定的表、视图或存储过程名提供模式名。 可以指定一个文字模式名或指定`_CURRENT_USER`。 如果指定`_CURRENT_USER`作为默认模式名, IRIS会将当前登录进程的用户名作为默认模式名。 ## EXACT_DISTINCT `EXACT_DISTINCT`布尔值选项指定是否在系统范围内使用`DISTINCT`处理`(TRUE)`或`Fast DISTINCT`处理`(FALSE)`。 系统范围的默认值是使用`Fast Distinct`处理。 当`EXACT_DISTINCT=TRUE`时,`GROUP BY`和`DISTINCT`查询生成原始值。 当`EXACT_DISTINCT=FALSE`时,启用快速`Distinct`,通过更好地使用索引(如果有索引),使涉及`Distinct`或`GROUP BY`子句的SQL查询更有效地运行。 但是,这些查询返回的值以与存储在索引中的相同的方式进行排序。 这意味着此类查询的结果可能都是大写的。 这可能对区分大小写的应用程序有影响。 这个选项也可以使用`$SYSTEM.SQL.Util.SetOption()`方法`FastDistinct boolean`选项来设置。 ## `LOCK_ESCALATION_THRESHOLD` `LOCK_ESCALATION_THRESHOLD`选项被设置为一个整数`n`,用于确定何时将行锁定升级为表锁定。 默认值是`1000`。 值`n`是单个事务中单个表的插入、更新或删除次数,当到达时将触发表级锁。 这是针对所有名称空间的系统范围设置。 例如,如果锁阈值为`1000`,并且进程启动一个事务,然后插入`2000`行,那么在插入第`1001`行之后,进程将尝试获取表级锁,而不是继续锁定各个行。 这有助于防止锁表变得太满。 这个选项也可以使用`$SYSTEM.SQL.Util.SetOption()`方法`LockThreshold`选项来设置。 ## LOCK_TIMEOUT `LOCK_TIMEOUT`数值选项允许为当前进程设置默认的锁定超时。 `LOCK_TIMEOUT`值是SQL执行期间试图建立锁时等待的秒数。 当锁定冲突阻止当前进程对`lock`、`INSERT`、`UPDATE`、`DELETE`或`SELECT`操作立即锁定一条记录、表或其他实体时,使用此锁定超时。 `SQL`继续尝试建立锁,直到超时超时,这时将生成`SQLCODE -110`或`-114`错误。 可用的值是正整数和零。 超时设置是每个进程的。 可以使用`$SYSTEM.SQL.Util.GetOption(“ProcessLockTimeout”)`方法确定当前进程的锁定超时设置。 如果没有为当前进程设置锁定超时,则默认为当前系统范围的锁定超时设置。 如果您的`ODBC`连接断开并重新连接,重新连接的进程将使用当前系统范围的锁定超时设置。 系统范围的锁定超时默认为10秒。 ## PKEY_IS_IDKEY `PKEY_IS_IDKEY boolean`选项指定主键是否也是系统范围内的ID键。 取值为`TRUE`、`FALSE`。 如果为`TRUE`,且该字段不包含数据,则将主键创建为`ID`键。 也就是说,表的主键也成为了类定义中的`IDKey`索引。 如果字段不包含数据,则没有定义`IDKey`索引。 如果将主键定义为`IDKey`索引,则数据访问将更加有效,但主键值一旦设置,就永远不能修改。 一旦设置,就不能更改分配给主键的值,也不能将其他键指定为主键。 使用此选项还将更改主键排序规则的默认值; 主键字符串值默认为`EXACT`排序规则。 如果为`FALSE`,则主键和`ID`键被定义为独立的,效率较低。 但是,主键值是可修改的,主键字符串值默认为当前排序规则类型`default`,默认为`SQLUPPER`。 要设置`PKEY_IS_IDKEY`选项,必须具有`%Admin_Manage:USE`权限。 否则,将收到一个`SQLCODE -99`错误(特权违反)。 一旦设置,该选项将在系统范围内对所有进程生效。 该选项的系统范围默认值也可以通过以下方式设置: - `$SYSTEM.SQL.Util.SetOption()`方法配置选项`DDLPKeyNotIDKey`。 要确定当前设置,调用`$SYSTEM.SQL.CurrentSettings()`,它显示通过DDL创建的是主键而不是ID键; 默认值是1。 - 管理门户配置设置。 选择系统管理,配置,SQL和对象设置,SQL。 查看或修改通过DDL创建的表的“将主键定义为ID键”的当前设置。 `PKEY_IS_IDKEY`设置保持有效,直到通过另一个SET OPTION `PKEY_IS_IDKEY`重置或直到 IRIS `Configuration`被重新激活,将该参数重置为IRIS System `Configuration`设置。 ## SUPPORT_DELIMITED_IDENTIFIERS 默认情况下,系统范围内支持分隔标识符。 `SUPPORT_DELIMITED_IDENTIFIERS`布尔选项允许您更改系统范围内对分隔标识符的支持。 取值为`TRUE`、`FALSE`。 如果为`TRUE`,用双引号分隔的字符串被认为是SQL语句中的标识符。 如果为`FALSE`,由双引号分隔的字符串被认为是SQL语句中的字符串字面值。 要设置`SUPPORT_DELIMITED_IDENTIFIERS`选项,必须具有`%Admin_Manage:USE`权限。 否则,将收到一个`SQLCODE -99`错误(特权违反)。 一旦设置,该选项将在系统范围内对所有进程生效。 `SUPPORT_DELIMITED_IDENTIFIERS`设置将保持有效,直到通过另一个设置选项`SUPPORT_DELIMITED_IDENTIFIERS`进行重置,或者直到由`$SYSTEM.SQL.Util.SetOption()方法delimitedifiers`选项在系统范围内进行更改。 ## Locale Options 区域设置选项是关键字选项,用于为当前进程的日期、时间和数字约定设置IRIS区域设置。 可选关键字有`AM、DATE_FORMAT、DATE_MAXIMUM、DATE_MINIMUM、DATE_SEPARATOR、DECIMAL_SEPARATOR、MIDNIGHT、MINUS_SIGN、MONTH_ABBR、MONTH_NAME、NOON、NUMERIC_GROUP_SEPARATOR、NUMERIC_GROUP_SIZE、PM、PLUS_SIGN、TIME_FORMAT、TIME_PRECISION、TIME_SEPARATOR、WEEKDAY_ABBR、WEEKDAY_NAME、YEAR_OPTION`。 所有这些选项都可以设置为文字,并且都采用默认值(美式英语惯例)。 `TIME_PRECISION`选项是可配置的(参见下面)。 如果将这些选项中的任何一个设置为无效值,InterSystems IRIS将发出`SQLCODE -129`错误(`set OPTION`区域设置属性的非法值)。 Date/Time Option Keyword| Description ---|--- `AM` |`String`. 默认 `'AM'` `DATE_FORMAT` |`Integer`. 默认值为`1`。取值范围为`0 ~ 15`。 `DATE_MAXIMUM`| `Integer`. 默认为`2980013(12/31/9999)`。可以设置为更早的日期,但不能设置为更晚的日期。 `DATE_MINIMUM`| `Positive Integer`. 默认为0`(12/31/1840)`。可以设置为较晚的日期,但不能设置为较早的日期。 `DATE_SEPARATOR`| Character. Default is '/' `DECIMAL_SEPARATOR`| Character. Default is '.' `MIDNIGHT`| String. Default is 'MIDNIGHT' `MINUS_SIGN`| Character. Default is '-' `MONTH_ABBR`| String. Default is ' Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec'. (注意,该字符串以空格字符开始,这是默认分隔符.) `MONTH_NAME`| String. Default is ' January February March April May June ... November December'. 注意,该字符串以空格字符开始,这是默认分隔符.) `NOON`| String. Default is 'NOON' `NUMERIC_GROUP_SEPARATOR`| Character. Default is ',' `NUMERIC_GROUP_SIZE` |Integer. Default is 3.PM String. Default is 'PM' `PLUS_SIGN`| Character. Default is '+' `TIME_FORMAT`| Integer. Default is 1. 取值范围为1 ~ 4。 `TIME_PRECISION`| Integer from 0 through 9 (inclusive). Default is 0. 小数秒的位数。 `TIME_SEPARATOR`| Character. Default is ':' `WEEKDAY_ABBR`| String. Default is ' Sun Mon Tue Wed Thu Fri Sat'. (注意,该字符串以空格字符开始,这是默认分隔符.) `WEEKDAY_NAME`| String. Default is ' Sunday Monday Tuesday Wednesday Thursday Friday Saturday'. (注意,该字符串以空格字符开始,这是默认分隔符.) `YEAR_OPTION`| Integer. Default is 0. 取值范围为0 ~ 6。有关表示2位数和4位数年份的这些方法的解释,见ObjectScript $ZDATE函数。 要在系统范围内配置`TIME_PRECISION`,请进入管理门户,选择“系统管理”、“配置”、“SQL”和“对象设置”、“SQL”。 查看和编辑`GETDATE()`、`CURRENT_TIME`和`CURRENT_TIMESTAMP`的默认时间精度的当前设置。 它指定小数秒的精确位数。 默认值是`0`。 允许的值的范围是`0`到`9`位精度。 小数秒中有意义的数字的实际数目与平台有关。
文章
Michael Lei · 四月 19, 2022

用Caché ObjectScript 生成EXCEL

有很多方法可以使用Intersystems生成excel文件,其中一些是ZEN报告、IRIS报告(Logi报告或正式称为JReports),或者我们可以使用第三方Java库,可能性几乎是无限的。 但是,如果你想只用Caché ObjectScript创建一个简单的电子表格呢?(没有第三方应用程序) 在我的案例中,我需要生成包含大量原始数据的报告(财务人员喜欢这些数据),但是我的ZEN/IRIS失败了,给了我一个我想称之为 "零字节的文件",基本上说java的内存用完了,并导致报告服务器上的重载。 这可以用Office Open XML(OOXML)来完成。Office Open XML格式是由一个ZIP包内的一些XML文件组成的。因此,基本上我们需要生成这些XML文件,并将其压缩重命名为.xslx。就这么简单。 这些文件遵循一套简单的惯例,称为开放包装惯例。你需要声明各部分的内容类型,以及告诉消费应用程序应该从哪里开始。 为了创建一个简单的电子表格,我们至少需要5个文件。 workbook.xml worksheet.xml [Content_Types].xml styles.xml _rels .rels workbook.xml.rels workbook.xml工作簿是各种工作表的容器。工作簿是你可以引用样式部分、共享字符串表以及适用于整个电子表格文件的任何其他信息的地方。、 ClassMethod GenerateWorkbookXML(){ set status =$$$OK set xmlfile = tempDirectoryPath_"workbook.xml" try{ set stream = ##class(%Stream.FileCharacter).%New() set sc=stream.LinkToFile(xmlfile) do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>") do stream.WriteLine("<workbook xmlns='http://schemas.openxmlformats.org/spreadsheetml/2006/main' xmlns:r='http://schemas.openxmlformats.org/officeDocument/2006/relationships'>") do stream.WriteLine("<sheets> <sheet name='"_workSheetName_"' sheetId='1' r:id='rId1'/>") do stream.WriteLine("</sheets> </workbook>") do stream.%Save() }catch{ set status=$$$NO } kill stream return status } _rels/workbook.xml.rels 我们只需要创建一个id为rId1的关系,这样它就会与workbook.xml部分的引用相匹配 ClassMethod CreateRelsXML(){ set status =$$$OK set isunix=$zcvt($p($zv," ",3,$l($p($zv," (")," ")),"U")["UNIX" if isunix { set ext="/" }else{ set ext="\" } set xmlfile = fileDirectory_"_rels"_ext_"workbook.xml.rels" set stream = ##class(%Stream.FileCharacter).%New() set sc=stream.LinkToFile(xmlfile) do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>") do stream.WriteLine("<Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>") do stream.WriteLine("<Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet' Target='worksheet.xml'/>") do stream.WriteLine("<Relationship Id='rId2' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles' Target='styles.xml' />") do stream.WriteLine("</Relationships>") try{ do stream.%Save() }catch{ set status=$$$NO } kill stream set xmlfile = fileDirectory_"_rels"_ext_".rels" set stream = ##class(%Stream.FileCharacter).%New() set sc=stream.LinkToFile(xmlfile) do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>") do stream.WriteLine("<Relationships xmlns='http://schemas.openxmlformats.org/package/2006/relationships'>") do stream.WriteLine("<Relationship Id='rId1' Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument' Target='workbook.xml'/>") do stream.WriteLine("</Relationships>") try{ do stream.%Save() }catch{ set status=$$$NO } kill stream return status } [Content_Types].xml静态文件(目前,它应该是一个动态文件,取决于工作表的数量)将工作簿的工作表和样式链接在一起。每个Office Open XML文件必须声明ZIP包中使用的内容类型。这是用[Content_Types].xml文件完成的。 ClassMethod GenerateConntentTypesXML(){ set status =$$$OK set xmlfile = tempDirectoryPath_"[Content_Types].xml" set stream = ##class(%Stream.FileCharacter).%New() set sc=stream.LinkToFile(xmlfile) try{ do stream.WriteLine("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>") do stream.WriteLine("<Types xmlns='http://schemas.openxmlformats.org/package/2006/content-types'>") do stream.WriteLine("<Default Extension='rels' ContentType='application/vnd.openxmlformats-package.relationships+xml'/>") do stream.WriteLine("<Override PartName='/workbook.xml' ContentType='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml'/>") do stream.WriteLine("<Override PartName='/worksheet.xml' ContentType='application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml'/>") do stream.WriteLine("<Override PartName='/styles.xml' ContentType='application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml' />") do stream.WriteLine("</Types>") do stream.%Save() }catch{ set status=$$$NO } kill stream return status } styles.xml所有的格式化都在这里,目前我已经添加了一些静态样式,(计划将其转换为更多的动态工作簿特定的样式)。 Excel Styles ID Style Excel Format 1 default Text 2 #;[Red]-# Number 3 #.##;[Red]-#.## Number 4 yyyy/mm/dd Date 5 hh:mm Date 6 Header and Center Aligned Text 7 Header 2 Left Aligned Text 8 Good(Green Highlight) General 9 Bad(Red Highlight) General 10 Neutral(Orange Highlight) General 11 yyyy/mm/dd hh:mm Date ClassMethod CreateStylesXML(){ set status =$$$OK set xmlfile = tempDirectoryPath_"styles.xml" try{ set stream = ##class(%Stream.FileCharacter).%New() set sc=stream.LinkToFile(xmlfile) do stream.WriteLine("<?xml version=""1.0"" encoding=""UTF-8"" standalone=""yes""?>") do stream.WriteLine("<styleSheet xmlns=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"" xmlns:mc=""http://schemas.openxmlformats.org/markup-compatibility/2006"" mc:Ignorable=""x14ac x16r2 xr"" xmlns:x14ac=""http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"" xmlns:x16r2=""http://schemas.microsoft.com/office/spreadsheetml/2015/02/main"" xmlns:xr=""http://schemas.microsoft.com/office/spreadsheetml/2014/revision"">") do stream.WriteLine("<numFmts count=""4"">") do stream.WriteLine("<numFmt numFmtId=""166"" formatCode=""#,##0;[Red]\-#,##0""/>") do stream.WriteLine("<numFmt numFmtId=""168"" formatCode=""#,##0.00;[Red]\-#,##0.00""/>") do stream.WriteLine("<numFmt numFmtId=""169"" formatCode=""dd\/mm\/yyyy;@""/>") do stream.WriteLine("<numFmt numFmtId=""170"" formatCode=""dd/mm/yyyy\ hh:mm""/></numFmts>") do stream.WriteLine("<fonts count=""5"" x14ac:knownFonts=""1"">") do stream.WriteLine("<font><sz val=""10""/><color theme=""1""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>") do stream.WriteLine("<font><sz val=""10""/><color rgb=""FF006100""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>") do stream.WriteLine("<font><sz val=""10""/><color rgb=""FF9C0006""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>") do stream.WriteLine("<font><sz val=""10""/><color rgb=""FF9C5700""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font>") do stream.WriteLine("<font><b/><sz val=""10""/><color theme=""1""/><name val=""Calibri""/><family val=""2""/><scheme val=""minor""/></font></fonts>") do stream.WriteLine("<fills count=""5"">") do stream.WriteLine("<fill><patternFill patternType=""none""/></fill>") do stream.WriteLine("<fill><patternFill patternType=""gray125""/></fill>") do stream.WriteLine("<fill><patternFill patternType=""solid""><fgColor rgb=""FFC6EFCE""/></patternFill></fill>") do stream.WriteLine("<fill><patternFill patternType=""solid""><fgColor rgb=""FFFFC7CE""/></patternFill></fill>") do stream.WriteLine("<fill><patternFill patternType=""solid""><fgColor rgb=""FFFFEB9C""/></patternFill></fill></fills>") do stream.WriteLine("<borders count=""1""><border><left/><right/><top/><bottom/><diagonal/></border></borders>") do stream.WriteLine("<cellStyleXfs count=""4"">") do stream.WriteLine("<xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""0""/>") do stream.WriteLine("<xf numFmtId=""0"" fontId=""1"" fillId=""2"" borderId=""0"" applyNumberFormat=""0"" applyBorder=""0"" applyAlignment=""0"" applyProtection=""0""/>") do stream.WriteLine("<xf numFmtId=""0"" fontId=""2"" fillId=""3"" borderId=""0"" applyNumberFormat=""0"" applyBorder=""0"" applyAlignment=""0"" applyProtection=""0""/>") do stream.WriteLine("<xf numFmtId=""0"" fontId=""3"" fillId=""4"" borderId=""0"" applyNumberFormat=""0"" applyBorder=""0"" applyAlignment=""0"" applyProtection=""0""/></cellStyleXfs>") do stream.WriteLine("<cellXfs count=""12""><xf numFmtId=""0"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0""/>") do stream.WriteLine("<xf numFmtId=""49"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" quotePrefix=""1"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""166"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""168"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""169"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""20"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""49"" fontId=""4"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1"" applyFont=""1""/>") do stream.WriteLine("<xf numFmtId=""49"" fontId=""4"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1"" applyFont=""1"" applyAlignment=""1""><alignment horizontal=""center""/>") do stream.WriteLine("</xf>") do stream.WriteLine("<xf numFmtId=""49"" fontId=""1"" fillId=""2"" borderId=""0"" xfId=""1"" applyNumberFormat=""1""/>") do stream.WriteLine("<xf numFmtId=""0"" fontId=""2"" fillId=""3"" borderId=""0"" xfId=""2""/>") do stream.WriteLine("<xf numFmtId=""0"" fontId=""3"" fillId=""4"" borderId=""0"" xfId=""3""/>") do stream.WriteLine("<xf numFmtId=""170"" fontId=""0"" fillId=""0"" borderId=""0"" xfId=""0"" applyNumberFormat=""1""/></cellXfs>") do stream.WriteLine("<cellStyles count=""4""><cellStyle name=""Bad"" xfId=""2"" builtinId=""27""/>") do stream.WriteLine("<cellStyle name=""Good"" xfId=""1"" builtinId=""26""/><cellStyle name=""Neutral"" xfId=""3"" builtinId=""28""/>") do stream.WriteLine("<cellStyle name=""Normal"" xfId=""0"" builtinId=""0""/></cellStyles><dxfs count=""0""/>") do stream.WriteLine("<tableStyles count=""0"" defaultTableStyle=""TableStyleMedium2"" defaultPivotStyle=""PivotStyleLight16""/> ") do stream.WriteLine("<extLst><ext uri=""{EB79DEF2-80B8-43e5-95BD-54CBDDF9020C}"" xmlns:x14=""http://schemas.microsoft.com/office/spreadsheetml/2009/9/main"">") do stream.WriteLine("<x14:slicerStyles defaultSlicerStyle=""SlicerStyleLight1""/></ext><ext uri=""{9260A510-F301-46a8-8635-F512D64BE5F5}"" xmlns:x15=""http://schemas.microsoft.com/office/spreadsheetml/2010/11/main"">") do stream.WriteLine("<x15:timelineStyles defaultTimelineStyle=""TimeSlicerStyleLight1""/></ext></extLst>") do stream.WriteLine("</styleSheet>") do stream.%Save() }catch{ set status=$$$NO } kill stream return status } worksheet.xml这是我们的数据所在的地方。工作表的第一行将有列的标题。 接下来的行将只有第一列的数据。我们将在这里定义每一列的列宽,如果不是默认的,列将被设置为自动适应。 worksheet xml 示例 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <worksheet xmlns="https://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="https://schemas.openxmlformats.org/officeDocument/2006/relationships"> <sheetData> <row> <c t="inlineStr"> <is> <t>Name</t> </is> </c> <c t="inlineStr"> <is> <t>Amount</t> </is> </c> </row> <row> <c t="inlineStr"> <is> <t>Jhon Smith</t> </is> </c> <c> <v>1000.74</v> </c> </row> <row> <c t="inlineStr"> <is> <t>Tracy A</t> </is> </c> <c> <v>6001.74</v> </c> </row> </sheetData> </worksheet> Excel 示例 工作表中的公式可以用函数<f>标签来完成 <c > <f>B2*0.08</f > </c > <c > <f>B2+C2</f > </c> and finally we zip them, rename it to.xlsx (using unix zip) set cmd ="cd "_fileDirectory_" && find . -type f | xargs zip .."_ext_xlsxFile 生成excel文件. 以下代码生成excel 文件. set file = "/temp/test.xlsx" set excelObj = ##class(XLSX.writer).%New(file) do excelObj.SetWorksheetName("test1") set status = excelObj.BeginWorksheet() set row = 0 set row = row+1 ;----------- excelObj.Cells(rowNumber,columnNumber,style,content) set status = excelObj.Cells(row,1,1,"Header1") set row = row+1 set status = excelObj.Cells(row,1,2,"Content 1") set status = excelObj.EndWorksheet() W !,excelObj.fileName 写Excel类请看这里 xlsx.writer.xml.zip
文章
姚 鑫 · 六月 17, 2022

第三章 锁定和并发控制(三)

# 第三章 锁定和并发控制(三) # 升级锁 使用升级锁来管理大量锁。当锁定数组的节点时,它们是相关的,特别是当将多个节点锁定在同一下标级别时。 当给定进程在同一数组中的给定下标级别创建了超过特定数量(默认为 `1000`)的升级锁时, 将删除所有单独的锁名称并用新锁替换它们。新锁位于父级,这意味着数组的整个分支被隐式锁定。示例(如下所示)演示了这一点。 应用程序应在合适的情况下尽快释放特定子节点的锁(与非升级锁完全相同)。当释放锁时, 会减少相应的锁计数。当的应用程序移除足够多的锁时,会移除父节点上的锁。第二小节显示了一个示例。 ## 锁升级示例 假设有 `1000` 个`^MyGlobal("sales","EU",salesdate)` 形式的锁,其中 `salesdate` 表示日期。锁表可能如下所示: ![image](A6C86FBB77E643A88642FDBF2A59D6E3) 注意 `Owner 19776` 的条目(这是拥有锁的进程)。 `ModeCount` 列指示这些是共享的、升级的锁。 当同一进程试图创建另一个相同形式的锁时, 会升级它们。它会移除这些锁并用名称为 `^MyGlobal("sales","EU")` 的单个锁替换它们。现在锁表可能如下所示: ![image](DE73B7B561CA4C0C912C1C3301C3CF32) `ModeCount` 列表明这是一个共享的升级锁,它的计数是 `1001`。 请注意以下关键点: - `^MyGlobal("sales","EU")` 的所有子节点现在都被隐式锁定,遵循数组锁定的基本规则。 - 锁定表不再包含有关 `^MyGlobal("sales","EU")` 的哪些子节点被特别锁定的信息。这在删除锁时具有重要意义。见下一小节。 当同一进程添加更多形式为 `^MyGlobal("sales","EU",salesdate)` 的锁名称时,锁表会增加锁名称 `^MyGlobal("sales","EU")` 的锁计数。锁定表可能如下所示: ![image](AC88A17ABE92442EA4E18CEC6699719E) `ModeCount` 列指示此锁的锁计数现在为 `1026`。 ## 移除升级锁 与非升级锁完全相同,应用程序应尽快释放特定子节点的锁。当这样做时, 会减少升级锁的锁计数。例如,假设代码删除了 `^MyGlobal("sales","EU",salesdate)` 的锁定,其中 `salesdate` 对应于 2011 年的任何日期 — 因此删除了 `365` 个锁定。锁表现在看起来像这样: ![image](D723701CA49E43C9AAD6357C9CC50076) 请注意,即使现在锁的数量低于阈值 (`1000`),锁表也不包含 `^MyGlobal("sales","EU",salesdate)`. 的锁的单独条目。 节点 `^MyGlobal("sales")`保持显式锁定,直到该过程再删除 `661` 个 `^MyGlobal("sales","EU",salesdate)` 形式的锁定。 重要提示:有一点需要考虑,与前面的讨论有关。应用程序可能会“释放”数组节点上的锁,这些节点一开始就从未锁定,从而导致升级锁的锁计数不准确 - 并且可能在需要这样做之前释放升级锁。 例如,假设进程锁定 `^MyGlobal("sales","EU",salesdate)` 中从 2010 年到现在的节点。这将创建超过 `1000` 个锁,并且此锁将按计划升级。假设应用程序中的错误删除了 `1970` 年节点的锁。 将允许此操作,即使这些节点以前没有被锁定,并且 会将锁计数减少 `365`。生成的锁计数不会是所需锁的准确计数。如果应用程序随后移除了其他年份的锁,则升级的锁可能会意外地提前移除。 # Locks, Globals, and Namespaces 锁通常用于控制对全局变量的访问。因为可以从多个命名空间访问全局, 为其锁定机制提供自动跨命名空间支持。该行为是自动的,不需要干预,但在此描述以供参考。有几种情况需要考虑: - 任何命名空间都有一个默认数据库,其中包含持久类和任何其他全局变量的数据;这是此命名空间的全局数据库。访问数据时, IRIS 会从该数据库中检索数据,除非有其他考虑。一个给定的数据库可以是多个命名空间的全局数据库。请参见方案 1。 - 命名空间可以包括提供对存储在其他数据库中的全局变量的访问的映射。请参见方案 2。 - 命名空间可以包括下标级别的全局映射,这些映射提供对部分存储在其他数据库中的全局变量的访问。请参见方案 3。 - 在一个命名空间中运行的代码可以使用扩展引用来访问在此命名空间中不可用的全局变量。请参见方案 4。 尽管锁名称本质上是任意的,但是当使用以插入符号 (`^`) 开头的锁名称时,IRIS 提供了适合这些情况的特殊行为。以下小节给出了详细信息。为简单起见,只讨论排他锁;共享锁的逻辑类似。 ## 场景 1:具有相同Global数据库的多个命名空间 如前所述,虽然进程 `A` 拥有一个具有给定锁名的独占锁,但没有其他进程可以获取任何具有相同锁名的锁。 如果锁名称以插入符号开头,则此规则适用于使用相同全局数据库的所有命名空间。 例如,假设命名空间 `ALPHA` 和 `BETA` 都配置为使用数据库 `GAMMA` 作为其全局数据库。下面显示一个草图: ![image](A7C676A9EEA5405CBE60A5E0271C35DA) 然后考虑以下场景: 1. 在命名空间 `ALPHA` 中,进程 `A` 获得一个名为 `^MyGlobal(15)` 的独占锁。 2. 在命名空间 `BETA` 中,进程 `B` 尝试获取名称为 `^MyGlobal(15)` 的锁。此 `LOCK` 命令不返回;进程被阻塞,直到进程 `A` 释放锁。 在这种情况下,锁表只包含进程 `A` 拥有的锁的条目。如果检查锁表,会注意到它指示了该锁应用到的数据库;请参阅目录列。例如: ![image](D4EB913EE5EC4D14A9B937D0A7009DEB) ## 场景 2:命名空间使用映射的Global 如果一个或多个命名空间包含全局映射,系统会自动跨适用的命名空间强制实施锁定机制。当在非默认命名空间中获得锁时, IRIS 会自动创建额外的锁表条目。 例如,假设命名空间 `ALPHA` 配置为使用数据库 `ALPHADB` 作为其全局数据库。假设命名空间 `BETA` 配置为使用不同的数据库 (`BETADB`) 作为其全局数据库。命名空间 `BETA` 还包括一个全局映射,它指定 `^MyGlobal` 存储在 `ALPHADB` 数据库中。下面显示一个草图: ![image](D00DD242E49146E8BA9F0782DED2B0A5) 然后考虑以下场景: 1. 在命名空间 `ALPHA` 中,进程 `A` 获得一个名为 `^MyGlobal(15)` 的独占锁。 与前面的场景一样,锁表仅包含进程 `A` 拥有的锁的条目。此锁适用于 `ALPHADB` 数据库: ![image](E47C71BD0CCC40FEBD2722F460BC15D4) 2. 在命名空间 `BETA` 中,进程 `B` 尝试获取名称为 `^MyGlobal(15)` 的锁。此 `LOCK` 命令不返回;进程被阻塞,直到进程 `A` 释放锁。 ## 场景 3:命名空间使用映射的`Global`下标 如果一个或多个命名空间包含使用下标级别映射的全局映射,系统会自动跨适用的命名空间强制实施锁定机制。在这种情况下,当在非默认命名空间中获取锁时,IRIS 还会自动创建额外的锁表条目。 例如,假设命名空间 `ALPHA` 配置为使用数据库 `ALPHADB` 作为其全局数据库。命名空间 `BETA` 使用 `BETADB` 数据库作为其全局数据库。 还假设命名空间 `BETA` 还包括一个下标级别的全局映射,因此 `^MyGlobal(15)` 存储在 `ALPHADB` 数据库中(而这个全局的其余部分存储在命名空间的默认位置)。下面显示一个草图: ![image](00394289AF904AFE8EC62C77BB8D5E91) 然后考虑以下场景: 1. 在命名空间 `ALPHA` 中,进程 `A` 获得一个名为 `^MyGlobal(15)` 的独占锁。 2. 与前面的场景一样,锁表仅包含进程 `A` 拥有的锁的条目。此锁适用于 `ALPHADB` 数据库(例如,`c:\InterSystems\IRIS\mgr\alphadb`)。 当非默认命名空间获得锁时,整体行为是相同的,但 IRIS 处理细节略有不同。假设在命名空间 `BETA` 中,一个进程获得了一个名为 `^MyGlobal(15)` 的锁。在这种情况下,锁表包含两个条目,一个用于 `ALPHADB` 数据库,一个用于 `BETADB` 数据库。这两个锁都归命名空间 `BETA` 中的进程所有。 ![image](A3128EA8041344DE8D186A6264F2AEE7) 当此进程释放锁名称 `^MyGlobal(15)` 时,系统会自动删除两个锁。 # 场景 4:扩展的Global引用 在一个命名空间中运行的代码可以使用扩展引用来访问在此命名空间中不可用的全局变量。在这种情况下,IRIS 将一个条目添加到影响相关数据库的锁表中。锁归创建它的进程所有。例如,考虑以下场景。为简单起见,此方案中没有全局映射。 1. 进程 `A` 在 `ALPHA` 命名空间中运行,该进程使用以下命令获取 `BETA` 命名空间中可用的全局锁: ```java lock ^["beta"]MyGlobal(15) ``` 2. 现在锁定表包括以下条目: ![image](9B63E247C2704E26BA9F4C07FE886993) 请注意,这仅显示全局名称(而不是用于访问它的引用)。此外,在这种情况下,`BETADB` 是 `BETA` 命名空间的默认数据库。 3. 在命名空间 `BETA` 中,进程 `B` 尝试获取名称为 `^MyGlobal(15)` 的锁。此 `LOCK` 命令不返回;进程被阻塞,直到进程 `A` 释放锁。 进程私有`Global`在技术上是一种扩展引用,但 IRIS 不支持使用进程私有全局名称作为锁名称;无论如何,都不需要这样的锁,因为根据定义,只有一个进程可以访问这样的全局。
文章
Lilian Huang · 十二月 29, 2023

使用 FHIR 适配器在传统系统上提供 FHIR 服务 - 阅读资源

我们继续推出有关可供 HealthShare HealthConnect 和 InterSystems IRIS 用户使用的 FHIR 适配器工具的系列文章。 在前几篇文章中,我们介绍了小型应用程序,并在此基础上建立了我们的工作,并展示了安装 FHIR 适配器后在 IRIS 实例中部署的架构。在今天的文章中,我们将看到一个示例,说明如何执行最常见的 CRUD(创建 - 读取 - 更新 - 删除)操作之一,即读取操作,我们将通过恢复资源来完成此操作。 什么是资源? FHIR 中的一个资源对应一种相关的临床信息,这种信息可以是病人(Patient)、对实验室的请求(ServiceRequest)或诊断(Condition)等。每种资源都定义了组成它的数据类型,以及对数据的限制和与其他类型资源的关系。每个资源都允许对其包含的信息进行扩展,从而满足 FHIR 80% 以外的需求(满足 80% 以上用户的需求)。 在本文的示例中,我们将使用最常见的资源 "Patient"。让我们来看看它的定义: { "resourceType" : "Patient" , // from Resource: id, meta, implicitRules, and language // from DomainResource: text, contained, extension, and modifierExtension "identifier" : [{ Identifier }], // An identifier for this patient "active" : <boolean>, // Whether this patient's record is in active use "name" : [{ HumanName }], // A name associated with the patient "telecom" : [{ ContactPoint }], // A contact detail for the individual "gender" : "<code>" , // male | female | other | unknown "birthDate" : "<date>" , // The date of birth for the individual // deceased[x]: Indicates if the individual is deceased or not. One of these 2 : "deceasedBoolean" : <boolean>, "deceasedDateTime" : "<dateTime>" , "address" : [{ Address }], // An address for the individual "maritalStatus" : { CodeableConcept }, // Marital (civil) status of a patient // multipleBirth[x]: Whether patient is part of a multiple birth. One of these 2 : "multipleBirthBoolean" : <boolean>, "multipleBirthInteger" : <integer>, "photo" : [{ Attachment }], // Image of the patient "contact" : [{ // A contact party (eg guardian, partner, friend) for the patient "relationship" : [{ CodeableConcept }], // The kind of relationship "name" : { HumanName }, // IA name associated with the contact person "telecom" : [{ ContactPoint }], // IA contact detail for the person "address" : { Address }, // I Address for the contact person "gender" : "<code>" , // male | female | other | unknown "organization" : { Reference(Organization) }, // I Organization that is associated with the contact "period" : { Period } // The period during which this contact person or organization is valid to be contacted relating to this patient }], "communication" : [{ // A language which may be used to communicate with the patient about his or her health "language" : { CodeableConcept }, // R! The language which can be used to communicate with the patient about his or her health "preferred" : <boolean> // Language preference indicator }], "generalPractitioner" : [{ Reference(Organization|Practitioner| PractitionerRole) }], // Patient's nominated primary care provider "managingOrganization" : { Reference(Organization) }, // Organization that is the custodian of the patient record "link" : [{ // Link to a Patient or RelatedPerson resource that concerns the same actual individual "other" : { Reference(Patient|RelatedPerson) }, // R! The other patient or related person resource that the link refers to "type" : "<code>" // R! replaced-by | replaces | refer | seealso }] } 正如您所看到的,它几乎涵盖了患者的所有管理信息需求。 从我们的 HIS 中恢复患者信息 如果您还记得之前的文章中我们部署了一个模拟 HIS 系统数据库的 PostgreSQL 数据库,那么让我们看一下我们特定 HIS 中的示例表。 虽然数量不多,但对于我们的例子来说已经足够了。让我们更详细地看看我们的患者表。 这里我们有 3 个示例患者,您可以看到每个患者都有一个唯一的标识符 ( ID ) 以及一系列与卫生组织相关的管理数据。我们的首要目标是为我们的一位患者获取 FHIR 资源。 患者咨询 我们如何从我们的服务器请求患者数据?根据 FHIR 制定的实现规范,我们必须通过 REST 对包含我们服务器地址、资源名称和标识符的 URL 执行 GET。我们必须调用: http://SERVER_PATH/Patient/{id} 在我们的示例中,我们将搜索 Juan López Hurtado,其 id = 1,因此我们必须调用以下 URL: http://localhost:52774/Adapter/r4/Patient/1 为了进行测试,我们将使用 Postman 作为客户端。让我们看看服务器的响应是什么: { "resourceType" : "Patient" , "address" : [ { "city" : "TERUEL" , "line" : [ "CALLE SUSPIROS 39 2ºA" ], "postalCode" : "98345" } ], "birthDate" : "1966-11-23" , "gender" : "M" , "id" : "1" , "identifier" : [ { "type" : { "text" : "ID" }, "value" : "1" }, { "type" : { "text" : "NHC" }, "value" : "588392" }, { "type" : { "text" : "DNI" }, "value" : "12345678X" } ], "name" : [ { "family" : "LÓPEZ HURTADO" , "given" : [ "JUAN" ] } ], "telecom" : [ { "system" : "phone" , "value" : "844324239" }, { "system" : "email" , "value" : "juanitomaravilla@terra.es" } ] } 现在让我们分析一下我们的请求在生产中所采取的路径: 这里我们有路径: 请求到达我们的 BS InteropService。 将请求转发到我们已配置为 BS 目的地的 BP,在该 BP 中将恢复所接收呼叫的患者标识符。 从我们的 BO FromAdapterToHIS 查询到我们的 HIS 数据库。 将患者数据转发到我们的 BP,并将其转换为 FHIR 患者资源。 将响应转发给BS。 让我们看一下我们在 BP ProcessFHIRBP中收到的消息类型: 让我们看一下三个属性,它们对于识别客户端请求的操作类型至关重要: Request.RequestMethod:它告诉我们要执行什么类型的操作。在此示例中,搜索病人将采用 GET 方式。 Request.RequestPath:该属性包含到达服务器的请求路径,该属性将指示我们要处理的资源,在本例中,它将包括恢复资源的特定标识符。 Quick.StreamId: FHIR 适配器会将收到的每条 FHIR 消息转换为流,并为其分配一个标识符,该标识符将保存在此属性中。在本例中,我们不需要它,因为我们执行的是 GET,并没有发送任何 FHIR 对象。 让我们深入分析负责处理的 GLP,继续我们的消息之旅。 流程FHIRBP: 我们在生产中实施了 BPL,它将管理我们从业务服务收到的 FHIR 消息传递。让我们看看它是如何实现的: 让我们看看每个步骤中将执行的操作: 管理 FHIR 对象: 我们将调用负责连接到 HIS 数据库并负责数据库查询的 BO FromAdapterToHIS。 Method ManageFHIR(requestData As HS.FHIRServer.Interop.Request, response As Adapter.Message.FHIRResponse) As %Status { set sc = $$$OK set response = ##class (Adapter.Message.FHIRResponse). %New () if (requestData.Request.RequestPath = "Bundle" ) { If requestData.QuickStreamId '= "" { Set quickStreamIn = ##class (HS.SDA3.QuickStream). %OpenId (requestData.QuickStreamId,, .tSC) set dynamicBundle = ##class ( %DynamicAbstractObject ). %FromJSON (quickStreamIn) set sc = ..GetBundle (dynamicBundle, .response) } } elseif (requestData.Request.RequestPath [ "Patient" ) { if (requestData.Request.RequestMethod = "POST" ) { If requestData.QuickStreamId '= "" { Set quickStreamIn = ##class (HS.SDA3.QuickStream). %OpenId (requestData.QuickStreamId,, .tSC) set dynamicPatient = ##class ( %DynamicAbstractObject ). %FromJSON (quickStreamIn) set sc = ..InsertPatient (dynamicPatient, .response) } } elseif (requestData.Request.RequestMethod = "GET" ) { set patientId = $Piece (requestData.Request.RequestPath, "/" , 2 ) set sc = ..GetPatient (patientId, .response) } } Return sc } 我们的 BO 将检查收到的HS.FHIRServer.Interop.Request类型的消息,在本例中,通过设置 GET 并在与患者资源对应的路径中指示将调用GetPatient方法,我们将在下面看到: Method GetPatient(patientId As %String , Output patient As Adapter.Message.FHIRResponse) As %Status { Set tSC = $$$OK set sql= "SELECT id, name, lastname, phone, address, city, email, nhc, postal_code, birth_date, dni, gender FROM his.patient WHERE id = ?" //perform the Select set tSC = ..Adapter .ExecuteQuery(.resultSet, sql, patientId) If resultSet.Next() { set personResult = { "id" :(resultSet.GetData( 1 )), "name" : (resultSet.GetData( 2 )), "lastname" : (resultSet.GetData( 3 )), "phone" : (resultSet.GetData( 4 )), "address" : (resultSet.GetData( 5 )), "city" : (resultSet.GetData( 6 )), "email" : (resultSet.GetData( 7 )), "nhc" : (resultSet.GetData( 8 )), "postalCode" : (resultSet.GetData( 9 )), "birthDate" : (resultSet.GetData( 10 )), "dni" : (resultSet.GetData( 11 )), "gender" : (resultSet.GetData( 12 )), "type" : ( "Patient" )} } else { set personResult = {} } //create the response message do patient.Resource.Insert(personResult. %ToJSON ()) Return tSC } 正如您所看到的,此方法仅在我们的 HIS 数据库上启动查询并恢复所有患者信息,然后生成一个 DynamicObject,随后将其转换为 String 并存储在Adapter.Message.FHIRResponse类型的变量中。我们已将 Resource 属性定义为字符串列表,以便能够稍后在跟踪中显示响应。您可以直接将其定义为 DynamicObjects,从而节省后续转换。 检查是否捆绑: 根据 BO 的响应,我们检查它是否是 Bundle 类型(我们将在以后的文章中解释)或者它是否只是一个 Resource。 创建动态对象: 我们将 BO 响应转换为 DynamicObject 并将其分配给临时上下文变量 (context.temporalDO)。用于转换的函数如下: ##class ( %DynamicAbstractObject ). %FromJSON (context.FHIRObject.Resource.GetAt( 1 )) FHIR 变换: 使用 DynamicObject 类型的临时变量,我们将其转换为HS.FHIR.DTL.vR4.Model.Resource.Patient类的对象。如果我们想寻找其他类型的资源,我们必须为每种类型定义特定的转换。让我们看看我们的转变: 这种转换使我们能够拥有 BS InteropService 可以解释的对象。我们将结果存储在变量context.PatientResponse中。 将资源分配给 Stream : 我们将FHIR变换中获得的变量context.PatientResponse转换为Stream。 转换为 QuickStream: 我们将必须返回给客户端的所有数据分配给响应变量: set qs= ##class (HS.SDA3.QuickStream). %New () set response.QuickStreamId = qs. %Id () set copyStatus = qs.CopyFrom(context.JSONPayloadStream) set response.Response.ResponseFormatCode= "JSON" set response.Response.Status= 200 set response.ContentType= "application/fhir+json" set response.CharSet = "utf8" 在这种情况下,我们总是返回 200 响应。在生产环境中,我们应该检查是否已正确恢复搜索到的资源,如果没有,请将响应状态从 200 修改为对应“未找到”的 404。正如您在此代码片段中看到的,对象HS.FHIR.DTL.vR4.Model.Resource.Patient转换为 Stream 并存储为HS.SDA3.QuickStream ,将所述对象的标识符添加到QuickStreamID属性,随后我们的 InteropService 服务将以 JSON 形式正确返回结果。 结论: 让我们总结一下我们所做的事情: 我们发送了一个 GET 类型的请求,以搜索具有定义 ID 的患者资源。 BS InteropService已将请求转发至配置的BP。 BP 调用了负责与 HIS 数据库交互的 BO。 已配置的 BO 已从 HIS 数据库检索患者数据。 业务处理程序将结果转换为默认互操作服务创建的 BS 可理解的对象。 BS已收到响应并将其转发给客户端。 如您所见,操作相对简单,如果我们想在服务器中添加更多类型的资源,只需在 BO 中添加对数据库中与要恢复的新资源相对应的表的查询,并在 BP 中将 BO 的结果转换为与之相对应的 HS.FHIR.DTL.vR4.Model.Resource.* 类型的对象。 在下一篇文章中,我们将回顾如何将患者类型的新 FHIR 资源添加到我们的 HIS 数据库中。 感谢大家的关注!
文章
姚 鑫 · 二月 9, 2021

第二十九章 Caché 变量大全 $ZERROR 变量

# 第二十九章 Caché 变量大全 $ZERROR 变量 包含上一个错误的名称和位置。 # 大纲 ``` $ZERROR $ZE ``` # 描述 `$ZERROR`包含最新错误的名称,最新错误的位置(在适用的情况下)以及(对于某些错误代码而言)有关导致错误的原因的其他信息。 `$ZERROR`始终包含相应语言模式的最新错误。 `$ZERROR`值旨在错误后立即使用。由于`$ZERROR`值可能不会在例程调用中保留,因此希望保留`$ZERROR`值以供以后使用的用户应将其复制到变量中。**强烈建议用户在使用后立即将`$ZERROR`设置为空字符串(“”)。** $ZERROR中包含的字符串可以是以下任何一种形式: ```java entryref info entryref info ``` - `` 错误名称。错误名称始终以全部大写字母返回,并用尖括号括起来。它可能包含空格。 - `entryref` 对发生错误的代码行的引用。它由标签名称和距该标签的行偏移量组成,后跟`^`和程序名称。此`entryre`f紧跟在错误名称的右尖括号之后。从终端调用`$ZERROR`时,此`entryref`信息没有意义,因此不会返回。对最近使用`ZLOAD`加载到例程缓冲区中的例程的引用。 - `info` 特定于某些错误类型的附加信息(见下表)。此信息与``或`entryref`之间用空格分隔。如果有多个组件要提供信息,则用逗号分隔。 例如,一个程序(名为`zerrortest`)包含以下例程(名为`ZerrorMain`),该例程试图写入`fred`(一个未定义的局部变量)的内容: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR() ClassMethod ZERROR() { ZerrorMain TRY { SET $ZERROR="" WRITE "$ZERROR = ",$ZERROR,! WRITE fred } CATCH { WRITE "$ZERROR = ",$ZCVT($ZERROR,"O","HTML") } } ``` ```java DHC-APP> d ##class(PHA.TEST.SpecialVariables).ZERROR() $ZERROR = $ZERROR = <UNDEFINED>zZERROR+5^PHA.TEST.SpecialVariables.1 *fred ``` 在上面的示例中,第一个`$ZERROR`包含一个空字符串(`“”`),因为自从`$ZERROR`重置为空字符串以来没有发生任何错误。尝试写入未定义的变量会设置`$ZERROR`并将其抛给`CATCH`块。此`$ZERROR`包含`ZerrorMain+4^zerrortest*fred`,指定错误的名称、位置和特定于该类型错误的附加信息。在本例中,附加信息是未定义的局部变量`fred`的名称;星号前缀表示它是局部变量。(请注意,本例中使用`$ZCVT($ZERROR,“O”,“HTML”)`,因为Caché错误名称用尖括号括起来,并且本例从Web浏览器运行。) `Entryref`可能如下所示: - `ZerrorMain+4^zerrortest`--程序`zerrortest`中标签`ZerrorMain`的4行偏移量 - `ZerrorMain^zerrortest`--在程序`zerrortest`中没有与标签`ZerrorMain`的偏移量;标签行中出现错误 - `+3^zerrortest`--从程序`zerrortest`开始的3行偏移量;错误行前面没有标签 `$ZERROR`值的最大长度为512个字符。超过该长度的值将被截断为512个字符。 ## AsSystemError() Method `%Exception.SystemException`类的`AsSystemError()`方法返回与`$ZERROR`相同的值。下面的示例显示了这一点: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR1() ClassMethod ZERROR1() { TRY { KILL mylocal WRITE mylocal } CATCH myerr { WRITE "AsSystemError is: ",myerr.AsSystemError(),! WRITE "$ZERROR is: ",$ZERROR } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR1() AsSystemError is: zZERROR1+3^PHA.TEST.SpecialVariables.1 *mylocal $ZERROR is: zZERROR1+3^PHA.TEST.SpecialVariables.1 *mylocal ``` **在`Try/Catch`异常处理块结构中,`AsSystemError()`比`$ZERROR`更可取,因为`$ZERROR`可能会被异常处理期间发生的错误覆盖。** ## 有关某些错误的其他信息 当发生某些类型的错误时,`$ZERROR`将以以下格式返回错误: ```java entryref info ``` `INFO`组件包含有关错误原因的附加信息。下表列出了错误列表,其中包括附加信息和该信息的格式。错误代码与`INFO`组件之间用空格字符分隔。 错误代码 |信息组件 ---|--- `` | 未定义变量的名称(包括使用的任何下标)。这可以是局部变量、进程私有全局属性、全局属性或多维类属性。局部变量名称以星号作为前缀。多维属性名以句点开头,以区别于本地变量名。通过设置`%SYSTEM.Process.Unfined()`方法,可以更改Caché行为,以便在引用未定义的变量时不会生成``错误。 `` | 错误的下标引用:生成错误的行引用(例程和行偏移)、下标变量以及错误的下标级别。对于结构化系统变量(SSVN),仅提供行引用(例程和行偏移量)。通过设置`%SYSTEM.Process.NullSubscript()`方法,可以更改默认行为,以便在引用字符串下标为空的全局变量时不会生成错误。局部变量不允许使用空字符串下标。 `` |前缀为星号,即引用的例程名称。 `` | 前缀为星号,即引用的类名。 `` | 前缀为星号(引用属性的名称),后跟逗号分隔符和应该在其中的类名。 `` |前缀是星号,即调用的方法的名称,后跟逗号分隔符和应该在其中的类名。 `` | 全局引用的名称和包含全局引用的目录的名称,用逗号分隔。 `` |前缀为星号、对象名称,后跟`DisplayString()`方法返回的值。 `` | 当不在事务中调用`TCOMMIT`时,`INFO`组件为`*NoTransaction`。当调用不返回值的用户定义函数时,`INFO`组件是一条消息,其中包含本应返回值的命令的位置。 `` |以星号为前缀的无效目录的完整路径名。 `` | 当``错误终止进程时,带有附加信息的``错误将作为消息写入`mgr/cconsole.log`。信息性消息显示已终止进程的进程ID(PID)和产生错误的行引用(例程和行偏移量)。例如:`(PID)0at+13^|“user\|mytest` 例程(或方法)本地变量的名称以及未定义例程、类、属性和方法的名称都以星号(`*`)为前缀。进程-专用全局变量由其`^||`前缀标识。全局变量由它们的`^`(插入符号)前缀标识。类名以其`%`前缀形式表示。 以下示例显示了指定错误原因的其他错误信息。在每种情况下,指定的项都不存在。请注意,生成的错误的`INFO`组件与错误名称之间用空格分隔。星号(`*`)表示局部变量、类、属性或方法。插入符号(`^`)表示全局,`^||`表示进程私有全局。 ``错误示例: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR2() ClassMethod ZERROR2() { UndefTest ; SET $NAMESPACE="SAMPLES" KILL x,abc(2) KILL ^xyz(1,1),^|"USER"|xyz(1,2) KILL ^||ppg(1),^||ppg(2) TRY { WRITE x } // 未定义的局部变量 CATCH { WRITE $ZERROR,! } TRY { WRITE abc(2) } // 未定义的下标局部变量 CATCH { WRITE $ZERROR,! } TRY { WRITE ^xyz(1,1) } // 未定义的全局变量 CATCH { WRITE $ZERROR,! } TRY { WRITE ^|"USER"|xyz(1,2) } // 另一个命名空间中未定义的全局变量 CATCH { WRITE $ZERROR,! } TRY { WRITE ^||ppg(1) } // 未定义的进程专用全局变量 CATCH { WRITE $ZERROR,! } TRY { WRITE ^|"^"|ppg(2) } // 未定义的进程专用全局变量 CATCH { WRITE $ZERROR,! } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR2() zZERROR2+7^PHA.TEST.SpecialVariables.1 *x zZERROR2+13^PHA.TEST.SpecialVariables.1 *abc(2) zZERROR2+19^PHA.TEST.SpecialVariables.1 ^xyz(1,1) zZERROR2+25^PHA.TEST.SpecialVariables.1 ^xyz(1,2) zZERROR2+31^PHA.TEST.SpecialVariables.1 ^||ppg(1) zZERROR2+37^PHA.TEST.SpecialVariables.1 ^||ppg(2) ``` ``错误的示例: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR3() ClassMethod ZERROR3() { SubscriptTest ; DO $SYSTEM.Process.NullSubscripts(0) KILL abc,xyz TRY { SET abc(1,2,3,"")=123 } CATCH { WRITE $ZERROR,! } TRY { SET xyz(1,$JUSTIFY(1,1000))=1 } CATCH { WRITE $ZERROR,! } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR3() zZERROR3+5^PHA.TEST.SpecialVariables.1 *abc() Subscript 4 is "" zZERROR3+11^PHA.TEST.SpecialVariables.1 *xyz() Subscript 2 > 511 chars ``` ``错误的示例: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR4() ClassMethod ZERROR4() { NoRoutineTest ; KILL ^NotThere TRY { DO ^NotThere } CATCH { WRITE $ZERROR,! } TRY { JOB ^NotThere } CATCH { WRITE $ZERROR,! } TRY { GOTO ^NotThere } CATCH { WRITE $ZERROR,! } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR4() zZERROR4+4^PHA.TEST.SpecialVariables.1 *NotThere zZERROR4+10^PHA.TEST.SpecialVariables.1 *NotThere zZERROR4+16^PHA.TEST.SpecialVariables.1 *NotThere ``` 对象错误的示例: ```java DHC-APP>DO $SYSTEM.SQL.MyMethod() DO $SYSTEM.SQL.MyMethod() ^ *MyMethod,%SYSTEM.SQL DHC-APP>WRITE $SYSTEM.XXQL.MyMethod() WRITE $SYSTEM.XXQL.MyMethod() ^ *%SYSTEM.XXQL DHC-APP>SET x=##class(%SQL.Statement).%New() DHC-APP>WRITE x.MyProp WRITE x.MyProp ^ *MyProp,%SQL.Statement ``` ``错误的示例(在Windows上): ```java // 用户没有%SYS名称空间的访问权限 SET x=^|"%SYS"|var ^var,c:\intersystems\cache\mgr\ ``` 调用用户定义函数时的``错误示例。在本例中,`MyFunc Quit`命令不返回值。这将生成一个``错误,其中`entryref`指定`$$MyFunc`调用的位置,`INFO`消息指定`QUIT`命令的位置: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR5() ClassMethod ZERROR5() { Main TRY { KILL x SET x=$$MyFunc(7,10) WRITE "returned value is ",x,! RETURN } CATCH { WRITE "$ZERROR = ",$ZCVT($ZERROR,"O","HTML"),! } MyFunc(a,b) SET c=a+b QUIT } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR5() $ZERROR = <COMMAND>zZERROR5+4^PHA.TEST.SpecialVariables.1 *Function must return a value at zZERROR5+13^PHA.TEST.SpecialVariables.1 ``` 使用`PUBLIC`关键字将函数作为过程调用时,出现相同的``错误: ```java Main TRY { KILL x SET x=$$MyFunc(7,10) WRITE "returned value is ",x,! RETURN } CATCH { WRITE "$ZERROR = ",$ZCVT($ZERROR,"O","HTML"),! } MyFunc(a,b) PUBLIC { SET c=a+b QUIT } ``` ``错误示例(在Windows上): ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR6() ClassMethod ZERROR6() { TRY { SET prev=$SYSTEM.Process.CurrentDirectory("bogusdir") WRITE "previous directory: ",prev,! RETURN } CATCH { WRITE "$ZERROR = ",$ZCVT($ZERROR,"O","HTML"),! QUIT } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR6() $ZERROR = <DIRECTORY>zCurrentDirectory+2^%SYSTEM.Process.1 *e:\dthealth\db\dthis\data\bogusdir\ ``` ## 5.1版本之前的错误处理代码 在Caché5.1和后续版本的这些错误代码中添加`INFO`组件的结果是,假设`$ZERROR`中的字符串格式的5.1版本之前的错误处理例程可能需要重新设计才能像以前一样工作。例如,以下内容在5.1版中将不再有效: ```java WRITE "Error line: ", $PIECE($ZERROR, ">", 2) ``` 并应更改为类似以下内容: ```java WRITE "Error line: ", $PIECE($PIECE($ZERROR, ">", 2), " ", 1) ``` # 注意 ## ZLOAD和错误消息 在`ZLOAD`操作之后,加载到例程缓冲区中的例程的名称出现在后续错误消息的`entryref`部分。这将在整个过程中持续存在,或者直到使用`ZREMOVE`删除,或者被另一个`ZLOAD`删除或替换。以下终端示例显示例程缓冲区内容的此显示: ```java SAMPLES>ZLOAD Sample.Person.1 SAMPLES>WRITE 6/0 ^Sample.Person.1 SAMPLES>WRITE fred ^Sample.Person.1 *fred SAMPLES>WRITE ^fred ^Sample.Person.1 ^fred SAMPLES>ZNAME "USER" USER>WRITE 7/0 ^Sample.Person.1 USER>ZREMOVE USER>WRITE ^fred ^fred ``` ## $ZERROR和程序栈 `$ZERROR`字符串的``部分包含最新的错误消息。`$ZERROR`字符串的`entryref`部分的内容反映了最近错误的堆栈级别。以下终端会话试图调用无意义的命令`gobbledegook`,导致``错误。它还运行`ZerrorMain`(上面指定),产生`$ZERROR`值``。此终端会话期间的后续`$ZERROR`值反映了此程序调用,如下所示: ```java SAMPLES>gobbledegook SAMPLES>WRITE $ZERROR SAMPLES>DO ^zerrortest SAMPLES>WRITE $ZERROR ZerrorMain+2^zerrortest *FRED SAMPLES 2d0>gobbledegook SAMPLES 2d0>WRITE $ZERROR ^zerrortest SAMPLES 2d0>QUIT SAMPLES>WRITE $ZERROR ^zerrortest SAMPLES>gobbledegook SAMPLES>WRITE $ZERROR ``` ## 设置`$ZTRAP`时的`$ZERROR`操作 发生错误并设置`$ZTRAP`时,Caché在`$ZERROR`中返回错误消息,并分支到为`$ZTRAP`指定的错误陷阱处理程序 ## 设置`$ZERROR` 只有在Caché模式下,才能使用`set`命令将`$ZERROR`设置为最多512个字符的值。长度超过512个字符的值将被截断为512。 **强烈建议在错误处理后将`$ZERROR`重置为空字符串(`“”`)。**
文章
Hao Ma · 五月 26, 2023

IRIS镜像配置(4)_配置后的步骤

题外话:我刚刚翻译了InterSystems专家Bob Binstock的[Caché Mirroring 101:简要指南和常见问题解答](https://cn.community.intersystems.com/post/cach%C3%A9-mirroring-101%EF%BC%9A%E7%AE%80%E8%A6%81%E6%8C%87%E5%8D%97%E5%92%8C%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94)。 尽管题目是Caché Mirror 101, 而且是写于2016年,但因为讲解的都是Mirror的基本原理,所以在大量使用IRIS的今天也完全适用。 前面的3篇文章,包括了配置Mirror的各个方面。如果您照着操作,现在已经有了一个工作的mirror环境,并加入了您的数据库。然而,还没完,这篇我来讨论一下后面的工作,首先的问题是: **Mirror不复制什么** 简单说,Caché/IRIS镜像是**数据库复制(Database Replication)**。在Caché/IRIS里什么是数据库?也就是**Cache.dat和iris.dat**文件。数据库的修改日志,也就是journal,从主机被传送到其他镜像成员。而除此之外的内容,需要维护人员来分别的个个处理, 解决这些内容在各个镜像成员间的拷贝。需要很多的计划和细心。 >系统数据库, 包括IRISSYS, IRISTEMP, IRISLIB等等, 这些Caché/IRIS本身的数据库不应该被加入Mirror,在大多数Caché/IRIS版本里也都设置成不可以加入入MIRROR。 > >例外的HealthCare产品, HSSYS需要做Mirror, HSCustom可以做Mirror, 而HSLIB不可以Mirror 我们可以把问题转换成下面的题目: ## 需要人工在镜像成员中同步的项目 ### 命名空间(namespace)和Mapping 命名空间是应用开发的概念,它使用数据库。命名空间定义了3种映射关系:Package Mapping, Routing Mapping, Global Mapping。这样在一个命名空间可以使用多个数据库的内容。 通常情况下,用户会在主机创建命名空间的同时,创建一个新的带有mirror属性的数据库,然后会在其他mirror成员中手工一个个的创建命名空间,加入镜像的数据库。之后,管理员无需考虑更多的操作。 然而,对命名空间的修改,比如要添加或者删除命名空间的某些mapping,这偶尔会需要,尤其是应用迭代和系统扩容的情况下,那么,管理员/实施人员,必须清楚Mirror无法同步这个修改,您必须手工同步修改到其他机器去。 如果配置的mapping比较多, 我建议使用Manifest来操作。Mainfest是一个xml的文本,用来安装或者修改Caché/IRIS的配置,你可以参考[在线文档: Using a Manifest](https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest), 或者社区文章[使用Manifest](https://cn.community.intersystems.com/post/%E4%BD%BF%E7%94%A8manifest)。 这里给一个配置mapping的例子: ```xml ``` 如果是资深的Caché维护工程师,懂得如果修改CPF文件并在不重启实例的情况下应用修改后的内容,可以考虑把主机上的CPF中的mapping部分复制粘贴到其他机器。如果您没有这方面的经验,我不建议这种方式。 另外,在IRIS 2022后的版本中有了一个新工具,Configuration Merge。 文档在[这里](https://docs.intersystems.com/iris20231/csp/docbook/Doc.View.cls?KEY=ACMF)。可惜只有最新版的IRIS或者Health Connect 用户有的用。 ### 数据库的修改 数据库的内容会通过Journal从主机同步到其他成员,但修改不会,一般会遇到的是**压缩和截断**。 由于某种错误操作,某个数据库,会扩展到不正常的大,而当错误修正后,用户可能需要对该数据库进行压缩和截断,以释放被错误占用的空闲的磁盘空间。 由于除主机外,其他镜像成员的数据库都是只读的,这个操作的顺序应该是这样: 1. 在主机A执行压缩和截断 2. 切换到备机B, 再次执行压缩和截断。 3. 异步成员DR。 一种方案是吧DR提升到备机。这时当前的备机A会将为灾备,然后再切换DR为主机,再进行压缩和截断。 还有一个选择,就是重新配置DR上的这个数据库,这需要从主机到DR的数据库备份和恢复。 ### IRIS实例的配置 从最常用的内存的配置,Service的配置, **用户,权限,资源**的配置等等。它们都不会被MIRROR同步。如果您在MIRROR主机里做了修改了缩表的大小,或者启动了一个,比如TELNET服务, 您需要人工在其他机器上做相同操作。 像上面的mapping配置一样,这里还是建议使用Manifest人工同步IRIS得修改。注意的是,Mainfest不保证能支持所有的配置。比如在Caché的版本下, 比如您在主机上启动了TELNET服务, Manifest没有相应的标签。这种情况下, 如果您熟悉ObjectScript语言,可以把ObjectScript实现加入执行Manifest的方法,比如说: ```java ClassMethod main(){ //执行Manifest修改命名空间 Set pVars("Namespace")="MYNAMESPACE" $$$ThrowOnError(..ModifyNamespace(.pVars)) //启动IRIS的TELNET服务 set properties("Enabled")=1 // 有効 set sts=##class(Security.Services).Modify("%Service_Telnet",.properties) } ``` 当然,如果您缺乏开发实施的知识,在用户界面上一个个机器的操作是最省心的办法。 问题是,打开一个服务,修改一个配置参数操作都很简单,但是如果要添加大量的用户和权限怎么办? 用Manifest管理是一个办法。但根本上,如果您经常有大量的用户管理的工作,其实使用Kerberos或者LDAP管理用户身份认证和授权的工作, 在有多个镜像成员的情况下,尤其的合适。 关于这部分内容,请参考[在线文档:Authentication and Authorization](https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls?KEY=PAGE_security_authentication_authorization) ### 定时任务(TASK) 在主机上创建的定时任务, 您需要人工在其他机器上做相同操作。这里有2个步骤: 1. 在主机上创建新任务的时候,要选择”**应如何为镜像运行任务**“。 这是个下拉菜单,选项有*”仅在主镜像成员上运行“,“仅在非主镜像成员上运行“ ,“在任何镜像成员上运行"。* 选择的出发点是:非主镜像成员的数据库是只读的。因此,比如一个Ensemble的镜像配置中, 删除Ensemble消息的定时任务, 一定是”仅在主镜像成员上运行“。 2. 把新的定时任务从主机同步到其他成员。 ​ 如果是一个或者少量几个TASK, 那么手工在其他各个镜像成员上添加是最简单直接的做法。而如果是有很长 的任务列表,尤其在配置Mirror得时候可以需要同步一个长长的列表时, 您可以考虑**从主机导出Task到其 他机器导入**,我只知道使用ObjectScript命令的方法, 使用`%SYS.Task.ExportTask()`和 `%SYS.Task.ImportTasks()`。 文档在[这里](https://docs.intersystems.com/iris20231/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYS.Task)。 ### Web Application 主机上配置的Web Applicaiton 也要同步到其他镜像成员。如果要同步的Web Application比较多,推荐的方式依然是Manifest, 下面是一个例子。 ```xml ``` 麻烦的是不同的版本Caché/IRIS使用的标签上会略有不同,要稍微仔细的查看一下您的版本的文档。 如果您对ZPM, 现在称为IPM熟悉的话, 用ZPM做同步也是个好选择。关于zpm, 您可以参考这个帖子[zpm介绍](https://cn.community.intersystems.com/post/zpm%E4%BB%8B%E7%BB%8D1)。提醒一下的是,程序因为是存在数据库里面的,如果该数据库是被镜像的,您其实不需要用ZPM把程序代码拷贝到其他镜像成员。 ### Gateway 一般用到的有**SQL Gateway**和**External Language Gateway**,它们分别用于连接其他的数据库和使用其他语音的代码包。 SQL Gateway 记录保存在%SYS命名空间的*%Library.sys_SQLConnection*数据表里。简单的方法是使用工具把表记录导入导出。 External Language Gateway(外部语言网关) 新版的IRIS系统内嵌了外部语言服务器,包括%Python Server, %Java Server, %Dotnet Server等。如果您使用的是默认配置,各个镜像成员是一致的,无需操心。如果只是IP端口的修改,手工同步一下也很容易,毕竟工作量有限,只是您需要清楚的记得,这个也是不被Mirror自动同步的。 ### 文件 我把文件分为两类, 一类是“固定文件”,包括一下几个部分, - CSP文件,js文件,css文件,html文件等 - XSLT文件 - 其他语言的程序代码,Java文件,python文件, .Net文件 这类文件上传到主机的时候, 也必须上传到其他镜像成员,这是个简单的操作,别忘了就行。 麻烦的是**流文件**。在ObjectScript里如果使用了%Stream.FileBinary, %Stream.FileCharacter等类,那么数据不是保存到Cache.Dat或者IRIS.data, 而是保存在和.Dat同目录的一个stream的子目录下,而这个目录是不会被镜像同步的。 而且,因为这是实时数据,你也不可能手工的把它拷来拷去。 如果您的应用里用到了文件流,我任务您需要一个文件服务器保证流文件在各个各个镜像成员间的同步。 ### Ensemble Production Consideration 对于Ensemble和Health Connect用户,您需要阅读这部分在线文档: [Production Considerations for Mirroring](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_manage#GHA_mirror_set_ensemble) , 简单总结一下: - 创建的带有ensemble或者Inteoprability的命名空间,数据库要创建为Mirror的数据库。 - **"production是否自动启动“**应该在主机和备机上,甚至DR上都配置为“自动启动”。 在Mirror配置下的Production会先检查这个实例是不是主机,如果不是,“自动启动”的配置也不会生效,这样保证了Production只在主机上运行,而切换后也不需要人工干预。 上面的这些并不是完整的内容,尽管在大多少情况下这些内容差不多够了。如果您想要确保Mirror的主机的工作内容完全同步到了备机和DR, 请仔细阅读在线文档的这一部分:[Mirror Configuration Guidelines](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_set_config#GHA_mirror_set_config_guidelines) 另外,对于各种需要人工同步的内容的操作,还建议阅读[在线文档:Server Migration](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=AMIG#AMIG_migration_external)。 如果是最新的IRIS用户,请参考[在线文档:Deploy Mirrors Using Configuration Merge](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_set_config#GHA_mirror_set_config_auto_merge)