搜索​​​​

清除过滤器
文章
water huang · 三月 27, 2023

医院信息化建设实战教程:如何在不允许使用Git的情况下自动备份代码/自动执行代码?

一、背景 1.1 我遇到了几个项目,他们的接口服务器崩溃了。 项目上希望尽快恢复服务器。他们的服务器在局域网上运行,他们不能使用git,服务器中有多个命名空间运行不同的服务,而且通常只有一台平台服务器。 1.2 如果消息中有字符流类型的属性,消息搜索页面不支持使用字符流属性进行过滤,因此很难找到想要的消息。 1.3 其他同事可能会更新服务器上的代码,代码中可能有些错误。 2.挑战 2.1 如何快速恢复? 2.2 如何支持字符流属性过滤消息? 2.3 如何在编译类时自动备份? 3.解决方案 1.编译时自动导出为备份文件 首先,我们定义一个名为“SYS.base”的类,它只有一个名为“ CLSBAKPATH”的参数,并设置它的值 Class SYS.Base Extends %RegisteredObject { Parameter CLSBAKPATH = "D:\IRIS\CLSBAK" ; } 然后,定义一个名为“SYS.Projection”的类,它继承了 base和%Projection.AbstractProjection,添加“Projection Reference As SYS.Projection”,重写类方法“CreateProjection”; 代码如下: Class SYS.Projection Extends ( %Projection.AbstractProjection , Base) { Projection Reference As SYS.Projection ; ClassMethod CreateProjection(classname As %String , ByRef parameters As %String , modified As %String , qstruct) As %Status { w ! s changetime=^oddDEF(classname,63) s changetime=$zdt(changetime,3) w "class"_classname_" is modified at "_changetime_" !",! s CLSBAKPATH=..#CLSBAKPATH i CLSBAKPATH[ "/" d .s PL= "/" e d .s PL= "\" s:$e(CLSBAKPATH,*)'=PL CLSBAKPATH=CLSBAKPATH_PL s sc= $SYSTEM.OBJ.Export(classname_".cls",CLSBAKPATH_classname_$tr(changetime,":-","")_ ".xml" ) q $$$OK } } 执行该方法后,被编译的类将直接导出到目标路径“CLSBAKPATH”,它的名称将类似于“类名20230217130218.xml” 最后,如何使用它? 选择一个要自动备份的类,让它继承“SYS.Projection”。当类被编译时,它会被导出。如果你不修改重新编译它,它会覆盖旧的备份文件。 顺便说一句,我认为关键点是 %Projection.AbstractProjection,有了“CreateProjection”,我们可以做更多,如果你想在生产环境中更新一些代码,并且又不想让人再手动去执行一些目标方法代码,这是个好方法。他需要做的是将xml文件导入studio,并编译它,如果勾选“Compile Imported Items”框,那就不再需要单独编译。也就是导入的时候就会自动编译,编译的时候就自动执行,相当于就是导入后自动执行! 到目前为止,它只备份了一个类文件。如果我们想要备份更多怎么办? 方法来了!简单来说,挂任务! 通常用于备份接口代码和数据。 在本教程中,我们在本地驱动器中备份文件。 首先,我们定义一个名为“Common.SYS”的类 然后添加一个名为“ Export ”的类方法,它是主要方法,它将调用其他功能方法。 A导出类,B导出凭证,C导出GLB。 类看起来像: Class Common.SYS Extends %RegisteredObject { /// Exportclass ClassMethod ExportAllClassesIndividual(NS As %String = "" ) { s Drive=..#Drive s currentns= $ZUTIL ( 67 , 6 , $j ) i NS'= "" d .w:'$d(^["%sys"]CONFIG("Namespaces",NS)) "namespace "_NS_" is not existed,please check it!" ,! .q:'$d(^[ "%sys"]CONFIG("Namespaces",NS)) .zn NS .s FilePath=Drive_":/BakFile/"_NS_"/" .i '##class(%File).DirectoryExists(FilePath) d ..d ##class(%File).CreateDirectoryChain(FilePath) .s sc= $system .OBJ.ExportAllClassesIndividual(FilePath,"b",.el,"","CDYZone,Sample,User" ) .i sc'=1 d ..w " namespace "_NS_" get an error when export class! "_$System.Status.GetErrorText(sc),! e d .s alns="" .f s alns=$o(^["%sys"]CONFIG("Namespaces",alns)) q:alns="" d ..q:$e(alns,1)="%" ..q:(alns="ENSDEMO")||(alns="ENSEMBLE")||(alns["-") ..q:'$d(^[alns]CacheMsg("Confirm")) ..zn alns ..s FilePath=Drive_":/BakFile/"_alns_"/" ..i '##class(%File).DirectoryExists(FilePath) d ...d ##class(%File).CreateDirectoryChain(FilePath) ..s sc=$system.OBJ.ExportAllClassesIndividual(FilePath,"b",.el,"","Common,SYS" ) ..i sc'=1 d ...w " namespace "_alns_" get an error when export class! "_ $System.Status.GetErrorText(sc),! zn currentns q $$$OK } /// Ens.Config.Credentials ClassMethod ExportAllCredentials(NS As %String = "" ) { s Drive=..#Drive s currentns= $ZUTIL ( 67 , 6 , $j ) i NS'= "" d . w :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) "namespace " _NS_ " is not existed,please check it!" ,! . q :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) . q :' $d (^[NS]Ens.Conf.CredentialsD) . zn NS . s Data=[] . s FilePath=Drive_ ":/BakFile/" _NS_ "/Ens_Config_Credentials" .i ' ##class ( %File ).DirectoryExists(FilePath) d ..d ##class ( %File ).CreateDirectoryChain(FilePath) . s Data=[] . s CID= "" .f s CID= $o ( ^Ens .Conf.CredentialsD(CID)) q :CID= "" d ..s obj= ##class (Ens.Config.Credentials). %OpenId (CID) ..q :' $IsObject (obj) ..s data={} ..s data.SystemName=obj.SystemName ..s data.Username=obj.Username ..s data.Password=obj.Password ..d Data. %Push (data) . d :Data. %Size ()> 0 ..SaveFile (FilePath,Data. %ToJSON ()) e d . ;w currentns,! . s alns= "" .f s alns= $o (^[ "%sys" ]CONFIG( "Namespaces" ,alns)) q :alns= "" d ..q : $e (alns, 1 )= "%" ..q :(alns= "ENSDEMO" )||(alns= "ENSEMBLE" )||(alns[ "-" ) ..q :' $d (^[alns]CacheMsg( "Confirm" )) ..q :' $d (^[alns]Ens.Conf.CredentialsD) ..zn alns ..s FilePath=Drive_ ":/BakFile/" _alns_ "/Ens_Config_Credentials" ..i ' ##class ( %File ).DirectoryExists(FilePath) d . ..d ##class ( %File ).CreateDirectoryChain(FilePath) ..s Data=[] ..s CID= "" ..f s CID= $o ( ^Ens .Conf.CredentialsD(CID)) q :CID= "" d . ..s obj= ##class (Ens.Config.Credentials). %OpenId (CID) . ..q :' $IsObject (obj) . ..s data={} . ..s data.SystemName=obj.SystemName . ..s data.Username=obj.Username . ..s data.Password=obj.Password . ..d Data. %Push (data) ..d :Data. %Size ()> 0 ..SaveFile (FilePath,Data. %ToJSON ()) zn currentns q $$$OK } ClassMethod SaveFile(FilePath, Data) { s file= ##class ( %FileCharacterStream ). %New () s file.Filename=FilePath_ "/Credentials.JSON" d file. Write (Data) s sc=file. %Save () i sc'= 1 d . w FilePath_ " save Credential failed! " _ $System .Status.GetErrorText(sc),! e d . w FilePath_ " save Credential success! " ,! q $$$OK } ClassMethod SaveGLB(NS) { s Drive=..#Drive w :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) "namespace " _NS_ " is not existed,please check it!" ,! q :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) 1 s rset = ##class ( %ResultSet ). %New ( "%SYS.GlobalQuery:NameSpaceList" ) q :'rset.QueryIsValid() "invalid Query:" d rset.Execute(NS, "CDY*" ) s Flag= 0 while (rset.Next()){ s HasData=rset.GetDataByName( "HasData" ) i HasData= 1 d . s Name=rset.GetDataByName( "Name" ) . q :Name[ "PushConfigListD" ;some needed globals . s Data(Name)= 1 . s Flag= 1 } w :Flag= 0 NS_ " namespace doesn't have global to be exported" ,! q :Flag= 0 NS_ " namespace doesn't have global to be exported" s FilePath=Drive_ ":/BakFile/" _NS_ "/GLB/" i ' ##class ( %File ).DirectoryExists(FilePath) d . d ##class ( %File ).CreateDirectoryChain(FilePath) q ##class ( %Library.Global ).Export(NS,.Data,FilePath_ "SYS.gof" ) } /// ExportAll CDY* Globals ClassMethod ExportAllCDYGLB(NS As %String = "" ) { s currentns= $ZUTIL ( 67 , 6 , $j ) i NS'= "" d . w :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) "namespace " _NS_ " is not existed,please check it!" ,! . q :' $d (^[ "%sys" ]CONFIG( "Namespaces" ,NS)) . s sc= ..SaveGLB (NS) .i sc'= 1 d ..w " namespace " _NS_ " get an error when export global! " _ $System .Status.GetErrorText(sc),! e d . s alns= "" .f s alns= $o (^[ "%sys" ]CONFIG( "Namespaces" ,alns)) q :alns= "" d ..q : $e (alns, 1 )= "%" ..q :(alns= "ENSDEMO" )||(alns= "ENSEMBLE" )||(alns[ "-" ) ..q :' $d (^[alns]CacheMsg( "Confirm" )) ..s sc= ..SaveGLB (alns) ..i sc'= 1 d . ..w " namespace" _alns_ " get an error when export global! " _ $System .Status.GetErrorText(sc),! zn currentns q $$$OK } /// location to store backuped file Parameter Drive = "d" ; ClassMethod Export() { s NS= "" ///namespace d ..ExportAllClassesIndividual (NS) ///class file(include package) d ..ExportAllCredentials (NS) ///Credential information d ..ExportAllCDYGLB (NS) ///Global file, or data q $$$OK } } 在 iris 中添加一个自定义任务,你就能保留最新的代码和数据。 优化消息搜索 当我得到使消息搜索页面支持使用字符流类型的属性进行过滤的解决方案时(为了解决这个问题,我问 intersystems 的工程师,她告诉我,使用“EnsPortal.MsgFilter.Assistant”,覆盖类方法“GetSQLCondition”。所以我做了一个类扩展 EnsPortal.MsgFilter.Assistant.it 似乎已经解决了问题。在编译类之后然后 s ^EnsPortal.Settings("MessageViewer","AssistantClass")=class ). 我写的代码如下: Class ENSLIB.MsgFilterAssistant Extends EnsPortal.MsgFilter.Assistant { ClassMethod GetSQLCondition(pOperator As %String , pProp As %String , pValue As %String , pDisplay As %Boolean = 0 ) As %String { if (pValue = "" ) || ((pOperator '= "Like" ) && (pOperator '= "NotLike" )) quit ##super (pOperator, pProp, pValue, pDisplay) if ( "%%" = $extract (pValue, *- 2 , *- 1 )) { set pValue = "'" _ $extract (pValue, 1 , *- 3 ) _ "' ESCAPE '" _ $extract (pValue, *) _ "'" } else { set pValue = "'" _ $replace (pValue, "'" , "''" ) _ "'" } quit "substring(" _ pProp _ ", 1, 3000000) " _ $case (pOperator, "Like" : "LIKE" , "NotLike" : "NOT LIKE" ) _ " " _ pValue } /// w ##class(ENSLIB.MsgFilterAssistant).SetMV() ClassMethod SetMV() { s ^EnsPortal .Settings( "MessageViewer" , "AssistantClass" )= "ENSLIB.MsgFilterAssistant" q $$$OK } } 导入代码后,必须有人执行'##class(ENSLIB.MsgFilterAssistant).SetMV()',然后它才能工作!所以我想如果有一种方法可以在导入或编译后自动运行 SetMV,我寻找解决方案,从论坛到开放交换,最后我想到可以用 Projection,代码更改为: Class ENSLIB.MsgFilterAssistant Extends (EnsPortal.MsgFilter.Assistant, %Projection.AbstractProjection ) { Projection Reference As MsgFilterAssistant ; ClassMethod GetSQLCondition(pOperator As %String , pProp As %String , pValue As %String , pDisplay As %Boolean = 0 ) As %String { if (pValue = "" ) || ((pOperator '= "Like" ) && (pOperator '= "NotLike" )) quit ##super (pOperator, pProp, pValue, pDisplay) if ( "%%" = $extract (pValue, *- 2 , *- 1 )) { set pValue = "'" _ $extract (pValue, 1 , *- 3 ) _ "' ESCAPE '" _ $extract (pValue, *) _ "'" } else { set pValue = "'" _ $replace (pValue, "'" , "''" ) _ "'" } quit "substring(" _ pProp _ ", 1, 3000000) " _ $case (pOperator, "Like" : "LIKE" , "NotLike" : "NOT LIKE" ) _ " " _ pValue } /// w ##class(ENSLIB.MsgFilterAssistant).SetMV() ClassMethod SetMV() { s ^EnsPortal .Settings( "MessageViewer" , "AssistantClass" )= $this q $$$OK } ClassMethod CreateProjection(cls As %String , ByRef params) As %Status { w ! w "开始执行" ,! d ..SetMV () w "执行完成" ,! q $$$OK } } 类编译完成后会自动执行SetMV!是否有更好的实现方式呢? 如果你有更好的方法,请分享! 4.更多 我将继续寻找更好的方法来复制源类和配置数据。 为黄老师点赞!
文章
姚 鑫 · 四月 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)
文章
姚 鑫 · 七月 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或其他“人类可读的”文档。
文章
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使用情况的简单介绍,如需了解更多,欢迎留言与我们交流!
文章
姚 鑫 · 五月 5, 2021

第三章 使用多维存储(全局变量)(一)

# 第三章 使用多维存储(全局变量)(一) 本章描述了使用多维存储(全局变量)可以执行的各种操作。 # 以全局变量存储数据 在全局节点中存储数据很简单:像对待任何其他变量一样对待全局变量。 区别在于对全局变量的操作是自动写入数据库的。 ## 创建全局变量 创建新的全局变量不需要设置工作;只需将数据设置为全局变量即可隐式创建新的全局结构。可以创建全局变量(或全局变量下标)并通过单个操作将数据放入其中,也可以创建全局变量(或下标)并通过将其设置为空字符串将其保留为空。在ObjectScript中,这些操作是使用`SET`命令完成的。 下面的例子定义了一个名为`Color`(如果还不存在)的全局变量,并将值`“Red”`与之关联。 如果已经存在一个名为`Color`的全局变量,那么这些示例将其修改为包含新信息。 在ObjectScript中: ```java SET ^Color = "Red" ``` 注意:在应用程序中使用直接全局访变量问时,应制定并遵守命名约定,以防止应用程序的不同部分相互“遍历”;这类似于为类、方法和其他变量开发命名约定。 ## 在全局变量节点中存储数据 要在全局下标节点中存储值,只需像设置任何其他变量数组一样设置全局节点的值。如果指定的节点以前不存在,则会创建该节点。如果它确实存在,则其内容将替换为新值。 可以通过表达式(称为全局引用)指定全局内的节点。全局引用由脱字符(`^`)、全局名称和(如果需要)一个或多个下标值组成。下标(如果有)用括号“()”括起来,并用逗号分隔。每个下标值本身都是一个表达式:文字值、变量、逻辑表达式,甚至是全局引用。 **设置全局节点的值是一个原子操作:它肯定会成功,不需要使用任何锁来确保并发性。** 以下都是有效的全局引用: 在ObjectScript中: ```java SET ^Data = 2 SET ^Data("Color")="Red" SET ^Data(1,1)=100 /*第二级下标(1,1)设置为值100。第一级下标(^DATA(1))不存储任何值。 */ SET ^Data(^Data)=10 /*全局变量^data的值是下标的名称。 */ SET ^Data(a,b)=50 /*局部变量a和b的值是下标的名称 */ SET ^Data(a+10)=50 ``` 此外,还可以在运行时使用间接方式构造全局引用。 ## 在全局变量节点中存储结构化数据 每个全局节点可以包含最多`32K`个字符的单个字符串。 数据通常以以下方式之一存储在节点中: - 作为最多`32K`个字符的单个字符串(具体地说,`32K - 1`)。 - 作为包含多条数据的字符分隔字符串。 要使用字符分隔符在节点中存储一组字段,只需使用连接操作符(`_`)将这些值连接在一起。下面的ObjectScript示例使用`#`字符作为分隔符: ```java SET ^Data(id)=field(1)_"#"_field(2)_"#"_field(3) ``` 检索数据时,可以使用`$PIECE`函数将字段拆分: ```java SET data = $GET(^Data(id)) FOR i=1:1:3 { SET field(i) = $PIECE(data,"#",i) } QUIT ``` - 作为包含多条数据的`$LIST`编码字符串。 `$LIST`函数使用特殊的长度编码方案,不需要保留分隔符。(这是InterSystems IRIS对象和SQL使用的默认结构。) 要在节点中存储一组字段,请使用`$LISTBUILD`函数构造列表: ```java SET ^Data(id)=$LISTBUILD(field(1),field(2),field(3)) ``` 检索数据时,可以使用`$LIST`或`$LISTGET`函数将字段拆分: ```java SET data = $GET(^Data(id)) FOR i = 1:1:3 { SET field(i)=$LIST(data,i) } QUIT ``` - 作为较大数据集(例如流或`“BLOB”`)的一部分。 **由于单个节点的数据量限制在略低于`32K`,因此可以通过将数据存储在一组连续节点中来实现更大的结构(如流):** ```java SET ^Data("Stream1",1) = "First part of stream...." SET ^Data("Stream1",2) = "Second part of stream...." SET ^Data("Stream1",3) = "Third part of stream...." ``` **获取流的代码(如`%GlobalCharacterStream`类提供的流)循环遍历结构中的连续节点,该结构将数据作为连续字符串提供**。 - 作为一个位串。 如果正在实现位图索引(位字符串中的位对应表中的行的索引),应该将全局索引的节点值设置为位字符串。 请注意IRIS使用压缩算法来编码位串; 因此,位串只能使用IRIS `$BIT`函数来处理。 - 作为一个空节点。 如果感兴趣的数据是由节点本身提供的,那么通常将实际下标设置为空字符串(`""`)。 例如,将名称与`ID`值相关联的索引通常是这样的: ```java SET ^Data("APPLE",1) = "" SET ^Data("ORANGE",2) = "" SET ^Data("BANANA",3) = "" ``` # 删除全局节点 要从数据库中删除一个全局节点、一组子节点或整个全局节点,请使用ObjectScript `kill`或`ZKILL`命令。 `Kill`命令删除特定全局引用处的所有节点(数据及其在数组中的相应条目),包括任何子代节点。也就是说,所有以指定下标开头的节点都将被删除。 例如,ObjectScript语句: ```java KILL ^Data ``` 删除整个`^Data`全局变量。对此全局变量的后续引用将返回``错误。 ObjectScript语句: ```java KILL ^Data(100) ``` 删除`^Data`全局变量中节点`100`的内容。如果有子代节点,如`^data(100,1)`、`^data(100,2)`和`^data(100,1,2,3)`,这些子节点也会被删除。 ObjectScript `ZKILL`命令用于删除指定的全局或全局下标节点。它不会删除子代子节点。 **注意:在杀死一个大型全局变量之后,该全局变量曾经占用的空间可能没有完全释放,因为垃圾收集器守护进程在后台将这些块标记为空闲。因此,在终止大型全局变量之后立即调用`SYS.Database`类的`ReturnUnusedSpace`方法可能不会返回预期大小的空间,因为该全局占用的块可能尚未释放。** **不能对全局变量使用`new`命令。** # 测试全变量局节点的存在 要测试特定全局变量(或其后代)是否包含数据,请使用`$DATA`函数。 `$DATA`返回一个值,该值指示指定的全局变量引用是否存在。可能的返回值包括: 状态值| 含义 ---|--- `0`| 全局变量未定义。 `1`| 全局变量存在并包含数据,但没有子代。请注意,空字符串(`“”`)可用作数据。 `10`| 全局变量有后代(包含指向子节点的向下指针),但本身不包含数据。对此类变量的任何直接引用都将导``错误。例如,如果`$data(^y)`返回`10`,则`SET x=^y`将产生``错误。 `11`| 全局变量既包含数据,又有后代(包含指向子节点的向下指针)。 # 检索全局变量节点的值 要获取存储在特定全局变量节点中的值,只需使用全局引用作为表达式: ```java SET color = ^Data("Color") ; assign to a local variable WRITE ^Data("Color") ; use as a command argument SET x=$LENGTH(^Data("Color")) ; use as a function parameter ``` ## `$GET`函数 还可以使用`$GET`函数获取全局节点的值: ```java SET mydata = $GET(^Data("Color")) ``` 这将检索指定节点的值(如果存在),如果该节点没有值,则返回空字符串(`“”`)。如果节点没有值,可以使用可选的第二个参数`$get`返回指定的默认值。 ## `WRITE`、`ZWRITE`和`ZZDUMP`命令 可以使用各种ObjectScript显示命令显示全局变量或全局变量子节点的内容。`WRITE`命令以字符串形式返回指定全局或子节点的值。`ZWRITE`命令返回全局变量的名称及其值,以及它的每个子代节点及其值。`ZZDUMP`命令以十六进制转储格式返回指定全局或子节点的值。
文章
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, 2024

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 在不断发展,下一次迭代将是将文本转换为音频的方法,以及其他一些新功能。如果您喜欢这篇文章,请记得点个 "赞"。
问题
liu bo · 七月 6, 2024

2016.2.3 ensemble portal进不去,IIS和terminal和studio都好使Server Availability Error,有人遇到过么?

csp.log日志是这个 解决了,原因是禁用了UnknownUser用户,参考这个: https://community.intersystems.com/post/debugging-%E2%80%9Cserver-availability-error%E2%80%9D-message-when-loading-web-application 赞学习能力👍!
文章
姚 鑫 · 四月 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中),所以你需要在每个独立实体中完全导入每个依赖。我希望这个问题能在未来的版本中得到解决,因为在我看来,这个功能的主要目标是减少模板,让事情变得更简单。 # 先写到这,谢谢大家!
文章
姚 鑫 · 六月 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 不支持使用进程私有全局名称作为锁名称;无论如何,都不需要这样的锁,因为根据定义,只有一个进程可以访问这样的全局。
文章
姚 鑫 · 十一月 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`位精度。 小数秒中有意义的数字的实际数目与平台有关。
文章
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 · 九月 17, 2022

IRIS镜像配置(3)

# 把数据库添加进Mirror 以往的经验里, 用户在把数据库添加到镜像时遇到过各种各样的问题,以致必须请求外部帮助才能解决。除了步骤本身比较繁琐,很大的原因是阅读文档不细致。还有一个,就是对英文水平不太高的用户,有些英文句式并不是很好懂,比如说,文档中有这一句其实非常关键: > If you attempt to add a new database to the mirror on a nonprimary member that was not created as a mirrored database on the primary, but rather added to the mirror after it was created, an error message notes this and you cannot complete the operation. 我用最好的翻译器DeepL翻译后的中文是: **如果你试图在一个非主要成员上向镜像添加一个新的数据库,而这个数据库并不是在主要成员上作为镜像数据库创建的,而是在创建后添加到镜像中的,那么就会出现错误信息提示,你无法完成操作。** 很讨厌的是它没用说明错误信息是什么,以致于很多用户, 当他们在Backup成员中把一个数据库添加到镜像时,遇到相关的错误时,没有把问题和这句话关联起来,这个错误提示是这样的: >“错误 #2105: 与成员 SERVERA/IRIS 中的相匹配的数据库 :mirror:AUGEST:DEMO 未被创建为镜像数据库”。 或者用英文, > ERROR #2105: Matching mirrored DB :mirror:AUGEST:DEMO in member SERVERA/IRIS was not created as mirrored DB 我来解释一下这句话,它说的是: ”嘿, 你在本机要添加的:mirror:AUGEST:DEMO数据库, 它在主镜像成员SERVERA/IRIS里, 未被创建为镜像数据库。“ 如果您看了我的解释, 还觉得莫名其秒,我相信您其实是没懂这个关键点: ​ **“一个数据库创建成镜像数据库,和创建成普通数据库后面后再添加到镜像里,它们是不同的。”** 关于这一点,其实文档也有说明,啰嗦,但说明了原因。直接上翻译: > 创建镜像数据库(即添加一个不含数据的新数据库)的过程与向镜像添加现有数据库的过程不同。作为镜像数据库创建的数据库上的Global操作从一开始就被记录在镜像Journal中,因此镜像可以访问它所需要的所有数据,以便在镜像成员之间同步数据库。但现有数据库在被添加到镜像之前的Global操作包含在非镜像Journal文件中,镜像不能访问这些文件。由于这个原因,一个现有的数据库在被添加到镜像后,必须在主故障转移成员上进行备份,并在备份故障转移成员和它要所在的任何异步成员上进行恢复。一旦这样做了,你必须激活并赶上数据库,使其与主数据库保持同步。 清楚了这个关键, 您才能理解为什么安装步骤分为下面的两个类型, - **创建新的镜像数据库** - **将已有的数据库加入镜像** > TIP: 另外,还有一个值得提醒的:只有用户自己的数据库可以被加入镜像。系统本身的数据库, 比如IRISSYS, IRISLIB, IRISTEMP等等,都不能加入镜像。早期有些版本可以,NOMORE! ## 创建新的镜像数据库 - 在**主镜像成员**的系统维护界面上,选择System Administration – Configuration – System Configuration – Local Databases , 选择Create New Database. **在数据库创建向导窗口,在“镜像数据库?”下拉菜框,选择'是‘(Yes)**。 SQL"页面, 确认表Persons同步到了所有的镜像成员。 ​ > 这里如果您遇到上面提到的“Error 2105“, 那就是这个数据库在Primary上先是创建成一般数据库,然后加入的镜像,那您应该按下面的步骤操作了。 > > 如果有人好奇:在Primary上的这种区别,Backup是怎么知道的,它不是还没加入到镜像吗? 故事是这样的: 镜像日志中同步的不是只有镜像数据库的数据的修改, 还包括IRISSYS, IRISAUDIT,等库的内容。NEWDB在主成员中是怎么加入到镜像的, IRISSYS里的Global Set是不一样的,而这个set, 是同步给备用成员backup的。 又一个没用的知识。 ## 将已有的数据库加入镜像 **已有的(Existing)数据库是指原本在主成员里按普通数据库创建的,然后加入镜像的数据库。** 这样的情况,哪怕同样名称,配置的数据库在其他成员上已经有了。能直接加入镜像吗?比如你主成员上有个User, 备用成员上也有,您能在主机, 备机直接把它们加入镜像吗? 答案是肯定不行。系统根本没法保证这两个库里面已有的数据是一样的。**您要在主成员上备份数据库,在其他成员恢复, 而恢复操作成功后,在其他成员上,这个数据库自动变成了“镜像数据库”**, 也就是加入了镜像。 这个同名的数据库要先在其他成员上创建。创建成普通数据库。如果其他成员上已经有了,也不用删除,就直接用主机的备份文件覆盖就好。 以下是详细的步骤: - 在主机的“系统>配置>本地数据库“页面, 点击**添加到镜像**按钮。然后在跳出窗口中选中您要添加的数据库,可以一次选多个。 数据库很大或者多个数据库同时加入是,可以选中”在后台运行“。通常这个添加动作是在秒级时候内完成的,无所谓是否后台运行。 - 到镜像监视器查看添加的结果。被添加的数据库状态这时候应该是"一般"(Normal) 。 - 到其他镜像成员的镜像监视器查看, 您会看到主机来的通知引发的提醒: - 在备机检查自己的数据库状态。如果没有DEMO或者USER数据库,那么创建它们,创建时下拉框”是否镜像?”选择否或者NO。之后在本地数据库列表中它们应该是这样,注意没有在镜像里。 - 在Primary做数据库的在线备份, 用于后面步骤里到其他成员上去做数据库恢复。 以下过程仅供参考: ```sh # 在主成员备份,并发送给备份成员serverb %SYS>do ^BACKUP 1) Backup 2) Restore ALL 3) Restore Selected or Renamed Directories 4) Edit/Display List of Directories for Backups 5) Abort Backup 6) Display Backup volume information 7) Monitor progress of backup or restore Option? 1 *** The time is: 2022-09-17 15:27:48 *** InterSystems IRIS Backup Utility -------------------------- What kind of backup: 1. Full backup of all in-use blocks 2. Incremental since last backup 3. Cumulative incremental since last full backup 4. Exit the backup program 1 => 1 Specify output device (type STOP to exit) Device: /isc/FullDBList_user.cbk => /isc/setmirror.cbk Backing up to device: /isc/setmirror.cbk Description: Backing up the following directories: /isc/data/demo/ /isc/iris/mgr/user/ Start the Backup (y/n)? => y Journal file switched to: /isc/jrnpri/MIRROR-AUGEST-20220917.011 Starting backup pass 1 Backing up /isc/data/demo/ at 09/17/2022 15:28:26 Copied 82 blocks in 0.004 seconds Finished this pass of copying /isc/data/demo/ Backing up /isc/iris/mgr/user/ at 09/17/2022 15:28:28 Copied 908 blocks in 0.475 seconds Finished this pass of copying /isc/iris/mgr/user/ Backup pass 1 complete at 09/17/2022 15:28:29 Starting backup pass 2 Backing up /isc/data/demo/ at 09/17/2022 15:28:31 Copied 2 blocks in 0.000 seconds Finished this pass of copying /isc/data/demo/ Backing up /isc/iris/mgr/user/ at 09/17/2022 15:28:33 Copied 2 blocks in 0.000 seconds Finished this pass of copying /isc/iris/mgr/user/ Backup pass 2 complete at 09/17/2022 15:28:33 Starting backup pass 3 Journal file '/isc/jrnpri/MIRROR-AUGEST-20220917.010' and the subsequent ones are required for recovery purpose if the backup were to be restored Journal marker set at offset 197572 of /isc/jrnpri/MIRROR-AUGEST-20220917.011 - This is the last pass - Suspending write daemon Backing up /isc/data/demo/ at 09/17/2022 15:28:35 Copied 2 blocks in 0.000 seconds Finished this pass of copying /isc/data/demo/ Backing up /isc/iris/mgr/user/ at 09/17/2022 15:28:35 Copied 2 blocks in 0.001 seconds Finished this pass of copying /isc/iris/mgr/user/ Backup pass 3 complete at 09/17/2022 15:28:35 ***FINISHED BACKUP*** Global references are enabled. Backup complete. 1) Backup 2) Restore ALL 3) Restore Selected or Renamed Directories 4) Edit/Display List of Directories for Backups 5) Abort Backup 6) Display Backup volume information 7) Monitor progress of backup or restore Option? %SYS>!scp /isc/setmirror.cbk root@172.16.58.102:/isc Enter passphrase for key '/root/.ssh/id_rsa': root@172.16.58.102's password: setmirror.cbk 100% 8448KB 49.4MB/s 00:00 %SYS> ``` - 在其他成员上恢复数据库,这里分两种情况: - 其他成员上没有这个数据库: 比如我的serverb没有DEMO数据库,要做的是:创建一个DEMO数据库,使用和servera一样的设置,除了**在下拉框“镜像数据库?“,回答”NO“** - 其他成员上有这个库,比如备机serverb里有User, 不用管它,下面我们就可以直接把它覆盖掉。 请参考下面的数据库恢复过程。 **提醒一点:不要使用第一个选项“All Directories", 该选项不能用其他机器的备份文件恢复本机。** ```sh # 在Backup成员serverb上执行,恢复用源文件拷贝自servera %SYS>do ^DBREST Cache DBREST Utility Restore database directories from a backup archive Restore: 1. All directories 2. Selected and/or renamed directories 3. Display backup volume information 4. Exit the restore program 1 => 2 Do you want to set switch 10 so that other processes will be prevented from running during the restore? Yes => Specify input file for volume 1 of backup 1 (Type STOP to exit) Device: /isc/setmirror.cbk This backup volume was created by: IRIS for UNIX (Red Hat Enterprise Linux 7 for x86-64) 2022.1 The volume label contains: Volume number 1 Volume backup SEP 17 2022 03:28PM Full Previous backup SEP 16 2022 09:11AM Full Last FULL backup SEP 16 2022 09:11AM Description Buffer Count 0 Mirror name AUGEST Failover Member SERVERA/IRIS Is this the backup you want to start restoring? Yes => This backup was made on the other mirror member. Limit restore to mirrored databases? yes For each database included in the backup file, you can: -- press RETURN to restore it to its original directory; -- type X, then press RETURN to skip it and not restore it at all. -- type a different directory name. It will be restored to the directory you specify. (If you specify a directory that already contains a database, the data it contains will be lost). /isc/data/demo/ (:mirror:AUGEST:DEMO) => /isc/iris/mgr/user/ (:mirror:AUGEST:USER) => Do you want to change this list of directories? No => Restore will overwrite the data in the old database. Confirm Restore? No => Yes ***Restoring /isc/data/demo/ at 15:47:09 82 blocks restored in 0.0 seconds for this pass, 82 total restored. Expanding /isc/iris/mgr/user/ ... Expanding /isc/iris/mgr/user/ from 1 MB to 654 MB ***Restoring /isc/iris/mgr/user/ at 15:47:12 908 blocks restored in 0.0 seconds for this pass, 908 total restored. ***Restoring /isc/data/demo/ at 15:47:12 2 blocks restored in 0.0 seconds for this pass, 84 total restored. ***Restoring /isc/iris/mgr/user/ at 15:47:12 2 blocks restored in 0.0 seconds for this pass, 910 total restored. ***Restoring /isc/data/demo/ at 15:47:12 2 blocks restored in 0.0 seconds for this pass, 86 total restored. ***Restoring /isc/iris/mgr/user/ at 15:47:12 2 blocks restored in 0.0 seconds for this pass, 912 total restored. Specify input file for volume 1 of backup following SEP 17 2022 03:28PM (Type STOP to exit) Device: Do you have any more backups to restore? Yes => no Mounting /isc/data/demo/ which is a mirrored DB /isc/data/demo/ ... (Mounted) Mounting /isc/iris/mgr/user/ which is a mirrored DB /isc/iris/mgr/user/ ... (Mounted) Journal records for mirrored DBs were restored successfully. %SYS> ``` - 检查数据库列表中的状态,注意它们已经成了AUGEST的镜像数据库了, **而且它们是只读模式**。 - 在serverb上查看镜像监视器,确认它们的状态是Dejournaling 后面您可以像上面提到的,在主机上操作数据, 确认数据修改同步给了备机。到此这部分工作才算结束。 > 如果只有外部备份文件: > > 按照文档上的说法,如果用外部备份在非主成员恢复,恢复后需要在镜像监视器的”镜像数据库列表里“点击"ACtiviate", 直到看到状态为Caaught up为至。请参考文档,我不是很清楚细节。 # 其他的镜像操作 这里我说说怎么删除镜像, 以及其他的一些常用操作的要点, 比如什么时候使用“SET NO FAILOVER”等等。 TO BE CONTINUED...