清除过滤器
文章
Michael Lei · 八月 18, 2022
Hi 社区的朋友们,大家好!
有时我们需要以编程方式自动将CSV数据从文件或者UR网址L导入到InterSystems IRIS。我们希望创建具有适当数据类型的类并导入数据。
我在Open Exchange上发布了一个模块csvgen,它正是这样做的。
如果你只需要将CSV文件导入IRIS,你可以这么做:
USER>do ##class(community.csvgen).Generate("/usr/data/titanic.csv",,"Data.Titanic")
Class name: Data.Titanic
Header: PassengerId INTEGER,Survived INTEGER,Pclass INTEGER,Name VARCHAR(250),Sex VARCHAR(250),Age INTEGER,SibSp INTEGER,Parch INTEGER,Ticket VARCHAR(250),Fare MONEY,Cabin VARCHAR(250),Embarked VARCHAR(250)
Records imported: 891
USER>
或者你的CSV文件在互联网上, 例如GitHub上面的新冠疫情数据 你可以这样获得数据:
USER>d ##class(community.csvgen).GenerateFromURL("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/05-29-2020.csv",",","Data.Covid19")
Class name: Data.Covid19
Header: FIPS INTEGER,Admin2 VARCHAR(250),Province_State VARCHAR(250),Country_Region VARCHAR(250),Last_Update DATE,Lat MONEY,Long_ DOUBLE,Confirmed INTEGER,Deaths INTEGER,Recovered INTEGER,Active INTEGER,Combined_Key VARCHAR(250),Incidence_Rate DOUBLE,Case-Fatality_Ratio DOUBLE
Records imported: 3522
USER>
Installation安装
从ZPM安装:
USER>zpm
zpm:USER>install csvgen
csvgen 模块是打包了 CSV2CLASS method .
ObjectiveScript 的profile还不是很理想,欢迎各种建议意见。谢谢!
公告
Claire Zheng · 十一月 22, 2021
大家好!
我们很高兴地向大家介绍我们开发者社区中文版的新版主——段海华( @Duan.Haihua)!
让我们以热烈的掌声欢迎段海华( @Duan.Haihua),以下是他的个人介绍。
段海华,东华医为利润中心副主管
以下是他的个人介绍 @Duan.Haihua:
– 我主持参加了多家医院的互联互通评审工作,并主持了互联互通成熟度测评(Health Information Connectivity Maturity Evaluation, HICME)工具的开发与维护。我有丰富的基于InterSystems产品和项目工作经验,对互联互通测评相关标准有较深刻的理解。
– 通过开发者社区,我希望能够帮助刚开始接触互联互通标准的开发者快速理解相应内容,分享互联互通测评过程中常见问题的解决方案,为开发者对系统标准化改造过程提供参考意见。
热烈欢迎!
感谢段海华的付出( @Duan.Haihua)!我们相信你会成为一名优秀的版主! 👏🏼 热烈欢迎👏!!!
文章
Michael Lei · 六月 1, 2022
InterSystems Production 监控是管理门户中的一个页面,用于显示当前运行的Production监控信息。我喜欢这个页面的样子,但这并不适合所有人。
2022年5月13日,我在开发者社区上看到这个帖子。
https://community.intersystems.com/post/creating-custom-monitoring-page
我同意马克的观点,Production监控很复杂。我想创建一个更漂亮干净的监控页面。
我开始着手制作一个利用类方法提供Production数据的 CSP(Cache Server Page)页面。
我与马克分享了我的第一次尝试。他根据自己的想法定制了这个页面。我喜欢他对页面的布局,使其在视觉上更有吸引力。我把他的设计整合到我的应用程序Production监控中。
我看到马克对业务服务的显示进行了过滤,只显示需要注意的服务。他在页面的底部添加了服务器的名称和它的镜像状态。
你可以在Open Exchange和当前的竞赛中找到我的Production监控器的应用。
https://openexchange.intersystems.com/package/production-monitor
文章
Frank Ma · 一月 5, 2022
本文译自 https://community.intersystems.com/post/using-sql-apache-hive-hadoop-big-data-repositories
大家好,
在使用Spark做Hadoop时,InterSystems IRIS有一个很好的连接器。但市场上也提供了大数据Hadoop访问的其他优秀替代方案-Aparche Hive。请看区别:
HIVE
SPARK
Hive是一个数据库,用类似于RDBMS数据库的表格形式存储数据。
Spark不是一个数据库,它是一个数据分析框架,可以在内存中对大至PB字节的大容量数据进行复杂的数据分析。
使用称作HiveQL的自己的SQL引擎,数据可以从Hive中抽取出来。只能使用SQLs来抽取数据。
Spark既能使用复杂SQLs(Complex SQLs)也能使用MapReduce机制进行数据分析。它支持Java, Scala 和Python写的分析框架。
Hive在Hadoop之上运行。
Spark没有自己专用的存储。实际上,它是从外部的分布式数据存储如运行在Hadoop和MongoDB上的Hive、HBase中抽取数据。
Hive是一个基于数据仓库技术的数据库
Spark更适合在内存中进行复杂和快速的数据分析以及对数据进行流式处理。
对于那些需要在可横向扩展的RDBMS数据库上运行数据仓库操作的应用来说,Hive是最适合的。
Spark最适合于那些要求比MapReduce机制更快地进行大数据分析的应用。
来源: https://dzone.com/articles/comparing-apache-hive-vs-spark
我做了一个PEX互操作性服务,可以让你在你的InterSystems IRIS应用内部使用Apache Hive。请试用如下步骤:
1. 在iris-hive-adapter 项目上做一个Git Clone:
$ git clone https://github.com/yurimarx/iris-hive-adapter.git
2. 在这个目录内打开terminal 并运行:
$ docker-compose build
3. 运行IRIS容器:
$ docker-compose up
4. 打开项目中的Hive Prouction,运行一个Hello样例): http://localhost:52773/csp/irisapp/EnsPortal.ProductionConfig.zen?PRODUCTION=dc.irishiveadapter.HiveProduction
5. 点击“开始”运行Production.
6. 现在我们来测试应用!
7. 运行你的REST客户端应用程序(比如Postman),在body部分使用项目的URLS和命令(使用POST请求):
7.1 在大数据中生成一个新的表:POST http://localhost:9980/?Type=DDL. 在BODY中: CREATE TABLE helloworld (消息字符串)
7.2 在表中插入: POST http://localhost:9980/?Type=DDL. 在BODY中: INSERT INTO helloworld VALUES ("hello")
7.3 T从表中得到结果清单: POST http://localhost:9980/?Type=DML. 在BODY中: SELECT * FROM helloworld (注意:这里的类型是)
现在,你有了2个在IRIS中使用大数据的选项:Hive 或者Spark。希望你喜欢。
文章
Michael Lei · 十二月 27, 2022
一个简单的生产配置,使 FHIR 交易捆绑包能够通过 Box 和 Dropbox 加载到 InterSystems® FHIR® 服务器中。使用包含的 MFT 连接组件和 14 行自定义业务流程,此生产配置会将您的交易捆绑包处理到 FHIR 资源,以便立即使用,就像哈利·波特的魔法一样。
我首先会展示该生产配置的短视频导览、MFT 连接以及 IRIS 上 Box 和 Dropbox 的 Oauth2 应用配置,接下来循序渐进地展示一些步骤,让您使用您喜欢的任何 MFT 供应商以及您选择的任何工作流程、桌面、API 或 Web 控制台拖放操作。
一些陷阱:
OAUTH2 回调需要使用 IRIS 的 SSL 端点来提供重定向… 最好在 Health Connect Cloud 上尝试一下!
Dropbox for Business 在基于团队的令牌方面面临挑战,个人 Dropbox 则运行良好。 这不是无法忍受的情形,但需要一些耐心。
配置 MFT 连接时,注意 Dropbox 的 baseurl 上的“/”(确保它存在)。
对于 Box 和 DropBox 的路径,MFT 出站适配器都需要具有结尾“/”。
现在,鉴于以上获奖的 OBS 支持的内容可能有不足之处,如果 InterSytems 文档还不够,下面是需要遵循的步骤。
步骤概览:
将 FHIRDrop 或 FHIRBox 应用创建到一个点,然后停止! (协作和倾听)
在您的 InterSystems FHIR 服务器、HealthConnect 或 I4H 上配置 MFT 连接。
完成您的 FHIRDrop 或 FHIRBox 应用,提供来自 MFT 连接的重定向 URL。
授权您的 MFT 连接。
构建您的生产配置。
正常拖放
创建 FHIRDrop 或 FHIRBox 应用
这里的想法是在每个 Box 和 Dropbox 开发者控制台中“开始”您的应用配置,这会让您看到客户端 ID 和客户端密码,然后让选项卡挂起并移动到 IRIS MFT 连接。
(协作和倾听)只需收集您的客户端 ID 和客户端密码,挂起浏览器选项卡,然后继续:
配置 MFT 连接
基 URL:https://api.box.com/2.0
基 URL:https://api.dropboxapi.com/2/(注意结尾斜杠)
完成应用注册
现在,回到应用注册并完成应用。确保插入上述步骤中的重定向 URL,并添加与 file.read、file.write 有关的范围。
授权您的托管文件传输连接
回到您的托管文件传输连接并通过调用“Get Access Token”(获取访问令牌)来“授权”连接。
构建您的生产配置
生产配置
以下是自定义业务流程,生产配置的源代码:https://gitlab.com/isc_cloud/fhir-drop-fhir-box-mft-2-fhir
正常拖放!
现在,获取 FHIR!
![图片](/sites/default/files/inline/images/gar.png)
文章
Louis Lu · 六月 12, 2023
文章相关视频参见Synthea生成FHIR测试数据,以及FHIR服务器加载FHIR资源文件
1. 什么是Synthea
Synthea是一个开源软件包,可以模拟生成患者就诊数据。他的github地址在这里。
生成的就诊模版从最初的模拟美国前十种常见病、前十种慢性病到现今超过90种不同的模型。详细模型参见这里。
基于当前版本,Synthea的特性包括:
从出生到死亡的全生命周期
可配置的人口统计学信息(默认为美国马萨诸塞州人口普查数据)
模块化规则系统
插入通用模块
用于附加功能的自定义 Java 规则模块
主要医疗事件就诊、急诊室就诊和症状驱动的就诊
症状、 过敏、药品、 疫苗接种、观察/生命体征、实验室、处置、 护理计划
支持格式
HL7 FHIR(R4、STU3 v3.0.1 和 DSTU2 v1.0.2)
ndjson 格式的批量 FHIR(设置 exporter.fhir.bulk_data = true 以激活)
C-CDA (设置 exporter.ccda.export = true 以激活)
CSV (设置 exporter.csv.export = true 以激活)
CPCDS (设置 exporter.cpcds.export = true 以激活)
使用Graphviz可视化呈现规则和疾病模块
支持的参数可见下图
比如 -p 5 生成5条测试数据
-g M 生成男性测试数据
-a 60-65 生成年龄在60-65周岁患者测试数据
2. 使用Synthea 生成测试数据
为了方便使用,也将该软件做成了docker,所以你可以简单的执行下面命令行
docker run --rm -v $PWD/output:/output --name synthea-docker intersystemsdc/irisdemo-base-synthea:version-1.3.4 -p 5
该命令会在当前路径的output文件夹下生成5条患者符合FHIR标准的就诊数据,数据相关摘要信息如下面终端输出:
3. 加载生成的 FHIR 数据至 InterSystems IRIS for Health
生成完FHIR数据后,需要加载到FHIR服务器(FHIR资源仓库)中。
我们在输出目录下可以看到生成7条json数据,其中5条患者就诊相关,1条就诊医院信息,一条参与者(就诊医生)信息。
在InterSystems IRIS for health中可以方便的使用DataLoader类中的方法,批量加载FHIR资源数据,进入FHIR 资源仓库命名空间后执行:
zw ##class(HS.FHIRServer.Tools.DataLoader).SubmitResourceFiles("/external/fhir/","FHIRServer","/fhir/r4")
该方法中的第一个参数是fhir资源文件路径; 第二个参数服务类型,这里一般是FHIRServer; 第三个参数FHIRServer的service名称。
执行后显示如下:
之后我们可以进入管理门户,或者使用SQL客户端查询相关存储表,表明数据被正确导入
文章
Jingwei Wang · 九月 20, 2022
在虚拟化环境中使用镜像,构成镜像的InterSystems IRIS实例被安装在虚拟主机上,创造了一个混合的高可用性解决方案,将镜像的优点与虚拟化的优点结合起来。镜像通过自动故障切换对计划内或计划外的故障提供即时响应,而虚拟化HA软件在计划外的机器或操作系统故障后自动重新启动承载镜像成员的虚拟机。这允许失败的成员迅速重新加入镜像,充当备份(或在必要时作为主机)。
当镜像被配置在虚拟化环境中时,请参考以下建议:
故障转移成员的虚拟主机和备机不可以配置在同一台物理机上。
为了避免单点存储故障,故障转移成员上的InterSystems IRIS实例所使用的存储应永久隔离在不同磁盘组或存储阵列的独立数据存储中。
在虚拟化平台层面上进行的一些操作,如备份或迁移,可能会导致故障转移成员长时间没有反应,从而导致不需要的故障转移或不理想的警报频率。为了解决这个问题,你可以增加QoS超时设置。
在进行导致故障转移成员连接中断的计划性维护操作时,你可以暂时停止备份上的镜像,以避免不必要的故障转移和警报。
在镜像成员上必须非常谨慎地使用快照管理,因为将一个成员恢复到早期的快照,既会删除该成员的最新状态(例如,自拍摄快照以来,该成员可能已经从主机变为备机),也会删除其他成员仍然拥有的日志数据。 使用虚拟机备份恢复镜像时请特别注意:
被恢复到早期快照的故障转移成员只能从power-off 状态下恢复;从power-on 状态下恢复会造成两个故障转移成员同时作为主机的可能性。
如果被恢复到早期快照的故障转移成员在没有获得自快照以来创建的所有日志数据的情况下成为主要成员--例如,因为它被迫成为主要成员--所有其他镜像成员必须被重建(如需重建镜像,请联系WRC)。
文章
Louis Lu · 六月 10
列式存储是 InterSystems IRIS 提供的一项较新的技术。与传统的基于行的存储不同,它通过将数据存储在列而不是行中来优化查询处理,从而实现更快的访问和检索相关信息。
下面是使用SQL创建此类表的例子
CREATE TABLE table (column1 type1, column2 type2, column3 type3) WITH STORAGETYPE = COLUMNAR -- ex 1
CREATE TABLE table (column1 type1, column2 type2, column3 type3 WITH STORAGETYPE = COLUMNAR) -- ex 2
我们知道InterSystems IRIS 是支持多模型的DMBS, 它可以无缝的通过关系型或对象的方式访问通一数据,
我们使用下面的方式在使用Object Script类定义的时候定义列存储:
1. 如果你想对类中的所有属性都定义为列存储,则直接通过在类中添加parameter 的方式实现:
Parameter STORAGEDEFAULT = "columnar"
如前面的例子,我们使用object script定义就会是这样:
Class Post.Address Extends %Persistent [Final]
{
Parameter STORAGEDEFAULT = "columnar";
Parameter USEEXTENTSET=1;
Property City As %String(MAXLEN = 12);
Property ZIP As %String(MAXLEN = 9);
Property Country As %String(MAXLEN = 12);
}
其中 Parameter STORAGEEDFAULT = "columnar" 告诉系统,所有数据都以列的方式保存(意味着每一列都有其自己的global节点)
Parameter USEEXTENTSET = 1 告诉系统,为了生成的效率,hashed 的global 使用短名称命名。
你可以将任意表定义为使用列存储。但是将列存作为默认存储模式的表,必须添加Final 或 NoExtent 的类关键字,也就是不能再被继承。
2. 也可以仅对表中的某些字段定义为使用列式存储,要实现此种设计进需要在属性上添加 STORAGEDEFAULT = "columnar"
Class Post.Address Extends %Persistent
{
Parameter STORAGEDEFAULT = "row";
Parameter USEEXTENTSET=1;
Property City As %String(MAXLEN = 12);
Property ZIP As %String(MAXLEN = 9);
Property Country As %String(MAXLEN = 12, STORAGEDEFAULT = "columnar");
}
这个例子中的City 和 ZIP 仍使用行式存储,也就是数据会存储在global ^Post.AddressD 中,而Country会以列式的方式存储在不同的global节点中。
在这个例子中,使用列式存储会是一个更好的方法,因为如果我们有一个包含不同国家不同城市的数据库,国家的数目是有限的,而城市的数目则不是。
3. 还可以对现有行存储的类,针对某些字段添加列式索引
Class Sample.BankTransaction Extends %Persistent [ DdlAllowed ]
{
// Line below is optional
Parameter STORAGEDEFAULT = "row";
Property Amount As %Numeric(SCALE = 2);
Index AmountIndex On Amount [ type = columnar ];
.
.
.
}
在这个例子中,金额这样的数字类型字段,使用行式存储,但对其建立列式索引,会极大提高针对该字段的统计类sql查询效率,比如SUM、Avg等。
文章
姚 鑫 · 六月 9, 2021
# 第二章 从对象写入XML输出
本章介绍如何从InterSystems IRIS对象生成XML输出。
# 创建XML编写器概述
InterSystems IRIS提供了用于为InterSystems IRIS对象生成`XML`输出的工具。可以指定XML投影的详细信息,如将对象投影到`XML`中所述。然后创建一个`Writer`方法,该方法指定`XML`输出的整体结构:字符编码、对象的显示顺序、是否包括处理指令等。
基本要求如下:
- 如果需要特定对象的输出,则该对象的类定义必须扩展`%XML.Adaptor`。除了少数例外,该对象引用的类还必须扩展`%XML.Adaptor`。
- 输出方法必须创建`%XML.Writer`的实例,然后使用该实例的方法。
下面的终端会话显示了一个简单的示例,在该示例中,我们访问启用了XML的对象并为其生成输出:
```java
/// d ##class(Sample.Person).Populate(100)
/// w ##class(PHA.TEST.Xml).Obj2Xml(1)
ClassMethod Obj2Xml(ID)
{
s obj = ##class(Sample.Person).%OpenId(ID)
s xml = ##class(%XML.Writer).%New()
s xml.Indent=1
s status = xml.RootObject(obj)
q ""
}
```
```java
DHC-APP>w ##class(PHA.TEST.Xml).Obj2Xml(1)
yaoxin
111-11-1117
1990-04-25
889 Clinton Drive
St Louis
WI
78672
9619 Ash Avenue
Ukiah
AL
56589
濮氶懌
111-11-1115
Red
Orange
Yellow
Green
Red
Orange
Yellow
31
```
# 创建输出方法
输出方法按照指定的顺序逐段构造一个XML文档。输出方法的整体结构取决于需要输出完整的XML文档,还是仅仅输出一个片段。
## 输出方法的整体结构
方法应按以下顺序执行以下部分或全部操作:
1. 如果使用的对象可能无效,请调用该对象的`%ValidateObject()`方法并检查返回的状态。如果对象无效,则XML也将无效。
**`%XML.Writer` 在导出对象之前不会对其进行验证。这意味着,如果刚刚创建了一个对象,但尚未对其进行验证,则该对象(以及XML)可能是无效的(例如,因为缺少必需的属性)。**
2. 创建`%XML.Writer`类的实例,并根据需要设置其属性。
特别是,需要设置以下属性:
- `Indent` 缩进-控制输出是在缩进和换行中生成(如果缩进等于1),还是作为单个长行生成(如果缩进等于0)。后者是默认设置。
- `IndentChars` 缩进字符-指定用于缩进的字符。默认值为两个空格的字符串。如果缩进为0,则此属性无效。
- `Charset` 字符集-指定要使用的字符集。
为了提高可读性,本文档中的示例使用缩进等于1。
3. 指定输出目标。
默认情况下,输出写入当前设备。要指定输出目标,请在开始编写文档之前调用以下方法之一:
- `OutputToDevice()`-将输出定向到当前设备。
- `OutputToFile()`-将输出定向到指定文件。可以指定绝对路径或相对路径。请注意,该目录路径必须已经存在。
- `OutputToString()`-将输出定向到字符串。稍后,可以使用另一种方法来检索此字符串。
- `OutputToStream()`-将输出定向到指定的流。
4. 启动文档。可以使用`StartDocument()`方法。请注意,如果尚未通过`StartDocument()`启动文档,则以下方法会隐式启动文档:`Write()`、`WriteDocType()`、`RootElement()`、`WriteComment()`和`WriteProcessingInstruction()`。
5. 可以选择写入文档的序言行。可以使用以下方法:
- `WriteDocType()` - 编写DOCTYPE声明。
- `WriteProcessingInstructions()`-编写处理指令。
6. 可以选择指定默认命名空间。编写器将其用于没有定义的XML命名空间的类。
7. 可以选择将命名空间声明添加到根元素。为此,可以在启动根元素之前调用几个实用程序方法。
8. 启动文档的根元素。详细信息取决于该文档的根元素是否对应于InterSystems IRIS对象。有两种可能性:
- 根元素可能直接对应于InterSystems IRIS对象。如果要为单个对象生成输出,通常会出现这种情况。
在本例中,使用`RootObject()`方法,该方法将指定的启用XML的对象作为根元素写入。
- 根元素可能只是一组元素的包装器,而这些元素是InterSystems IRIS对象。
在本例中,使用`RootElement()`方法,该方法插入具有指定名称的根级元素。
9. 如果使用`RootElement()`方法,请调用方法来为根元素内的一个或多个元素生成输出。可以按照选择的任何顺序或逻辑在根元素中编写任何元素。有几种方法可以编写单个元素,并且可以结合使用这些技术:
- 可以使用`object()`方法,该方法写入启用XML的对象。可以指定此元素的名称,也可以使用由对象定义的默认值。
- 可以使用`element()`方法,该方法使用提供的名称写入元素的开始标记。然后,可以使用`WriteAttribute()`、`WriteChars()`、`WriteCData()`等方法编写内容、属性和子元素。子元素可以是另一个`Element()`,也可以是`Object()`。使用`EndElement()`方法指示元素的结束。
- 可以使用`%XML.Element`并手动构造元素。
10. 如果使用的是`RootElement()`方法,请调用`EndRootElement()`方法。此方法关闭文档的根元素,并根据需要减少缩进(如果有)。
11. 如果文档是从`StartDocument()`开始的,请调用`EndDocument()`方法关闭文档。
12. 如果将输出定向到字符串,请使用`GetXMLString()`方法检索该字符串。
还有许多其他可能的组织,但请注意,某些方法只能在某些上下文中调用。具体地说,一旦开始一个文档,在结束第一个文档之前,不能开始另一个文档。如果这样做,`Writer`方法将返回以下状态:
```java
#6275: Cannot output a new XML document or change %XML.Writer properties
until the current document is completed.
#6275:在当前文档完成之前,无法输出新的XML文档或更改%XML。Writer属性。
```
`StartDocument()`方法的作用是:显式启动文档。如前所述,其他方法隐式启动文档:`write()`、`WriteDocType()`、`RootElement()`、`WriteComment()`和`WriteProcessingInstruction()`。
注意:这里描述的方法旨在使够向XML文档写入特定的单元,但在某些情况下,可能需要更多的控制。在`%XML.Writer`提供了一个额外的方法`Write()`,可以使用该方法将任意字符串写入输出中的任何位置。
此外,还可以使用`Reset()`方法重新初始化编写器属性和输出方法。如果已经生成了一个XML文档,并且希望在不创建新的编写器实例的情况下生成另一个文档,这将非常有用。
## 错误检查
**%XML.Writer的大多数方法都会返回状态。应该在每个步骤之后检查状态,并在适当的情况下退出。**
## 插入注释行
如前所述,使用`WriteComment()`方法插入注释行。可以在文档中的任何位置使用此方法。如果尚未启动XML文档,此方法将隐式启动文档。
## 示例
```java
/// w ##class(PHA.TEST.Xml).Write()
ClassMethod Write() As %Status
{
s obj = ##class(Sample.Person).%OpenId(1)
set writer=##class(%XML.Writer).%New()
set writer.Indent=1
set status=writer.OutputToDevice()
if $$$ISERR(status) {
do $System.Status.DisplayError(status)
quit $$$ERROR($$$GeneralError, "输出目标无效")
}
set status=writer.RootObject(obj)
if $$$ISERR(status) {
do $System.Status.DisplayError(status)
quit $$$ERROR($$$GeneralError, "写入根对象时出错")
}
quit status
}
```
请注意,此方法使用`OutputToDevice()`方法将输出定向到当前设备(默认目标)。这不是必需的,但仅用于演示目的。
当然,输出取决于所使用的类,但可能如下所示:
```java
DHC-APP>w ##class(PHA.TEST.Xml).Write()
xiaoli
111-11-1111
test
2662
1
```
## 有关缩进选项的详细信息
如前所述,可以使用编写器的缩进属性来获取包含附加换行符的输出,以获得更好的可读性。此格式没有正式规范。本节介绍`%XML.Writer`使用的规则。如果缩进等于`1`:
- 任何只包含空格字符的元素都会转换为空元素。
- 每个元素都放在自己的行上。
- 如果某个元素是前一个元素的子元素,则该元素相对于该父元素缩进。缩进由`IndentChars`属性确定,该属性默认为两个空格。
文章
Kelly Huang · 七月 19, 2023
在之前的文章中,我们已经了解了如何配置和自定义我们的 EMPI,我们已经了解了如何通过 HL7 消息传递将新患者纳入我们的系统中,但当然,并不是所有的东西都是 HL7 v.2!我们如何配置 EMPI 实例以使用 FHIR 消息传递?
什么是FHIR?
对于那些不太熟悉 FHIR 这个术语的人,只需指出它们是Fast Healthcare Interoperability Resource的首字母缩写即可。 FHIR是HL7制定的医疗保健互操作性标准,基于JSON格式和REST通信,建立了一系列不同类型信息(患者数据、医院中心、诊断、医疗预约......)的“资源”。您可以在他们的官方页面上查看所有这些资源
InterSystems 和 FHIR
从 InterSystems 中,我们了解到 FHIR 为医疗保健互操作性领域提供的实用程序,因此我们拥有广泛的产品和功能,使我们能够充分利用并发挥其全部潜力:
InterSystems FHIR 服务器允许存储和管理 FHIR 资源。
InterSystems FHIR SQL Builder使用户可以通过 SQL 查询使用 FHIR 存储库中存储的所有信息。
InterSystems FHIR 适配器允许在我们的产品中发送和接收 FHIR 消息。
好吧,在我们的文章中,我们将利用适配器提供的功能来接收 FHIR 消息。
EMPI配置
FHIR 适配器安装
我们首先回顾一下独立 EMPI 安装中有哪些服务:
正如您所看到的,在独立模式下安装 EMPI 后,创建了一个注册表服务,其中包含配置 EMPI 所需的所有选项,以及我们称为 HSPIDATA 的命名空间,其中我们有一个产品来管理互操作性功能,需要。
对于我们的案例,我们创建了一个名为 WEBINAR 的新命名空间,我们将在其中部署正常的互操作性生产。我们将在此命名空间中安装FHIR 互操作性适配器,此安装将发布一个 Web 应用程序,我们将向该应用程序发送带有 FHIR 消息的 REST 调用。为此,我们将从终端执行以下命令:
zn "WEBINAR" set status = ##class (HS.FHIRServer.Installer).InteropAdapterConfig( "/csp/healthshare/webinar/fhir/r4" )
安装后,我们将检查从管理门户创建的新应用程序:
让我们在 WEBINAR 命名空间中检查我们的生产:
默认创建了两个新的业务组件:
InteropService :在我们的生产中,我们直接使用其类 HS.FHIRServer.Interop.Service 的名称,它将是负责接收发送到我们端点的 FHIR 消息的业务服务。 /csp/healthshare/网络研讨会/fhir/r4
InteropOperation :我们已将此名称更改为 HS.FHIRServer.Interop.Operation。对于我们的示例,我们忽略了此业务操作,因为我们将通过 TCP 将 FHIR 消息的信息发送到我们的 EMPI 生产
创建业务组件来管理 FHIR 消息传递
很好,我们已经启用了业务服务来接收 FHIR 消息,现在我们感兴趣的是从消息中提取信息并将其发送到我们的 EMPI 生产。让我们看一下 HS.FHIRServer.Interop.Service 将接收的消息类型:
Include HS.FHIRServer /// FHIRServer REST Business Service Class HS.FHIRServer.Interop.Service Extends (Ens.BusinessService, HS.HC.Util.Trace.Helper) { Parameter SETTINGS = "TargetConfigName:Basic:selector?context={Ens.ContextSearch/ProductionItems?targets=1&productionName=@productionId},Timeout:Basic" ; /// Configuration item to which to send inbound messages. Property TargetConfigName As Ens.DataType.ConfigName [ InitialExpression = "HS.FHIRServer.Interop.Operation" ] ; /// Timeout for dispatch (so we don't hold up the HTTP service too long or hang up a production shutdown). Property Timeout As %Integer [ InitialExpression = 25 ] ; /// Process an incoming message into the production; dispatch it to the configured target. /// The Interoperability contract requires that errors be returned as %Status here. Method OnProcessInput(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status
该消息的类型为 HS.FHIRServer.Interop.Request,如果我们查阅文档,我们将看到它有一个名为QuickStreamId的关联属性,我们将使用该属性来提取关联的 FHIR 消息。为此,我们将业务服务中收到的消息重定向到业务流程Webinar.BP.FHIRToHubRequest
该业务流程仅具有将消息重定向到负责通过 TCP 将消息发送到 EMPI 生产的业务操作FromFHIRToMPI的功能。
Method OnRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { Set tSC = ..SendRequestSync ( "FromFHIRToMPI" , pRequest, .pResponse) Quit $$$OK }
让我们看一下Webinar.BO.ToMPI类,并解释它如何从 FHIR 获取消息并将其转换为我们将发送到 EMPI 生产的字符串:
Include HS.FHIRServer Class Webinar.BO.ToMPI Extends Ens.BusinessOperation { Parameter ADAPTER = "EnsLib.TCP.CountedOutboundAdapter" ; Property Adapter As EnsLib.TCP.CountedOutboundAdapter ; Parameter INVOCATION = "Queue" ; Parameter SETTINGS = "FHIRMetadataSet::selector?context={HS.FHIRServer.Util.ContextSearch/FHIRMetadataSets}" ; /// FHIR Metadata Set. These are defined in HS_FHIRServer.FHIRMetadataSet. Property FHIRMetadataSet As %String (MAXLEN = 256 ) ; Method SendFHIRRequest(pRequest As HS.FHIRServer.Interop.Request, Output pResponse As HS.FHIRServer.Interop.Response) As %Status { // Get version of FHIR message configured Set tFHIRMetadataSetKey = $ZStrip ( $Piece ( ..FHIRMetadataSet , "/" , 1 ), "<>W" ) // We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'= "" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class (HS.SDA3.QuickStream). %OpenId (pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'= "" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError ( ##class (HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat= "json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat= "xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj (tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (( $IsObject (pRequest.Request.Json))&&(pRequest.Request.Json. %GetIterator (). %GetNext ())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj (pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus ( $$$ERROR ( $$$GeneralError , "FHIR interop request message missing FHIR content" )) } // Transform Dynamic Object to string set tRequest = tDynObj. %ToJSON () // Send message to EMPI production Set tSC= ..Adapter .SendMessageString(tRequest,.tResponse) $$$ThrowOnError (pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC } ClassMethod GetDynObj(stream As %Stream.Object , fhirVersion As %String , fhirFormat As %String ) As %DynamicObject { set schema = ##class (HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}. %FromJSON (stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class (HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj } XData MessageMap { <MapItems> <MapItem MessageType= "HS.FHIRServer.Interop.Request" > <Method>SendFHIRRequest</Method> </MapItem> </MapItems> } }
让我们一步步分析我们的类:
第一点是了解我们将在业务运营选项中配置的 FHIR 消息的版本。
Set tFHIRMetadataSetKey = $ZStrip ( $Piece ( ..FHIRMetadataSet , "/" , 1 ), "<>W" )
我们恢复正在处理的 FHIR 消息的类型,在我们的例子中它将是 R4。
接下来我们提取FHIR消息的内容:
// We can have our FHIR message saved as a QuickStream or a Dynamic Object inside the json property of the request If pRequest.QuickStreamId'= "" { // Recover QuickStream of the FHIR message Set tQuickStream = ##class (HS.SDA3.QuickStream). %OpenId (pRequest.QuickStreamId) // Checking if message is in JSON format or XML If pRequest.Request.RequestFormatCode'= "" { Set tFHIRFormat = pRequest.Request.RequestFormatCode } Else { $$$ThrowOnError ( ##class (HS.HC.Util).GetFormatFromData(tQuickStream, .tFHIRFormat)) Do tQuickStream.Rewind() If tFHIRFormat= "json" { Set tFHIRFormat = $$$FHIRContentCodeJSON } ElseIf tFHIRFormat= "xml" { Set tFHIRFormat = $$$FHIRContentCodeXML } } // Transform QuickStream to DynamicObject Set tDynObj = ..GetDynObj (tQuickStream, tFHIRMetadataSetKey, tFHIRFormat) } ElseIf (( $IsObject (pRequest.Request.Json))&&(pRequest.Request.Json. %GetIterator (). %GetNext ())) { // Could have Json %DynamicObject if this host is called InProc. Set tDynObj = ..GetDynObj (pRequest.Request.Json, tFHIRMetadataSetKey) } Else { $$$ThrowStatus ( $$$ERROR ( $$$GeneralError , "FHIR interop request message missing FHIR content" )) }
我们从 FHIR 消息中获取 %DynamicObject:
ClassMethod GetDynObj(stream As %Stream.Object , fhirVersion As %String , fhirFormat As %String ) As %DynamicObject { set schema = ##class (HS.FHIRServer.Schema).LoadSchema(fhirVersion) if fhirFormat = $$$FHIRContentCodeJSON { set dynObj = {}. %FromJSON (stream) } elseif fhirFormat = $$$FHIRContentCodeXML { set dynObj = ##class (HS.FHIRServer.Util.XMLToJSON).XMLToJSON(stream, schema) } Quit dynObj }
最后,我们将其转换为字符串,并通过 TCP 将其发送到我们的 EMPI 生产,向向我们发送原始消息的系统返回 200:
// Transform Dynamic Object to string set tRequest = tDynObj. %ToJSON () // Send message to EMPI production Set tSC= ..Adapter .SendMessageString(tRequest,.tResponse) $$$ThrowOnError (pRequest.NewResponse(.pResponse)) //Set pResponse.Response = ##class(HS.FHIRServer.API.Data.Response).%New() Set pResponse.Response.ResponseFormatCode = pRequest.Request.ResponseFormatCode set pResponse.Response.Status = 200 set pResponse.ContentType = "text/plain" Quit tSC
好吧,我们已经将消息发送到我们的 EMPI 生产中。让我们看一下示例消息的跟踪:
在下一篇文章中,我们将检索提交的字符串,将其转换回 %DynamicObject 并向 EMPI 提供数据以使用新数据执行注册/更新操作。
希望这些内容对你有用!
致敬原创作者 @Luis Angel
文章
姚 鑫 · 七月 6, 2021
# 第二十九章 从XML架构生成类
Studio提供了一个向导,该向导读取XML模式(从文件或URL),并生成一组支持XML的类,这些类对应于模式中定义的类型。
所有的类都扩展`%XML.Adaptor`。
指定一个包来包含类,以及控制类定义细节的各种选项。
向导还可以作为类方法使用,也可以使用该类方法。
在内部,SOAP向导在读取WSDL文档并生成web客户端或web服务时使用此方法;
注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码,InterSystems IRIS将使用本书前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。
# 使用向导
要使用XML架构向导,请执行以下操作:
1. 选择 Tools > Add-Ins > XML Schema Wizard.
![image](/sites/default/files/inline/images/1_50.png)
2. 在第一个屏幕上,指定要使用的XML模式。
做以下其中一项:
- 对于模式文件Schema File,选择Browse 以选择XML模式文件。
- 对于URL,指定模式的URL。
![image](/sites/default/files/inline/images/2_30.png)
3. 选择Next。
下一个屏幕显示模式,以便可以验证选择了正确的模式。
![image](/sites/default/files/inline/images/3_24.png)
4. 可选择以下选项:
- 保留空类Keep Empty Classes,它指定是否保留没有属性的未使用的类。
如果选择此选项,则不会在向导结束时删除此类;
否则,将删除它们。
- “不创建数组属性”Create No Array Properties控制向导是否生成数组属性。
如果选择此选项,向导不会生成数组属性,而是生成另一个表单。
- 为可为空的元素生成XMLNIL属性参数,它控制向导是否为生成的类中适用的属性指定XMLNIL属性参数。
该选项适用于每个对应于用`nillable="true"`指定的XML元素的属性。
如果选择此选项,向导将向属性定义添加`XMLNIL=1`。
否则不添加该参数。
该参数的详细信息请参见将对象投影到XML中的“处理空字符串和空值”。
- 为可为空的元素生成`XMLNILNOOBJECT`属性参数,它控制向导是否为生成的类中适用的属性指定`XMLNILNOOBJECT`属性参数。
该选项适用于每个对应于用`nillable="true"`指定的XML元素的属性。
如果选择此选项,向导将向属性定义添加`XMLNILNOOBJECT=1`。
否则不添加该参数。
该参数的详细信息请参见将对象投影到XML中的“处理空字符串和空值”。
5. 选择Next。
下一个屏幕显示关于要生成的类的选项的一些基本信息。
6. 在这个屏幕上,指定以下选项:
- 如果希望向导编译生成的类,可以选择“编译生成的类”。
- 可选择“添加NAMESPACE类参数”来指定`NAMESPACE`参数。
在本例中,`NAMESPACE`被设置为模式中`targetNamespace`的值。
如果不设置此选项,则不指定`NAMESPACE`。
建议在所有情况下都选择这个选项,因为每个支持XML的类都应该分配给一个XML名称空间。
(但是,为了向后兼容,可以将此选项清除。)
- 如果希望生成的类是持久类,请选择Create persistent classes。然后类扩展`%Persistent`。
可以稍后在向导中针对各个类更改这一点。
- 如果生成持久类,可以选择如何处理由另一个` b`的``组成的`` a。当向导生成一个包含属性`a`的持久类时,该属性有三种可能的形式。
可以将其定义为对象列表、一对多关系(默认)或父子关系。
下表总结了这些选择:
在持久性类中为集合属性使用关系 |向多对关系添加索引 |使用父子关系|生成的属性A的形式
---|---|---|---
selected (default)| not selected| not selected| 无索引的一对多关系
selected (default) |selected| not selected| 在多侧与索引的一对多关系
selected (default) |如果选择Use parent-child relationship,则忽略此选项| selected| 父子关系
not selected| not selected| not selected |List of objects
此外,如果未选择使用父子关系,则可以选择将`%OnDelete`方法添加到类以级联删除。如果选择此选项,当向导生成类定义时,它会在这些类中包含`%OnDelete()`回调方法的实现。生成的`%OnDelete()`方法删除类引用的所有持久对象。如确实选择了使用父子关系,请不要选择此选项;父子关系已经提供了类似的逻辑。
注意:如果修改生成的类,请确保根据需要修改`%OnDelete()`回调方法。
如果生成持久类,向导可以向每个对象类型类添加临时属性,以便可以为对象投影InterSystems IRIS内部标识符。选项如下:
- None-如果选择此选项,向导不会添加此处描述的任何属性。
- Use Id -如果选择此选项,向导将向每个对象类型类添加以下属性:
```
Property %identity As %XML.Id (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient];
```
- Use Oid -如果选择此选项,向导将向每个对象类型类添加以下属性:
```
Property %identity As %XML.Oid (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient];
```
- Use GUID-如果选择此选项,向导将向每个对象类型类添加以下属性:
```
Property %identity As %XML.GUID (XMLNAME="_identity", XMLPROJECTION="ATTRIBUTE") [Transient];
```
底部的表格列出了模式中的XML名称空间。在这里,指定包含该行中显示的XML名称空间的类的包。要执行此操作,请在程序包名字段中为该行指定程序包名。
7. 选择下一步。
8. 在下一个屏幕上,指定以下选项:
- Java Enabled - 如果选择此选项,则每个类都包括一个Java映射。
- Data Population数据填充-如果选择此选项,则除`%XML.Adaptor`外,每个类还继承会`%Populate`。
- SQL Column Order-如果选择此选项,每个属性将为`SqlColumnNumber`关键字指定一个值,以便属性在SQL中的顺序与它们在架构中的顺序相同。
- No Sequence Check-如果选中此选项,向导将生成的类中的`XMLSEQUENCE`参数设置为0。在某些情况下,如果XML文件的元素顺序与XML架构不同,则此选项非常有用。
默认情况下,`XMLSEQUENCE`参数在生成的类中设置为1。这可确保属性以与架构中相同的顺序包含在类定义中。
- XMLIGNORENULL-如果选择此选项,向导会将`XMLIGNORENULL=1`添加到类定义中。否则,它不会添加此参数。
- 将流用于二进制Use Streams for Binary - 如果选择此选项,向导将为`xsd:base64Binary`类型的任何元素生成`%Stream.GlobalBinary`类型的属性。如果清除此选项,则该属性的类型为`%xsd.base64Binary`。
请注意,向导将忽略`xsd:base64Binary`类型的任何属性。
- 在复选框下方,该表列出了向导将生成的类。对于每个类,确保适当地设置了`Extensions/Type`。在此,可以选择以下选项之一:
- 持久类`Persistent` -如果选择此选项,则类是持久性类。
- `Serial`-如果选择此选项,则类为序列类。
- `Registered Object`-如果选择此选项,则类为注册对象类。
所有生成的类还扩展`%XML.Adaptor`。
- 在表的右列中,为每个应编制索引的属性选择索引。
9. 选择Finish(完成)。
然后,向导将生成这些类,并在需要时编译它们。
对于这些类的属性,如果架构中相应元素的名称以下划线(_)开头,则属性名称以百分号(%)开头。
# 以编程方式生成类
XML架构向导也可用作`%XML.Utils.SchemaReader`类的`process()`方法。要使用此方法,请执行以下操作:
1. 创建`%XML.Utils.SchemaReader`的实例。
2. 可以选择设置实例的属性以控制其行为。
3. 可以选择创建InterSystems IRIS多维数组,以包含有关其他设置的信息。
4. 调用实例的`process()`方法:
```
method Process(LocationURL As %String,
Package As %String = "Test",
ByRef Features As %String) as %Status
```
- LocationURL必须是架构的URL或架构文件的名称(包括其完整路径)。
- Package是用于放置生成的类的包的名称。如果不指定程序包,InterSystems IRIS将使用服务名称作为程序包名称。
- Feature是在上一步中选择创建的多维数组。
# 每种XSD类型的默认IRIS数据类型
对于它生成的每个属性,XML架构向导会根据架构中指定的XSD类型自动使用适当的InterSystems IRIS数据类型类。下表列出了XSD类型和相应的InterSystems IRIS数据类型:
用于XML类型的InterSystems IRIS数据类型
源文档中的XSD类型 |生成的IRIS类中的数据类型
---|---
anyURI | %xsd.anyURI
base64Binary | `%xsd.base64Binary`或`%Stream.GlobalBinary`,具体取决于选择的选项。确定每个字符串是否可能超出字符串长度限制,如果可能,则将生成的属性从`%xsd.base64Binary`修改为适当的流类。)
boolean | `%Boolean`
byte | `%xsd.byte`
date | `%Date`
dateTime | `%TimeStamp`
decimal |`%Numeric`
double | `%xsd.double`
float | `%xsd.float`
hexBinary | `%xsd.hexBinary`
int |`%xsd.int`
integer |`%Integer`
long | `%Integer`
negativeInteger | `%xsd.negativeIntege`
nonNegativeInteger | `%xsd.nonNegativeInteger`
nonPositiveInteger |`%xsd.nonPositiveInteger`
positiveInteger | `%xsd.positiveInteger`
short | `%xsd.short`
string | `%String` (注意:责任确定每个字符串是否可能超出字符串长度限制,如果可能,则将生成的类型修改为适当的流类。)
time | `%Time`
unsignedByte | `%xsd.unsignedByte`
unsignedInt |`%xsd.unsignedInt`
unsignedLong | `%xsd.unsignedLong`
unsignedShort | `%xsd.unsignedShort`
no type given | `%String`
# 生成的属性的属性关键字
对于它生成的每个属性,XML架构向导还使用架构中的信息自动设置以下关键字:
- Description
- Required
- ReadOnly (如果相应的元素或属性是用固定属性定义的)
- InitialExpression (该值取自架构中的固定属性)
- Keywords related to relationships
# 生成的属性的参数
对于它生成的每个属性,XML架构向导会根据需要自动设置`XMLNAME`、`XMLPROJECTION`和所有其他与XML相关的参数。它还根据需要设置其他参数,如`MAXVAL`、`MINVAL`和`VALUELIST`。
# 调整为超长字符串生成的类
在极少数情况下,可能需要编辑生成的类来容纳超长的字符串或二进制值,超出字符串长度限制。
对于任何字符串类型,XML架构都不包含任何指示字符串长度的信息。XML架构向导将所有字符串值映射到InterSystems IRIS `%String`类,并将所有`base64Binary`值映射到`%xsd.base64Binary`类。这些选择可能不合适,具体取决于类要承载的数据。
在使用生成的类之前,应该执行以下操作:
- 检查生成的类,找到定义为`%string`或`%xsd.base64Binary`的属性。考虑将在其中使用这些类的上下文,特别是这些属性。
- 如果认为`%string`属性可能需要包含超出字符串长度限制的字符串,请将该属性重新定义为适当的字符流。同样,如果认为`%xsd.base64Binary`属性可能需要包含超过相同限制的字符串,请将该属性重新定义为适当的二进制流。
- 另请注意,对于类型为`%string`、`%xsd.string`和`%BINARY`的属性,默认情况下,`MAXLEN`属性参数为`50`个字符。可能需要指定更高的限制才能进行正确的验证。
(对于`%xsd.base64Binary`类型的属性,`MAXLEN`为`“”`,这意味着不会通过验证检查长度。但是,字符串长度限制确实适用。)
文章
姚 鑫 · 六月 25, 2021
# 第十八章 签署XML文档
本章介绍如何向XML文档添加数字签名。
# 关于数字签名文档
数字签名的XML文档包括一个或多个``元素,每个元素都是数字签名。
每个``元素对文档中的特定元素进行如下签名:
- 每个签名元素都有一个ID属性,该属性等于某个唯一值。例如:
```xml
```
- 一个``元素包含一个``元素,它指向该Id,如下所示:
```xml
```
``元素是由私钥签名的。此元素包括由签名机构签署的X.509证书。如果已签名文档的接收方信任此签名机构,则接收方可以验证证书,并使用包含的公钥验证签名。
注意: IRIS还支持一种变体,其中有签名的元素有一个名为ID的属性,而不是ID。
下面是一个示例,为了便于阅读,添加了空格:
```xml
Persephone MacMillan
1976-02-20
FHwW2U58bztLI4cIE/mp+nsBNZg=
MTha3zLoj8Tg content omitted
MIICnDCCAYQCAWUwDQYJ content omitted
```
要创建数字签名,可以使用类`%XML.Security.Signature`。
这是一个支持xml的类,它的投影是适当名称空间中的有效``元素。
# 创建数字签名XML文档
要创建数字签名的XML文档,请使用`%XML.Writer`为一个或多个适当定义的启用了XML的对象生成输出。
在为对象生成输出之前,必须创建所需的签名并将其写入对象,以便可以将信息写入目标。
## 签名的前提条件
在签署文档之前,必须至少创建一个IRIS凭据集。InterSystems IRIS凭据集是存储在系统管理器数据库中的以下信息集的别名:
- 包含公钥的证书。证书应由文档接收者信任的签名机构签名。
- 关联的私钥, IRIS在需要时使用,但从不发送。签名需要私钥。
- (可选)私钥的密码, IRIS在需要时使用私钥,但从不发送。可以加载私钥,也可以在运行时提供私钥。
## 启用XML的类的要求
启用XML的类必须包括以下内容:
- 投影为ID属性的特性。
- 至少一个类型为`%XML.Security`的属性。投影为``元素的签名。(一个XML文档可以包含多个``元素。)
考虑以下类:
```java
Class XMLEncryption.Simple Extends (%RegisteredObject, %XML.Adaptor)
{
Parameter NAMESPACE = "http://mynamespace";
Parameter XMLNAME = "Person";
Property Name As %String;
Property DOB As %String;
Property PersonId As %String(XMLNAME = "Id", XMLPROJECTION = "ATTRIBUTE");
Property MySig As %XML.Security.Signature(XMLNAME = "Signature");
}
```
## 生成和添加签名
要生成和添加数字签名,请执行以下步骤:
1. 可以选择包含`%soap.inc`包含文件,该文件定义可能需要使用的宏。
2. 创建`%SYS.X509Credentials`的实例在访问相应InterSystems IRIS凭据集。为此,调用`%SYS.X509Credentials`的`GetByAlias()`类方法。
```java
classmethod GetByAlias(alias As %String, pwd As %String) as %SYS.X509Credentials
```
- alias 别名是证书的别名。
- pwd 是私钥密码。仅当关联的私钥已加密并且在加载私钥文件时未加载密码时,才需要私钥密码。
若要运行此方法,必须以该凭据集的`OwnerList`中包含的用户身份登录,否则`OwnerList`必须为空。
3. 在使用给定凭据集创建 `%XML.Security.Signature`的实例。为此,请调用该类的`Createx509()`类方法:
```java
classmethod CreateX509(credentials As %SYS.X509Credentials, signatureOption As %Integer, referenceOption As %Integer) as %XML.Security.Signature
```
- `credentials` 凭据是刚刚创建`%SYS.X509Credentials`的实例。
- `signatureOption`是`$$$SOAPWSIncludeNone`(还有其他选项,但它们不适用于此方案)
- `referenceOption` 指定对符号元素的引用的性质。
这里使用的宏在`%soap.inc`中定义包括文件。
4. 获取ID属性的值,对于此签名将点的ID。此详细信息取决于启用XML对象的定义。
5. 创建`%XML.Security.Reference`的实例,指向该ID。为此,请调用该类的`Create()`类方法:
```java
ClassMethod Create(id As %String, algorithm As %String, prefixList As %String)
```
- `id`是该参考应该指向的ID。
- `algorithm` 算法应该是以下之一:
- `$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSexcc14`n — 使用此版本获取独占规范化。
- `$$$SOAPWSEnvelopedSignature` — 这相当于前面的选项。
- `$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSexcc14n` — 使用此版本进行包容性规范化。
6. 对于签名对象,调用`AddReference()`方法将此引用添加到签名:
```java
Method AddReference(reference As %XML.Security.Reference)
```
7. 更新启用XML的类的相应属性以包含签名。
```java
set object.MySig=signature
```
8. 创建`%XML.Document`的实例,该实例包含序列化为XML的启用了XML的对象。
这是必要的,因为签名必须包括有关签名文档的信息。
注意:本文档不包含空格。
9. 调用签名对象的`SignDocument()`方法:
```java
Method SignDocument(document As %XML.Document) As %Status
```
此方法的参数是刚刚创建的中`%XML.Document`的实例。`SignDocument()`方法使用该实例中的信息更新签名对象。
10. 使用`%XML.Writer`中为对象生成输出。
注意:生成的输出必须包含与签名中使用的文档相同的空格(或不包含空格)。签名包含文档的摘要,如果将编写器中的缩进属性设置为1,则摘要将与文档不匹配。
例如:
**放入到对应的实体类中,有一些属性需要替换**
```java
Method WriteSigned(filename As %String = "")
{
#Include %soap
//创建签名对象
set cred=##class(%SYS.X509Credentials).GetByAlias("servercred")
set parts=$$$SOAPWSIncludeNone
set ref=$$$KeyInfoX509Certificate
set signature=##class(%XML.Security.Signature).CreateX509(cred,parts,ref,.status)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
// 获取我们要签名的元素的ID属性;
set refid=$this.PersonId ; 此详细信息取决于类的结构
// 然后在签名对象中创建对该ID的引用
set algorithm=$$$SOAPWSEnvelopedSignature_","_$$$SOAPWSc14n
set reference=##class(%XML.Security.Reference).Create(refid,algorithm)
do signature.AddReference(reference)
//设置MySig属性,以便$this具有我们为其生成输出时所需的所有信息
set $this.MySig=signature ; 此详细信息取决于类的结构
//除了$this之外,我们还需要%XML.Document的一个实例,该实例包含序列化为XML的对象
set document=..GetXMLDoc($this)
//使用序列化的XML对象对文档进行签名,这将更新部分签名
set status=signature.SignDocument(document)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
// 写入对象的输出
set writer=##class(%XML.Writer).%New()
if (filename'="") {
set status=writer.OutputToFile(filename)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
}
do writer.RootObject($this)
}
```
前面的实例方法使用以下泛型类方法,该方法可以与任何启用了XML的对象一起使用:
```java
ClassMethod GetXMLDoc2(object) As %XML.Document
{
//步骤1-将对象作为XML写入流
set writer=##class(%XML.Writer).%New()
set stream=##class(%GlobalCharacterStream).%New()
set status=writer.OutputToStream(stream)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
set status=writer.RootObject(object)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
//步骤2-从流中提取%XML.Document
set status=##class(%XML.Document).GetDocumentFromStream(stream,.document)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
quit document
}
```
变体:引用中带有`URI=""`的数字签名
作为一种变体,签名的``元素可以具有`URI=""`,这是对包含签名的XML文档根节点的引用。
要通过以下方式创建数字签名:
1. 可以选择包含`%soap.inc`包含文件,该文件定义可能需要使用的宏。
2. 创建`%SYS.X509Credentials`的实例在访问相应InterSystems IRIS凭据集。为此,请调用`%SYS.X509Credentials`的`GetByAlias()`类方法,如前面的步骤所述。
3. 创建使用给定凭据集的`%XML.Security.Signature`的实例。为此,请调用该类的`CreateX509()`类方法,如前面的步骤所述。
4. 按如下方式创建`%XML.Security.X509Data`的实例:
```java
set valuetype=$$$KeyInfoX509SubjectName_","_$$$KeyInfoX509Certificate
set x509data=##class(%XML.Security.X509Data).Create(valuetype,cred)
```
其中,`cred`是`%SYS`的实例。
`x509credentials`在之前创建的新窗口中打开。
这些步骤创建了一个``元素,其中包含一个``元素和一个``元素。
5. 将``元素添加到签名的``元素中,方法如下:
```java
do signature.KeyInfo.KeyInfoClauseList.Insert(x509data)
```
其中签名是`%XML.Security`的实例。
`x509data`是`%XML.Security.X509Data`的实例。
6. 创建`%XML.Security`的实例。
参考如下:
```java
set algorithm=$$$SOAPWSEnvelopedSignature
set reference=##class(%XML.Security.Reference).Create("",algorithm)
```
7. 在步骤6(调用`AddReference()`)中继续上述步骤。
# 验证数字签名
对于收到的任何数字签名文档,都可以验证签名。不需要具有与文档内容匹配的启用XML的类。
## 验证签名的前提条件
若要验证数字签名,必须首先为签名者向InterSystems IRIS提供受信任的证书。如果InterSystems IRIS可以验证签名者的证书链(从签名者自己的证书到来自InterSystems IRIS信任的证书颁发机构(CA)的自签名证书),包括中间证书(如果有),则InterSystems IRIS可以验证签名。
## 验证签名
要验证数字签名的XML文档中的签名,请执行以下操作:
1. 创建`%XML.Reader`的实例并使用它打开文档。
2. 获取阅读器的`Document`属性。这是 `%XML.Document`的一个实例。包含作为`DOM的XML文档的文档
3. 使用阅读器的`correlation()`方法将``元素或元素与类`%XML.Security.Signature`关联起来。
例如:
```java
do reader.Correlate("Signature","%XML.Security.Signature")
```
4. 遍历文档以读取``元素或多个元素。为此,可以使用阅读器的`Next()`方法,该方法通过引用返回一个导入的对象(如果有的话)。
例如:
```java
if 'reader.Next(.isig,.status) {
write !,"Unable to import signature",!
do $system.OBJ.DisplayError(status)
quit
}
```
导入的对象是`%XML.Security.Signature`的实例
5. 调用导入签名的`ValidateDocument()`方法。该方法的参数必须是`%XML`的实例。先前检索到的文档。
```java
set status=isig.ValidateDocument(document)
```
例如:
```java
ClassMethod ValidateDoc(filename As %String)
{
set reader=##class(%XML.Reader).%New()
set status=reader.OpenFile(filename)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
set document=reader.Document
//获取 元素
//假设只有一个签名
do reader.Correlate("Signature","%XML.Security.Signature")
if 'reader.Next(.isig,.status) {
write !,"无法导入签名",!
do $system.OBJ.DisplayError(status)
quit
}
set status=isig.ValidateDocument(document)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
}
```
# 变体:引用ID的数字签名
在典型的情况下,``元素包含一个``元素,该元素指向文档中其他地方的唯一Id。
InterSystems IRIS还支持一种变体,其中``元素指向名为ID(而不是ID)的属性。
在这种变体中,需要额外的工作来签署文档和验证文档。
要对文档进行数字签名,请遵循“创建数字签名XML文档”中的步骤,并进行以下更改:
- 对于支持xml的类,包含一个作为ID属性而不是ID属性投影的属性。
- 在生成和添加签名时,调用`%XML`的`AddIDs()`方法。文档实例。
在获得序列化的XML文档之后,在调用签名对象的`SignDocument()`方法之前,执行此操作。
例如:
```java
//设置MySig属性,使$this在为其生成输出时拥有所需的所有信息
set $this.MySig=signature ; 这个细节取决于类的结构
//除了$this之外,我们还需要%XML的实例 包含序列化为XML的对象的文档
set document=..GetXMLDoc($this)
//***** 当签名引用ID属性时添加步骤 *****
do document.AddIDs()
//使用序列化的XML对象对文档进行签名,这更新了部分签名
set status=signature.SignDocument(document)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
```
- 当验证文档时,在调用`Correlate()`之前包含以下步骤:
1. 调用 `%XML.Document`的`AddIDs()`方法。
2. 调用XML阅读器的`Rewind()`方法。
例如:
```
set document=reader.Document
//添加签名引用ID属性时的步骤
do document.AddIDs()
do reader.Rewind()
//获取 元素
do reader.Correlate("Signature","%XML.Security.Signature")
```
文章
姚 鑫 · 四月 13, 2021
# 第二章 定义和构建索引(一)
# 概述
索引是由持久类维护的结构,InterSystems IRIS®数据平台可以使用它来优化查询和其他操作。
可以在表中的字段值或类中的相应属性上定义索引。(还可以在多个字段/属性的组合值上定义索引。)。无论是使用SQL字段和表语法还是类属性语法定义相同的索引,都会创建相同的索引。当定义了某些类型的字段(属性)时,InterSystems IRIS会自动定义索引。可以在存储数据或可以可靠派生数据的任何字段上定义附加索引。InterSystems IRIS提供了几种类型的索引。可以为同一字段(属性)定义多个索引,为不同的目的提供不同类型的索引。
无论是使用SQL字段和表语法,还是使用类属性语法,只要对数据库执行数据插入、更新或删除操作,InterSystems IRIS就会填充和维护索引(默认情况下)。可以覆盖此默认值(通过使用`%NOINDEX`关键字)来快速更改数据,然后作为单独的操作生成或重新生成相应的索引。可以在用数据填充表之前定义索引。还可以为已经填充了数据的表定义索引,然后作为单独的操作填充(构建)索引。
InterSystems IRIS在准备和执行SQL查询时使用可用的索引。**默认情况下,它选择使用哪些索引来优化查询性能。** 可以根据需要覆盖此默认值,以防止对特定查询或所有查询使用一个或多个索引。
## 索引属性
每个索引都有一个唯一的名称。此名称用于数据库管理目的(报告、索引构建、删除索引等)。**与其他SQL实体一样,索引同时具有SQL索引名和相应的索引属性名;这些名称在允许的字符、区分大小写和最大长度方面有所不同。如果使用`SQL CREATE INDEX`命令定义,系统将生成相应的索引属性名称。如果使用持久类定义进行定义,则`SqlName`关键字允许用户指定不同的SQL索引名(SQL映射名称)。** Management Portal SQL界面的Catalog Details显示每个索引的SQL索引名称(SQL映射名称)和相应的索引属性名称(索引名称)。
索引类型由两个索引类关键字`Type`和`Extent`定义。IRIS提供的索引类型包括:
- 标准索引(`Type = index`)——一个持久数组,它将索引值与包含该值的行的 RowID相关联。
**任何没有明确定义为位图索引、位片索引或区段索引的索引都是标准索引。**
- 位图索引(`Type = Bitmap`)——一种特殊的索引,使用一系列位字符串来表示与给定索引值对应的RowID值集;
InterSystems IRIS包括许多位图索引的性能优化。
- 位片索引(`Type = Bitslice`)——一种特殊的索引,能够非常快速地计算某些表达式,例如总和数和范围条件。
某些SQL查询自动使用位片索引。
- 区段索引(`Extent Indices`)——一个区段中所有对象的索引。
有关更多信息,请参阅类定义参考中的区段索引关键字页。
- 范围索引-范围中所有对象的索引。
**表(类)的最大索引数为400。**
## 存储类型和索引
这里描述的索引功能适用于存储在持久化类中的数据。
InterSystems SQL支持使用InterSystems IRIS默认存储结构存储的数据的索引功能:`%Storage.Persistent`(`%Storage.Persistent`映射类)。
InterSystems SQL还支持使用`%Storage.SQL`(`%Storage.SQL`映射的类)存储的数据的索引功能。可以使用函数索引类型为`%Storage.SQL`映射的类定义索引。索引的定义方式与使用默认存储的类中的索引相同,但有以下特殊注意事项:
- 如果`IdKey`函数索引不是系统自动分配的,则该类必须定义`IdKey`函数索引。
- 此功能索引必须定义为索引。
请注意,不应直接调用`%Storage.Persistent`和`%Storage.SQL`类方法。相反,应该使用`%Persistent`类方法和本章中描述的操作调用索引功能。
## 索引全局名称
使用以下两种策略之一生成用于存储索引数据的下标全局:
- `%CLASSPARAMETER USEEXTENTSET=0`使用全局命名策略创建由用户指定的名称、附加的字母代码和索引名称组成的“传统”全局名称。用户可以理解这些全局名称,但它们可能很长,并且效率低于散列的全局名称。
- 如果`USEEXTENTSET=0`且未指定`DEFAULTGLOBAL`,则以下示例将描述生成的全局名称:`Sample.MyTest`持久类将定义名为`^Sample.MyTestD`的master map全局名`^Sample.MyTestD`位图范围索引全局名`^Sample.MyTestI(“$MyTest”)`(或`^Sample.MyTestI(“DDLBEIndex”)`),并且对于定义的索引`NameIDX`,它将定义名为`^Sample.MyTestI(“DDLBEIndex”)`的全局名。请注意,这些全局变量指定的是持久性类名(区分大小写),而不是SQL表名。
- 如果`USEEXTENTSET=0`并指定了`DEFAULTGLOBAL`,则指定的全局名称将替换永久类名。这允许指定一个比持久类名称更短或更清晰的名称。例如,如果`DEFAULTGLOBAL=“MyGlobal”`,则全局变量的名称如下:`^MyGlobalD`和`^MyGlobalI(“NameIDX”)`。
- `%CLASSPARAMETER USEEXTENTSET=1`使用创建哈希全局名称的全局命名策略。这包括对包名进行散列,对类名进行散列,然后追加一个点和一个标识索引的连续整数后缀。这些全局名称对用户来说不太容易理解,但往往更短、效率更高。
整数后缀仅作为索引名的关键字;与索引名和索引类型相关联的字段对整数编号没有影响。例如,`^EW3K.CgZk.1` 是 `Master Map`,`^EW3K.CgZk.2`是位图范围(`Bitmap Extent`),`^EW3K.CgZk.3`是`LastName`字段的已定义标准索引`NameIDX`,`^EW3K.CgZk.4`是已定义索引`WorkIdIDX`。如果删除`NameIDX`,则全局`^EW3K.CgZk.3`也会被删除,从而在整数序列中产生间隙。如果为`LastName`字段定义`LNameIDX`,则会创建全局`^EW3K.CgZk.5`;但是,如果稍后为`FullName`字段创建位图索引`NameIDX`,则全局索引将再次为`^EW3K.CgZk.3`。
- 如果`USEEXTENTSET=1`并且未指定`DEFAULTGLOBAL`,则包名和类名将被散列,如上所述。将追加连续的整数后缀。
- 如果`USEEXTENTSET=1`并指定了`DEFAULTGLOBAL`,则使用`DEFAULTGLOBAL`名称,而不是散列的包名和类名。将追加连续的整数后缀。例如,如果`DEFAULTGLOBAL="MyGlobal"`,则全局变量的名称如下:`^MyGlobal.1`和`^MyGlobal.3`
如果使用`CREATE TABLE`命令定义表,则`USEEXTENTSET`默认为1。因此,默认情况下,`CREATE TABLE`创建散列全局名称。可以使用`%CLASSPARAMETER`关键字以及`USEEXTENTSET`和`DEFAULTGLOBAL`参数更改此默认行为。可以使用`$SYSTEM.SQL.Util.SetOption()`方法在系统范围内更改此默认设置。
```java
SET status=$SYSTEM.SQL.Util.SetOption("DDLUseExtentSet",0,.oldval).
```
如果定义投影到表的持久类,则`USEEXTENTSET`默认为0。因此,默认情况下,使用传统的全局名称。
`DEFAULTGLOBAL`(如果已定义)将作为默认值。如果定义了`ExtentLocation`、`DataLocation`或`IndexLocation`存储关键字,则使用这些值,而不是上述默认值。
可以向`ZWRITE`提供全局名称以显示索引数据。
## Master Map
系统自动为每个表定义一个主图(`Data/Master`)。M`aster Map`不是索引,它是使用其`Map`下标字段直接访问数据本身的`Map`。默认情况下,`Master Map`下标字段是系统定义的`RowID`字段。默认情况下,使用`RowID`字段进行的这种直接数据访问由SQL映射名称(SQL索引名称)`IDKEY`表示。
默认情况下,用户定义的主键不是`IDKEY`。**这是因为使用`RowID`整数查找 `Master Map`总是比使用主键值查找效率更高。** 但是,如果指定主键为`IDKEY`,则主键索引被定义为表的主映射,SQL映射名称为主键SQL索引名。
对于单字段`key/IDKEY,`,主键索引是主映射,但主映射数据访问列仍然是`RowID`。这是因为在记录的唯一主键字段值和其`RowID`值之间存在一对一的匹配,而`RowID`被认为是更高效的查找。对于多字段主键/`IDKEY`,会为`Master Map`指定主键索引名称,并且`Master Map Data Access`列是主键字段。
可以通过Management Portal SQL Catalog Details(管理门户SQL目录详细信息)选项卡查看主图定义。除其他项目外,它还显示存储Master Map数据的全局名称。对于SQL和默认存储,此主映射全局默认为`^Package.classnameD`,并记录命名空间以防止歧义。对于自定义存储,未定义主地图数据存储全局名称;可以使用`DATALOCATIONGLOBAL`类参数指定数据存储全局名称。
对于SQL和默认存储,主映射数据存储在下标全局名为`^package.classnameD` 或 `^hashpackage.hashclass.1`。请注意,全局名指定持久类名,而不是相应的SQL表名,并且全局名区分大小写。可以向`ZWRITE`提供全局名称以显示`Master Map`数据。
**使用`Master Map`访问数据效率很低,尤其是对于大型表。因此,建议用户定义可用于访问`WHERE`条件、联接操作和其他操作中指定的数据字段的索引。**
# 自动定义的索引
定义表时,系统会自动定义某些索引。在为表格定义并在添加或修改表数据时,自动生成以下索引。如果定义:
- 不是`IDKEY`的主键,则系统会生成唯一类型的相应索引。主键索引的名称可以是用户指定的,也可以是从表名派生的。例如,如果定义一个未命名的主键,则相应的索引将命名为`tablenamePKEY#`,其中`#`是每个`UNIQUE`和`PRIMARY KEY`约束的顺序整数。
- 唯一的字段,Intersystems Iris为每个唯一字段生成索引,其中名称T`ableNameUnique#`,其中`#`是每个唯一和主键约束的顺序整数。
- 唯一约束,系统为每个具有指定名称的唯一约束生成索引,为共同定义唯一值的字段编制索引。
- shard key,系统在shard key字段上生成一个索引,命名为`ShardKey`。
可以通过Management Portal SQL Catalog Details选项卡查看这些索引。`CREATE INDEX`命令可用于添加唯一字段约束;D`ROP INDEX`命令可用于删除唯一字段约束。
默认情况下,系统在`RowID`字段上生成`IDKEY`索引。定义身份字段不会生成索引。但是,如果定义标识字段并将该字段作为主键,则InterSystems IRIS将在标识字段上定义`IDKEY`索引并将其作为主键索引。下面的示例显示了这一点:
```sql
CREATE TABLE Sample.MyStudents (
FirstName VARCHAR(12),
LastName VARCHAR(12),
StudentID IDENTITY,
CONSTRAINT StudentPK PRIMARY KEY (StudentID) )
```
同样,如果定义标识字段并为该字段提供唯一约束,则InterSystems IRIS将在标识字段上显式定义`IdKey/Unique`索引。下面的示例显示了这一点:
```sql
CREATE TABLE Sample.MyStudents (
FirstName VARCHAR(12),
LastName VARCHAR(12),
StudentID IDENTITY,
CONSTRAINT StudentU UNIQUE (StudentID) )
```
这些标识索引操作仅在没有明确定义的`idkey`索引时出现,并且表不包含任何数据。
## 位图范围索引
**位图范围索引是表的行的位图索引,而不是针对表的任何指定字段。在位图范围索引中,每个位表示顺序`ROWID`整数值,并且每个位的值指定相应的行是否存在。**
**SQL使用此索引来提高`Count(*)`的性能,返回表中的记录数(行)。**
**一个表最多可以有一个位图区段索引。创建多个位图范围索引导致`SQLCode -400`错误。** 其中 `ERROR #5445: Multiple Extent indices defined:DDLBEIndex.`
所有使用`CREATE TABLE`定义的表都会自动定义位图区段索引。
将此自动生成的索引分配索引名称(索引属性名称)`DDLBEIndex`和SQL MapName (SQL索引名称)`%%DDLBEIndex`。
定义为类的表可以有一个位图区索引,索引名和`$ClassName`的SQL MapName(其中`ClassName`是表的持久化类的名称)。
可以使用带有`BITMAPEXTENT`关键字的`CREATE INDEX`命令将位图区段索引添加到表中,或者重命名自动生成的位图区段索引。
可以通过管理门户SQL Catalog详细选项卡查看表的位图范围索引。虽然表只有一个位图范围索引,但是从另一个表中继承的表在其自身位图范围索引和它从其扩展的表中的位图范围索引中列出。例如,`Sample.employee`表扩展了`Sample.person`表;在目录详细信息映射 `Sample.Employee`列出`$Employee` 和 `$Person Bitmap`范围索引。
在经历许多删除操作的表格中,位图范围索引的存储可以逐渐变得效率较低。可以通过选择表的“目录详细信息”选项卡,“映射”选项和选择重建索引来重建从管理门户中重建位图范围索引。
![image](/sites/default/files/inline/images/1_33.png)
**%SYS.Maint.Bitmap实用程序方法压缩位图范围索引,以及位图指数和bitslice索**引。
在以下任何情况下,调用`%BuildIndices()`方法都会构建现有的位图范围索引:未指定`%BuildIndices() pIndexList`参数(构建所有定义的索引);`pIndexList`按名称指定位图范围索引;或`pIndexList`指定任何定义的位图索引。
# 定义索引
## 使用类定义定义索引
在Studio中,可以使用新建索引向导或通过编辑类定义的文本将索引定义添加到`%Persistent`类定义。索引在一个或多个索引属性表达式上定义,后跟一个或多个可选索引关键字(可选)。它采用以下形式:
```sql
INDEX index_name ON index_property_expression_list [index_keyword_list];
```
- `index_name`是有效的标识符。
- `index_property_expression_list`是一个或多个以逗号分隔的属性表达式的列表,它们作为索引的基础。
- `index_keyword_list`是一个可选的索引关键字列表,用逗号分隔,用方括号括起来。
用于指定位图或位片索引的索引类型。
也用于指定唯一的、`IdKey`或`PrimaryKey`索引。
(根据定义,`IdKey`或`PrimaryKey`索引也是唯一索引。)
索引关键字的完整列表出现在类定义引用中。
`index_property_expression_list`参数由一个或多个索引属性表达式组成。
索引属性表达式包括:
- 要建立索引的属性的名称。
- 可选(元素)或(键)表达式,提供对集合子值进行索引的方法。
如果`index`属性不是一个集合,用户可以使用`BuildValueArray()`方法生成一个包含键和元素的数组。
- 可选的排序规则表达式。
它包含一个排序规则名称,后面可选地跟着一个或多个以逗号分隔的排序规则参数列表。
不能为惟一索引、`IdKey`索引或`PrimaryKey`索引指定索引排序规则。
唯一索引或`PrimaryKey`索引从正在建立索引的属性(字段)中获取其排序规则。
`IdKey`索引总是精确(`EXACT`)的排序。
例如,下面的类定义定义了两个属性和一个基于它们的索引:
```java
Class MyApp.Student Extends %Persistent [DdlAllowed]
{
Property Name As %String;
Property GPA As %Decimal;
Index NameIDX On Name;
Index GPAIDX On GPA;
}
```
更复杂的索引定义是:
```sql
Index Index1 On (Property1 As SQLUPPER(77), Property2 AS EXACT);
```
### 可以建立索引的属性
唯一可以被索引的属性是:
- 那些存储在数据库中的
- 那些可以从存储的属性可靠地派生出来的
必须使用`SQLComputed`关键字定义可以可靠地派生(并且未存储)的属性; `SQLComputeCode`指定的代码必须是导出属性值的唯一方法,并且无法直接设置属性。
如果可以直接设置一个派生属性的值,比如是一个简单的情况下(non-collection)属性定义为瞬态和不也定义为计算,然后直接设置属性的值将覆盖`SQLComputeCode`中定义的计算和存储的值不能可靠地来自属性;
这种类型的派生属性称为不确定性。(计算的关键字实际上意味着没有分配实例内存。)
一般规则是,只有定义为`calculate`和`SQLComputed`的派生属性才能被索引。
但是,派生集合有一个例外:派生的(`SQLComputed`)集合是暂时的(没有存储)集合,也没有定义为计算的集合(意味着没有实例内存)可以被索引。
**注意:`IdKey`索引所使用的任何属性的值内都不能有连续的一对竖条(`||`),除非该属性是对持久类实例的有效引用。**
这个限制是InterSystems SQL内部机制所要求的。
在`IdKey`属性中使用`||`会导致不可预知的行为。
### 多个属性的索引
可以在两个或多个属性(字段)的组合上定义索引。在类定义中,使用索引定义的`ON`子句指定属性列表,例如:
```java
Class MyApp.Employee Extends %Persistent [DdlAllowed]
{
Property Name As %String;
Property Salary As %Integer;
Property State As %String(MAXLEN=2);
Index MainIDX On(State,Salary);
}
```
如果需要执行使用字段值组合的查询,例如:
```sql
SELECT Name,State,Salary
FROM Employee
ORDER BY State,Salary
```
### 索引排序
唯一索引、`PrimaryKey`索引或`IdKey`索引不能指定排序规则类型。
对于其他类型的索引,索引定义中指定的每个属性都可以有一个排序规则类型。
应用索引时,索引排序类型应与属性(字段)排序类型匹配。
1. 如果索引定义包含为属性显式指定的排序规则,则索引使用该排序规则。
2. 如果索引定义不包括为属性显式指定的排序规则,则索引使用属性定义中显式指定的排序规则。
3. 如果属性定义不包括显式指定的排序规则,则索引使用属性数据类型的默认排序规则。
例如,`Name`属性被定义为字符串,**因此在默认情况下具有`SQLUPPER`排序规则。**
如果在`Name`上定义一个索引,默认情况下,它接受属性的排序规则,索引也将使用`SQLUPPER`定义。
属性排序和索引排序匹配。
但是,如果比较应用不同的排序规则,例如,`WHERE %EXACT(Name)=%EXACT(:invar)`,则此用法中的属性排序规则类型不再与索引排序规则类型匹配。属性比较排序规则类型与索引排序规则类型之间的不匹配可能会导致不使用索引。因此,在这种情况下,可能希望为具有精确(`EXACT`)排序规则的`Name`属性定义索引。如果`JOIN`语句的`ON`子句指定了排序规则类型,例如,`FROM Table1 LEFT JOIN Table2 ON %EXACT(Table1.Name) = %EXACT(Table2.Name)`,此处指定的属性排序类型与索引排序类型不匹配可能导致InterSystems IRIS不使用该索引。
以下规则控制索引和属性之间的排序规则匹配:
- **匹配的排序规则类型总是最大限度地使用索引。**
- **排序规则类型不匹配,其中属性指定为精确的排序规则(如上所示),并且索引有一些其他的排序规则,允许使用索引,但是它的使用不如匹配排序类型有效。**
- **排序规则类型不匹配,其中属性排序规则不准确,属性排序规则不匹配索引排序规则,这将导致不使用索引。**
要在索引定义中显式地为属性指定排序规则,语法如下:
```sql
Index IndexName On PropertyName As CollationName;
```
- `IndexName`是索引的名称
- `PropertyName`是被索引的属性
- `CollationName`是用于索引的排序规则的类型
例如:
```sql
Index NameIDX On Name As Exact;
```
不同的属性可以有不同的排序规则类型。
例如,在下面的例子中,`F1`属性使用`SQLUPPER`排序,而F2使用`EXACT`排序:
```sql
Index Index1 On (F1 As SQLUPPER, F2 As EXACT);
```
**注意:指定为`Unique`、`PrimaryKey`或`IdKey`的索引不能指定索引排序规则。
索引从属性`collations`中获取其`collation`。**
文章
Nicky Zhu · 一月 11, 2021
当我向技术人员介绍InterSystems IRIS时,我一般会先讲其核心是一个多模型DBMS。
我认为这是其主要优势(在DBMS方面)。数据仅存储一次。您只需访问您想用的API。
- 您想要数据的概要?用SQL!
- 您想用一份记录做更多事情?用对象!
- 想要访问或设置一个值,并且您知道键?用Globals!
乍一看挺好的,简明扼要,又传达了信息,但当人们真正开始使用InterSystems IRIS时,问题就来了。类、表和Globals是如何关联的?它们之间有什么关系?数据是如何存储的?
本文我将尝试回答这些问题,并解释这些到底是怎么回事。
## 第一部分 模型偏见
处理数据的人往往对他们使用的模型有偏见。
开发者们把数据视为对象。对他们而言,数据库和表都是通过CRUD(增查改删,最好是基于ORM)交互的盒子,但底层的概念模型都是对象(当然这对于我们大多数使用面向对象编程语言的开发者来说没错)。
而DBA大部分时间都在搞关系型DBMS,他们把数据视为表。对象只是行的封装器。
对于InterSystems IRIS,持久类也是一个表,将数据存储在Global中,因此需要进行一些澄清。
## 第二部分 举例
假设您创建了类Point:
```objectscript
Class try.Point Extends %Persistent [DDLAllowed]
{
Property X;
Property Y;
}
```
您也可以用DDL/SQL创建相同的类:
```
CREATE Table try.Point (
X VARCHAR(50),
Y VARCHAR(50))
```
编译后,新类将自动生成一个存储结构,将原生存储在Global中的数据映射到列(对于面向对象开发者而言,是属性):
```
Storage Default
{
%%CLASSNAME
X
Y
^try.PointD
PointDefaultData
^try.PointD
^try.PointI
^try.PointS
%Library.CacheStorage
}
```
这是怎么回事?
自下向上(加粗文字很重要):
- Type: 生成的存储类型,本例中是持久对象的默认存储
- StreamLocation - 存储流的Global
- IndexLocation - 索引Global
- IdLocation - 存储ID自增计数器的Global
- **DefaultData** - 存储将Global值映射到列/属性的XML元素
- **DataLocation** - 存储数据的Global
现在我们的DefaultData是PointDefaultData,让我们分析下它的结构。本质上Global节点有这样的结构:
- 1 - %%CLASSNAME
- 2 - X
- 3 - Y
所以我们可能期望我们的Global是这样的:
```
^try.PointD(id) = %%CLASSNAME, X, Y
```
但如果我们输出 Global 它会是空的,因为我们没有添加任何数据:
```
zw ^try.PointD
```
让我们添加一个对象:
```
set p = ##class(try.Point).%New()
set p.X = 1
set p.Y = 2
write p.%Save()
```
现在我们的Global变成了这样
```
zw ^try.PointD
^try.PointD=1
^try.PointD(1)=$lb("",1,2)
```
可以看到,我们期望的结构%%CLASSNAME, X, Y是用 $lb("",1,2) 设置的,它对应的是对象的X和Y属性(%%CLASSNAME 是系统属性,忽略)。
我们也可以用SQL添加一行:
```
INSERT INTO try.Point (X, Y) VALUES (3,4)
```
现在我们的Global变成了这样:
```
zw ^try.PointD
^try.PointD=2
^try.PointD(1)=$lb("",1,2)
^try.PointD(2)=$lb("",3,4)
```
所以我们通过对象或SQL添加的数据根据存储定义被存储在Global中(备注:可以通过在PointDefaultData 中替换X和Y来手动修改存储定义,看看新数据会怎样!)。
现在,如果我们想执行SQL查询会怎样?
```
SELECT * FROM try.Point
```
这段sql查询被转换为ObjectScript代码, 遍历^try.PointD,并根据存储定义(其中的 PointDefaultData 部分)填充列。
下面是修改。让我们从表中删除所有数据:
```
DELETE FROM try.Point
```
看看我们的Global变成什么样了:
```
zw ^try.PointD
^try.PointD=2
```
可以看到,只剩下ID计数器,所以新对象/行的ID=3。我们的类和表也继续存在。
但如果我们运行会怎样:
```
DROP TABLE try.Point
```
它会销毁表、类并删除Global。
```
zw ^try.PointD
```
看完这个例子,希望您现在对Global、类和表如何相互集成和互补有了更好的理解。根据实际需要选用正确的API会让开发更快、更敏捷、bug更少。
文章
姚 鑫 · 七月 17, 2021
# 第六章 使用%File对象
如果想要操作文件本身,需要使用`%Library.File`的`%New()`方法实例化`%File`对象。该类还提供了允许使用该文件的实例方法。
注意:本节提供了几个使用`%File`对象的示例,以供说明。
对于简单的文件读写,使用`%Stream.FileCharacter`和%`Stream.FileBinary`。因为它们提供了额外的功能,例如,以正确的模式自动打开文件。
## 创建%File对象的实例
要使用文件,需要使用`%New()`方法实例化表示该文件的%File对象。该文件可能已经存在,也可能不存在于磁盘上。
以下示例在默认目录中为文件`export.xml`实例化一个%File对象。
```java
set fileObj = ##class(%File).%New("export.xml")
```
## 打开和关闭文件
实例化`%File`对象后,需要使用`open()`方法打开文件,以读取或写入该文件:
```java
USER>set status = fileObj.Open()
USER>write status
1
```
使用`Close()`方法关闭文件:
```java
USER>do fileObj.Close()
```
## 检查%File对象的属性
一旦实例化了文件,就可以直接检查文件的属性。
```java
USER>write fileObj.Name
export.xml
USER>write fileObj.Size
2512
USER>write $zdate(fileObj.DateCreated)
11/18/2020
USER>write $zdate(fileObj.DateModified)
11/18/2020
USER>write fileObj.LastModified
2020-11-18 14:24:38
USER>write fileObj.IsOpen
0
```
请注意,`LastModified`是人类可读的时间戳,而不是`$H`格式的日期。
属性“大小Size”、“创建日期DateCreated”、“修改日期DateModified”和“最后修改日期LastModified”是在访问时计算的。为不存在的文件访问这些属性会返回-2,表示找不到该文件。
注意:Windows是目前唯一跟踪实际创建日期的平台。其他平台存储最后一次文件状态更改的日期。
```java
USER>write ##class(%File).Exists("foo.xml")
0
USER>set fooObj = ##class(%File).%New("foo.xml")
USER>write fooObj.Size
-2
```
如果文件已打开,可以通过访问`CanonicalName`属性来查看其规范名称,这是根目录的完整路径。
```java
USER>write fileObj.CanonicalName
USER>set status = fileObj.Open()
USER>write fileObj.IsOpen
1
USER>write fileObj.CanonicalName
c:\intersystems\IRIS\mgr\user\export.xml
```
## 从文件中读取
要读取文件,可以打开文件,然后使用`Read()`方法。
以下示例读取`messages.log`的前`200`个字符。
```java
USER>set messages = ##class(%File).%New(##class(%File).ManagerDirectory() _ "messages.log")
USER>set status = messages.Open("RU")
USER>write status
1
USER>set text = messages.Read(200, .sc)
USER>write text
*** Recovery started at Mon Dec 09 16:42:01 2019
Current default directory: c:\intersystems\IRIS\mgr
Log file directory: .\
WIJ file spec: c:\intersystems\IRIS\mgr\IR
USER>write sc
1
USER>do messages.Close()
```
要从文件中读取整行,请使用`ReadLine()`方法,该方法继承自`%Library.File`的父类`%Library.AbstractStream`。
下面的示例读取`E:\temp\new.txt`的第一行。
```java
/// desc: 读取数据
/// w ##class(Demo.FileDemo).ReadFileData("E:\temp\new.txt")
ClassMethod ReadFileData(str)
{
s fileObj = ##class(%File).%New(str)
s status = fileObj.Open("RU")
w status,!
s text = fileObj.ReadLine(,.sc)
w text,!
w sc,!
d fileObj.Close()
q ""
}
```
## 写入文件
要写入文件,可以打开文件,然后使用`Write()`或`WriteLine()`方法。
以下示例将一行文本写入新文件。
```java
/// desc: 写入数据
/// w ##class(Demo.FileDemo).WriteFileData("E:\temp\new.txt")
ClassMethod WriteFileData(str)
{
s fileObj = ##class(%File).%New(str)
s status = fileObj.Open("RUWSN")
w status,!
s status = fileObj.WriteLine("Writing to a new file.")
w status,!
w fileObj.Size,!
d fileObj.Rewind()
s text = fileObj.ReadLine(,.sc)
w text,!
q ""
}
```
## 倒回文件
从文件读取或写入文件后,希望使用`Rewind()`方法倒回文件,以便可以从文件开头执行操作。
从上一个示例停止的地方开始,`fileObj`现在位于其末尾。倒回文件并再次使用`WriteLine()`会覆盖该文件。
```java
USER>set status = fileObj.Rewind()
USER>write status
1
USER>set status = fileObj.WriteLine("Rewriting the file from the beginning.")
USER>write status
1
USER>write fileObj.Size
40
```
关闭文件并重新打开它也会倒回文件。
```java
USER>do fileObj.Close()
USER>set status = fileObj.Open("RU")
USER>write status
1
USER>set text = fileObj.ReadLine(,.sc)
USER>write sc
1
USER>write text
Rewriting the file from the beginning.
```
## 清除文件
要清除文件,可以打开文件,然后使用`Clear()`方法。这将从文件系统中删除该文件。
以下示例清除默认目录中的`junk.xml`。
```java
USER>write ##class(%File).Exists("junk.xml")
1
USER>set fileObj = ##class(%File).%New("junk.xml")
USER>set status = fileObj.Open()
USER>write status
1
USER>set status = fileObj.Clear()
USER>write status
1
USER>write ##class(%File).Exists("junk.xml")
0
```