清除过滤器
文章
姚 鑫 · 七月 27, 2021
# 第二十九章 类关键字 - PropertyClass
向该类添加属性参数。
# 用法
要向该类添加属性参数,请使用以下语法:
```java
Class PropClass.MyClass Extends %RegisteredObject [ PropertyClass = PropClass.MyPropertyClass ] { //class members }
```
其中·propertyclasslist·是下列之一:
```java
[ PropertyClass = PropClass.MyPropertyClass ]
```
- 用逗号分隔的类名列表,用括号括起来。
# 详情
如果需要添加自定义属性参数,请执行以下操作:
1. 定义并编译一个定义一个或多个类参数的类。例如:
```java
Class PropClass.MyPropertyClass
{
Parameter MYPARM As %String = "XYZ";
}
```
这些类参数在下一步中成为属性参数。
2. 在定义属性的类中,指定`PropertyClass`关键字。
# 对子类的影响
子类继承这个关键字添加的自定义行为。如果子类为关键字指定了一个值,则该值会指定一个或多个为该类的属性指定参数的附加类。
# 第三十章 类关键字 - ServerOnly
指定此类是否被投影到Java客户端。
# 用法
要覆盖将类投影到`Java`客户端的默认方式,请使用以下语法:
```java
Class Sample.NewClass1 [ ServerOnly = serveronlyvalue ] { //class members }
```
其中`serveronlyvalue`是以下值之一:
- 0表示可以投影此类。
- 1表示这个类不会被投影。
# 详解
如果该关键字为`1`,则该类不会被投影到`Java`客户端。如果该关键字为`0`,则将投影该类。
# 对子类的影响
此关键字不是继承的。
# 默认
如果省略这个关键字,这个类如果不是存根就会被投影(但是如果是存根就不会被投影)。
# 第三十一章 类关键字 - Sharded
指定此类是否被分片。仅适用于包含分片集群的环境中的持久类。
# 用法
要将类定义为分片类,请使用以下语法:
```java
Class MyApp.MyClass Extends %Persistent [ Sharded = 1 ]
{ //class members }
```
否则,省略此关键字。
# 详解
分片是一种水平扩展数据存储的机制。如果一个类被分片,该类的实例将分布在分片集群中任何已定义的数据节点上。
如果有一个分片环境,并且将一个类定义为未分片,那么该类的实例只存储在第一个数据节点上,尽管所有节点都可以看到该数据。
# 对子类的影响
这个关键字是继承的。
# 默认
如果省略这个关键字,类就不会被分割。
# 第三十二章 类关键字 - SoapBindingStyle
指定此类中定义的任何web方法使用的绑定样式或SOAP调用机制。仅适用于定义为web服务或web客户端的类。
# 用法
要指定此类中定义`web method`使用的绑定样式,请使用以下语法:
```java
Class MyApp.MyClass [ SoapBindingStyle = soapbindingstyle ] { //class members }
```
其中`soapbindingstyle`是下列之一:
- `document` 文档(默认)—默认情况下,此类中的`web method`使用文档样式的绑定。
使用这种绑定风格,`SOAP`消息被格式化为文档,并且通常只有一个部分。
在`SOAP`消息中,``元素通常包含一个子元素。``元素的每个子元素对应于一个消息部分。
- `rpc` —默认情况下,此类中的`web method`使用`rpc`(远程过程调用)样式的绑定。
使用这种绑定风格,`SOAP`消息被格式化为具有多个部分的消息。
在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。这个元素是一个生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。
如果`SoapBindingStyle`是文档,如果`ARGUMENTSTYLE`是消息,那么消息样式与`RPC`非常相似;
重要提示:对于手动创建的`web service`,该关键字的默认值通常是合适的。当使用`SOAP`向导从`WSDL`生成`web客户端或服务`时,InterSystems IRIS会将此关键字设置为适合该`WSDL`;如果修改该值,`web客户端或服务`可能不再工作。
# 详解
此关键字允许指定此类中定义的任何`web method`使用的默认绑定样式。它影响`SOAP`主体的格式(但不影响任何SOAP头)。
通过使用`SoapBindingStyle`方法关键字或`SoapBindingStyle`查询关键字,可以重写单个方法的绑定样式。
# 对子类的影响
此关键字不是继承的。
# 默认
默认值为文档。
# 与WSDL的关系
`SoapBindingStyle`类关键字指定了`WSDL`的``部分中``元素的样式属性的值。例如,如果`SoapBindingStyle`是文档,则`WSDL`可能如下所示:
```xml
...
...
```
如这里所示,在`WSDL`的``部分中,`SoapBindingStyle`类关键字还指定了``元素的样式属性的默认值;该属性由`SoapBindingStyle`方法关键字进一步控制。
相比之下,如果`SoapBindingStyle`是`rpc`,则`WSDL`可以改为如下所示:
```xml
...
...
```
绑定样式也会影响``元素,如下所示:
- 如果绑定样式是文档,默认情况下,消息只有一个部分。例如:
```xml
```
如果`ARGUMENTSTYLE`参数是`message`,那么一条消息可以有多个部分。例如:
```xml
```
- 如果绑定样式是`rpc`,消息可以有多个部分。例如:
```xml
```
# 对SOAP消息的影响
对`SOAP`消息的主要影响是控制`SOAP`主体是否可以包含多个子元素。
对于使用`RPC`样式绑定和编码样式消息的`web method`,下面显示了请求消息正文的示例:
```xml
10
5
17
2
```
相比之下,下面显示了使用文字绑定和编码样式消息的`web method`的请求消息正文的示例:
```xml
10
5
17
2
```
在这种情况下,`SOAP`主体只有一个子元素。
# 与 `%XML.DataSet` 一起使用
对于 `%XML.DataSet`, 类型的对象,并非所有 `SoapBindingStyle` 和 `SoapBodyUse` 关键字的排列都是允许的,,如下表总结:
type | supported?| supported?
---|---|---
空 |SoapBodyUse=literal(默认) | SoapBodyUse=encoded
SoapBindingStyle=document(default) |supported| not supported
SoapBindingStyle=rpc| supported |supported
文章
姚 鑫 · 八月 19, 2021
# 第121章 查询关键字 - Private
指定查询是否为私有查询。
# 用法
要指定此查询为私有查询,请使用以下语法:
```java
Query name(formal_spec) As classname [ Private ] { //implementation }
```
否则,请省略此关键字或将该词放在该关键字之前。
# 详解
私有类成员只能由同一类(或其子类)的其他成员使用。请注意,其他语言通常使用单词`Protected`来描述这种可见性,使用单词`Private`来表示从子类不可见。
# 默认
如果省略此关键字,则此查询不是私有的。
# 第122章 查询关键字 - SoapBindingStyle
指定此查询用作`Web方法`时使用的绑定样式或`SOAP`调用机制。仅适用于定义为`Web服务`或`Web客户端`的类。
# 用法
要覆盖查询使用的默认绑定样式(当它用作`Web方法`时),请使用以下语法:
```java
Query name(formal_spec) As classname [ WebMethod, SoapBindingStyle = soapbindingstyle ] { //implementation }
```
其中`soapbindingstyle`为下列值之一:
- `document` - 此`Web方法`使用文档式调用。
使用这种绑定样式,`SOAP`消息被格式化为文档,并且通常只有一个部分。
在`SOAP`消息中,``元素通常包含单个子元素。``元素的每个子元素对应一个消息部分。
- `rpc` - 此`Web方法`使用`RP`C(远程过程调用)风格的调用。
使用这种绑定样式,`SOAP`消息被格式化为具有多个部分的消息。
在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。此元素是生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。
重要提示:对于手动创建的`Web服务`,此关键字的默认值通常比较合适。当使用`SOAP`向导从`WSDL`生成`Web客户端`或`服务`时,InterSystems IRIS会将此关键字设置为适用于该`WSDL`;如果修改此值,`Web客户端`或服务可能不再工作。
# 详情
此关键字允许指定此查询在作为`Web方法`调用时使用的绑定样式。
对于给定查询,此关键字覆盖`SoapBindingStyle`类关键字。
# 默认
如果忽略此关键字,``元素的`style`属性将由`SoapBindingStyle`类关键字的值决定。
# WSDL的关系
(请注意,与方法关键字和查询关键字相比,同名的`class关键字`对`WSDL`的影响更大。)
# 对SOAP消息的影响
有关信息,请参阅`SoapBindingStyle`类关键字的条目。
# 第123章 查询关键字 - SoapBodyUse
指定该查询用作`web方法`时,输入和输出使用的编码。
仅应用于定义为`web服务`或`web客户端`的类。
# 用法
要覆盖查询的输入和输出使用的默认编码(当它被用作`web方法`时),请使用以下语法:
```java
Query name(formal_spec) As classname [ WebMethod, SoapBodyUse = encoded ] { //implementation }
```
其中,`soapbodyuse`是下列值之一:
- `literal` - 这个`web方法`使用文字数据。
也就是说,`SOAP`消息的``中的`XML`与`WSDL`中给出的模式完全匹配。
- `encoded` = 这个web方法使用soap编码的数据。
也就是说,`SOAP`消息的``中的`XML`根据所使用的`SOAP`版本使用适当的SOAP编码,满足以下规范的要求:
- `SOAP 1.1` (https://www.w3.org/TR/2000/NOTE-SOAP-20000508/)
- `SOAP 1.2` (https://www.w3.org/TR/soap12-part2/)
重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。
当使用`SOAP`向导从`WSDL`生成`web客户端`或服务时, IRIS将此关键字设置为适合该`WSDL`的;
如果修改了该值,`web客户端`或服务可能不再工作。
# 详解
该关键字允许您指定该查询作为`web方法`调用时的输入和输出的编码。
对于给定的查询,此关键字覆盖`SoapBodyUse`类关键字。
# 默认
如果忽略此关键字,则使用`SoapBodyUse`类关键字的值。
# 与WSDL的关系以及对SOAP消息的影响
有关信息,请参阅`SoapBodyUse`类关键字的条目。
# 第124章 查询关键字 - SoapNameSpace
在`WSDL`中的绑定操作级别指定名称空间。
仅应用于定义为`web服务`或`web客户端`的类。
# 用法
要在绑定操作级别覆盖默认命名空间(当查询被用作`web方法`时),请使用以下语法:
```java
Query name(formal_spec) As classname [ SoapNameSpace = "soapnamespace", WebMethod ] { //implementation }
```
其中,`soapnamespace`是一个名称空间`URI`。
注意,如果`URI`包含冒号(`:`),则字符串必须用引号括起来。
也就是说,你可以使用以下方法:
```java
Query MyQuery() [ SoapNameSpace = "http://www.mynamespace.org", WebMethod ]
```
或以下:
```java
Query MyQuery() [ SoapNameSpace = othervalue, WebMethod ]
```
但以下情况并非如此:
```java
Query MyQuery() [ SoapNameSpace = http://www.mynamespace.org, WebMethod ]
```
重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。
当使用`SOAP`向导从`WSDL`生成`web客户`端或服务时, IRIS将此关键字设置为适合该WSDL的;
如果修改了该值,`web客户端`或服务可能不再工作。
# 详解
该关键字允许指定查询作为`web方法`调用时使用的`XML`名称空间。
注意:此关键字仅在查询使用`rpc`样式绑定时有效。
也就是说,查询(或包含它的类)必须用等于`rpc`的`SoapBindingStyle`标记。
(如果为使用文档样式绑定的查询指定此关键字,则`WSDL`将不是自一致的。)
# 默认
如果忽略此关键字,则`web方法`位于由`web服务`或客户端类的`namespace`参数指定的命名空间中。
# 与WSDL的关系以及对SOAP消息的影响
有关信息,请参阅`SoapNameSpace`方法关键字的条目。
文章
Frank Ma · 三月 2, 2022
好人不需要规则。
神秘博士
要成为日期和时间的主人并不是一件容易的事,在任何编程语言中,这总是一个问题,有时会让人感到困惑,我们将澄清并提出一些提示,使这项任务尽可能简单。
坐上TARDIS,我将把你变成一个时间领主。
让我们从基本知识开始
如果你通常使用其他语言,请记住,Intersystems Object Script(以下简称IOS,不要与苹果手机混淆)的日期有点特殊。当我们在终端运行$HOROLOG 命令时,为了得到当前的日期和时间,你会看到它被分为两部分:
WRITE $HOROLOG
> 66149,67164
第一个值是天数,确切地说,是自1840年12月31日以来的天数,也就是说,值1是1841年1月1日;第二个值是自今天00:00以来的秒钟。
在这个例子中,66149对应于09/02/2022(欧洲格式的日/月/年的2月9日),67164对应于18:39:24。我们将这种格式称为数据和时间的内部格式。
感到困惑吗?好吧,我们将开始揭示宇宙的伟大秘密(日期和时间)。
如何将内部格式转换为更清晰的格式?
为此,我们将用到命令 $ZDATETIME
基本的命令是
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow)
> 02/09/2022 18:39:24
默认情况下,它使用美国格式月/日/年(mm/dd/yyyy)。如果你想使用其他格式的日期,我们将使用第二个参数,比如欧洲格式日/月/年(dd/mm/yyyy),在这种情况下,我们将给它一个值4(关于更多的格式,见文档$ZDATETIME.dformat)。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow,4)
> 09/02/2022 18:39:24
该选项使用我们在本地变量中定义的分隔符和年份格式。
如果我们还想放另一种时间格式,例如12小时格式(AM/PM)而不是24小时格式,我们使用第三个参数,其值为3,如果我们不想显示秒,我们将使用值4(见文件$ZDATETIME.tformat)。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow,4,3)
> 09/02/2022 06:39:24PM
WRITE $ZDATETIME(RightNow,4,4)
> 09/02/2022 06:39PM
现在是不是更清楚了?那么让我们更深入地了解一下。
ODBC 格式
这个格式与你的本地配置无关,它将始终显示为年/月/日格式 yyyy-mm-dd,其值为3。 如果我们想创建要导出文件的数据,如CSV、HL7文件等,建议使用它。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATETIME(RightNow,3)
> 2022-02-09 18:39:24
一周的日子,星期名称,一年中的某天
Value 值
描述
10
一周的日子将是一个介于0和6之间的值,0代表星期天,6代表星期六。
11
星期的缩写名称,它将根据你定义的本地配置返回,IRIS的默认安装是 enuw (English, United States, Unicode)
12
长格式的星期名称,与11相同。
14
一年中的某一天,自1月1日以来的天数。
如果我们只是想分别处理日期和时间,应该分别使用$ZDATE和$ZTIME命令。格式的参数与 $ZDATETIME.dformat 和 $ZDATETIME.tformat中定义的参数相同。
SET RightNow = $HOROLOG
WRITE RightNow
> 66149,67164
WRITE $ZDATE(RightNow,10)
> 3
WRITE $ZDATE(RightNow,11)
> Wed
WRITE $ZDATE(RightNow,12)
> Wednesday
那我如何将日期转换为内部格式?
好了,现在我们来看看相反的步骤,即有一个带有日期的文本,并将其转换成IOS格式。对于这个任务,我们将使用命令 $ZDATETIMEH。
这一次,我们必须指出日期和时间的格式(如果我们使用$ZDATETIMEH),或者分别指出日期($ZDATEH)和时间($ZTIMEH)。
格式是相同的,也就是说,如果我们有一个ODBC格式(yyyy-mm-dd)的日期字符串,那么我们将使用值3。
SET MyDatetime = "2022-02-09 18:39:24"
SET Interna1 = $ZDATETIMEH(MyDatetime, 3, 1) // ODBC Format
SET MyDatetime = "09/02/2022 18:39:24"
SET Interna2 = $ZDATETIMEH(MyDatetime, 4, 1) // European format
SET MyDatetime = "02/09/2022 06:39:24PM"
SET Interna3 = $ZDATETIMEH(MyDatetime, 1, 3) // American format with time in 12h AM/PM
WRITE Interna1,!,Interna2,!,Interna3
> 66149,67164
66149,67164
66149,67164
从逻辑上讲,如果我们说字符串使用的是一种特殊的格式,而我们给它提供了错误的参数,那么任何事情都可能发生,比如它理解为2月9日,而不是9月2日。
不要混合格式,这样以后会出现问题。
SET MyDatetime = "09/02/2022"
/// American format
SET InternalDate = $ZDATEH(MyDatetime, 1)
/// European format
SET OtherDate = $ZDATETIME(InternalDate, 4)
WRITE InternalDate,!,OtherDate
> 66354
02/09/2022
不用说,如果我们试图设定一个欧洲的日期并试图将其转化为美国的日期...... 在情人节会发生什么?
SET MyDatetime = "14/02/2022"
SET InternalDate = $ZDATEH(MyDatetime, 1) // American format. month 14 doesn't exists!!!
^
<ILLEGAL VALUE>
嗯,就像所有的情人节一样......破碎的心,嗯......在这种情况下,破碎的代码。
好吧,让我们用你已经学到的东西做一些事情。
READ !,"Please indicate your date of birth (dd/mm/yyyy): ",dateOfBirth
SET internalFormat = $ZDATEH(dateOfBirth, 4)
SET dayOfWeek= $ZDATE(internalFormat, 10)
SET nameOfDay = $ZDATE(internalFormat, 12)
WRITE !,"The day of the week of your birth is: ",nameOfDay
IF dayOfWeek = 5 WRITE "you always liked to party!!!" // was born on friday
以后我们将看到其他的做事方法,以及如何处理错误。
下一章:如何进行时间旅行
好奇
如果你想知道为什么01/01/1841的值被当作1的值,那是因为选择这个日期是因为它是在世的最年长的美国公民出生前的非闰年,当MUMPS编程语言被设计时,他是一个121岁的内战老兵,它从这个语言中扩展了对象脚本。
文章
姚 鑫 · 七月 12, 2022
# 第三章 嵌入式Python概述(三)
## 使用 SQL
`IRIS` 中的类被投影到 `SQL`,除了使用类方法或直接全局访问之外,还允许使用查询访问数据。 `iris` 模块为提供了两种从 `Python` 运行 `SQL` 语句的不同方式。
以下示例使用 `iris.sql.exec()` 运行 `SQL SELECT` 语句以查找类名称以“`%Net.LDAP`”开头的所有类定义,返回一个包含每个名称和超类的结果集每个班级。在这里,系统类 `%Dictionary.ClassDefinition` 将 `SQL` 投影为同名表。
```java
>>> rs = iris.sql.exec("SELECT Name, Super FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH '%Net.LDAP'")
```
以下示例使用 `iris.sql.prepare()` 准备 `SQL` 查询对象,然后执行查询,将`“%Net.LDAP”`作为参数传入:
```java
>>> stmt = iris.sql.prepare("SELECT Name, Super FROM %Dictionary.ClassDefinition WHERE Name %STARTSWITH ?")
>>> rs = stmt.execute("%Net.LDAP")
```
无论哪种情况,都可以按如下方式遍历结果集,并且输出相同:
```java
>>> for idx, row in enumerate(rs):
... print(f"[{idx}]: {row}")
...
[0]: ['%Net.LDAP.Client.EditEntry', '%RegisteredObject']
[1]: ['%Net.LDAP.Client.Entries', '%RegisteredObject,%Collection.AbstractIterator']
[2]: ['%Net.LDAP.Client.Entry', '%RegisteredObject,%Collection.AbstractIterator']
[3]: ['%Net.LDAP.Client.PropList', '%RegisteredObject']
[4]: ['%Net.LDAP.Client.Search.Scope', '%Integer']
[5]: ['%Net.LDAP.Client.Session', '%RegisteredObject']
[6]: ['%Net.LDAP.Client.StringList', '%RegisteredObject']
[7]: ['%Net.LDAP.Client.ValueList', '%RegisteredObject,%Collection.AbstractIterator']
```
## 使用Globals
在 `IRIS` 数据库中,所有数据都存储在全局变量中。全局数组是持久的(意味着它们存储在磁盘上)、多维的(意味着它们可以有任意数量的下标)和稀疏的(意味着下标不必是连续的)。当您在表中存储类的对象或行时,这些数据实际上存储在全局变量中,尽管您通常通过方法或 `SQL` 访问它们并且从不直接接触全局变量。
有时将持久数据存储在全局变量中会很有用,而无需设置类或 `SQL` 表。在 `IRIS` 中,全局变量看起来很像任何其他变量,但它在名称前用插入符号 (`^`) 表示。以下示例将工作日的名称存储在当前命名空间的全局 `^Workdays` 中。
```java
>>> myGref = iris.gref('^Workdays')
>>> myGref[None] = 5
>>> myGref[1] = 'Monday'
>>> myGref[2] = 'Tuesday'
>>> myGref[3] = 'Wednesday'
>>> myGref[4] = 'Thursday'
>>> myGref[5] = 'Friday'
>>> print(myGref[3])
Wednesday
```
第一行代码 `mmyGref = iris.gref('^Workdays') ` 获取一个全局引用(或 `gref`),指向一个名为 `^Workdays` 的全局引用,它可能已经存在也可能不存在。
第二行 `myGref[None] = 5` 将工作日数存储在 `^Workdays` 中,不带下标。
第三行 `myGref[1] = 'Monday'` 将字符串 `Monday` 存储在位置 `^Workdays(1)` 中。接下来的四行将剩余的工作日存储在位置 `^Workdays(2)` 到 `^Workdays(5)` 中。
最后一行 `print(myGref[3])` 显示了如何在给定 `gref` 的情况下访问存储在全局中的值。
# 一起使用 ObjectScript 和 Python
`IRIS` 让 `ObjectScript` 和 `Python` 程序员的混合团队轻松协作。例如,类中的一些方法可以用 `ObjectScript` 编写,而另一些可以用 `Python` 编写。程序员可以选择用他们最熟悉的语言编写,或者更适合手头任务的语言。
## 创建混合 InterSystems IRIS 类
下面的类有一个用 `Python` 编写的 `Print()` 方法和一个用 `ObjectScript` 编写的 `Write()` 方法,但它们在功能上是等效的,并且可以从 `Python` 或 `ObjectScript` 调用这两种方法。
```java
Class Sample.Company Extends (%Persistent, %Populate, %XML.Adaptor)
{
/// The company's name.
Property Name As %String(MAXLEN = 80, POPSPEC = "Company()") [ Required ];
/// The company's mission statement.
Property Mission As %String(MAXLEN = 200, POPSPEC = "Mission()");
/// The unique Tax ID number for the company.
Property TaxID As %String [ Required ];
/// The last reported revenue for the company.
Property Revenue As %Integer;
/// The Employee objects associated with this Company.
Relationship Employees As Employee [ Cardinality = many, Inverse = Company ];
Method Print() [ Language = python ]
{
print ('\nName: ' + self.Name + ' TaxID: ' + self.TaxID)
}
Method Write() [ Language = objectscript ]
{
write !, "Name: ", ..Name, " TaxID: ", ..TaxID
}
}
```
此 `Python` 代码示例展示了如何使用 `%Id=2` 打开 `Company` 对象并调用 `Print()` 和 `Write()` 方法。
```java
>>> company = iris.cls("Sample.Company")._OpenId(2)
>>> company.Print()
Name: IntraData Group Ltd. TaxID: G468
>>> company.Write()
Name: IntraData Group Ltd. TaxID: G468
```
此 `ObjectScript` 代码示例展示了如何打开相同的 `Company` 对象并调用这两种方法。
```java
SAMPLES>set company = ##class(Sample.Company).%OpenId(2)
SAMPLES>do company.Print()
Name: IntraData Group Ltd. TaxID: G468
SAMPLES>do company.Write()
Name: IntraData Group Ltd. TaxID: G468
```
## 在 Python 和 ObjectScript 之间传递数据
虽然 `Python` 和 `ObjectScript` 在许多方面都兼容,但它们有许多自己的数据类型和结构,有时在将数据从一种语言传递到另一种语言时需要进行一些数据转换。之前看到了一个示例,即从 `ObjectScript` 向 `Python` 传递命名参数的示例。
`%SYS.Python` 类的 `Builtins()` 方法为提供了一种方便的方式来访问 `Python` 的内置函数,它可以帮助创建 `Python` 方法所期望的类型的对象。
以下 `ObjectScript` 示例创建两个 `Python` 数组 `newport` 和 `cleveland`,每个数组都包含一个城市的纬度和经度:
```java
USER>set builtins = ##class(%SYS.Python).Builtins()
USER>set newport = builtins.list()
USER>do newport.append(41.49008)
USER>do newport.append(-71.312796)
USER>set cleveland = builtins.list()
USER>do cleveland.append(41.499498)
USER>do cleveland.append(-81.695391)
USER>zwrite newport
newport=11@%SYS.Python ; [41.49008, -71.312796] ;
USER>zwrite cleveland
cleveland=11@%SYS.Python ; [41.499498, -81.695391] ;
```
下面的代码使用在前面的示例中看到的 `geopy` 包来计算纽波特,罗德岛和克利夫兰,俄亥俄州之间的距离。它使用 `geopy.distance.distance()` 方法创建一条路线,将数组作为参数传递,然后打印路线的英里属性。
```java
USER>set distance = $system.Python.Import("geopy.distance")
USER>set route = distance.distance(newport, cleveland)
USER>write route.miles
538.3904453677205311
```
注意: `geopy.distance.distance()` 方法实际上期望参数是 `Python` 元组数据类型,但数组也可以。
## 运行 Python 命令
当开发或测试某些东西时,有时运行一行 `Python` 代码以查看它的作用或是否有效可能会很有用。在这种情况下,可以使用 `%SYS.Python.Run()` 方法,如下例所示:
```java
USER>set rslt = ##class(%SYS.Python).Run("print('hello world')")
hello world
```
针对于字典如何初始化呢,目前是有异常,望指教
文章
Michael Lei · 四月 17, 2022
在最近一次探索马里兰小镇的 "度假 "期间,我偶然发现了一家非常令人愉快的书店,在那里我愉快地消磨了一下午。我和我的家人都是读者,喜欢各种类型的书--新的、二手的、印刷的、电子的。我们尽量在当地购物,以帮助零售店保持运营。
这次访问促使我思考图书行业所发生的事情与我们的医疗保健系统所发生的事情之间的一些相似之处。
医疗保健行业与图书行业的趋势
数字化
我们阅读内容的格式已经发生了根本性的变化。在2020年,电子书几乎占美国市场的四分之一。音频书占美国图书收入的10亿美元。许多印刷书籍是按需出版的,而不是保存在库存中。同样,医疗保健早已不再是一个“伸出舌头说啊 ”的行业,基因组测试、由人工智能算法读取的X射线、可植入设备和远程医疗访问已经改变了医疗的面貌。
虚拟服务
书店现在有多种形式,医疗机构也是如此。订阅图书服务,从当地独立的小公司、大的连锁店、电子零售的网上订单。而与你的本地门诊竞争的是你手机上的一个应用程序。同样,你的治疗师可能是一个机器人,你的基层医疗服务可能由你社区附近药店的驻店医师提供,你可能在一个办公园区做手术。在所有这些竞争中,我们如何确保在我们需要时仍有健康的、提供全面服务的医院?
更智能的算法
分析和预测模型现在几乎和个人推荐一样重要。过去,当我想要一本书的建议,或者一个医生,我就会问朋友。虽然我仍然这样做,但我也同样有可能去看Goodreads,或查看在线医生评论。当我进行搜索时,亚马逊、苹果或谷歌也同样可能提供他们的建议,不管我是否要求它们。他们知道我是谁,我的购买模式是什么,我检查过哪些疾病和症状,以及在当地急诊科订购书籍或看医生的等待时间是什么。
合并和收购
无论你是卖书还是卖医疗服务,改变或死亡都是关键词。我们附近的一家大的巴诺书店(Barnes & Nobles,美国最大的实体书店)最近搬到了一个不到以前一半的地方。大多数独立书店出售的礼品和书籍一样多,而且许多书店同时出售新货和二手货。像Alibris这样的网站将当地的小企业与世界各地的买家联系起来。同样,根据普华永道的数据,2021年医疗保健业的合并和收购增长了56%,预计这一趋势在2022年还会继续。IQVIA艾昆纬研究所的一份报告发现,在主要的应用程序商店中,有大约35万个数字健康应用程序。而美国最大的零售商都在医疗保健领域进行了大量投资。例如,亚马逊是颠覆性的电子书业务的主要参与者,现在也有实体书店,它正积极地进入医疗保健服务领域,包括线上和线下服务。
对未来医疗的影响
对未来医疗的影响是什么?有趣的是,这两种业务都唤起了人们对也许是神话般的过去的相当大的怀念。虽然现在的实体书店比以前少了,但书籍实际上比以前更容易获得。对很大一部分人来说,亲切的家庭医生上门服务从来就不是那么容易的,而且不同地区采用的差异巨大的护理标准也不一定能带来最好的结果。
新世界秩序的便利性和选择令人难以置信地吸引人,无论我是在手机上购买一本书,还是在街上走到我附近的CVS公司购买Covid 疫苗加强针。但是,一方面浏览完所有这些选择也会让人感到困惑,另一方面我不想失去浏览当地商店的货架或者我年迈的母亲随时获得住院床位的选择。
没有任何整齐划一的策略可以向前推进。因此,作为一个消费者,我将继续光顾本地小店来帮助他们经营下去。作为一名医疗IT专业人士,我将继续关注如何利用信息来指导未来,带着一点点害怕,但更多的还是兴奋来展望未来。
关于作者:
Kathleen Aller负责InterSystems公司的医疗市场战略。她在医疗和技术领域有多年的经验,在分析、患者管理、电子健康记录、医疗信息共享以及质量和绩效评估方面有专长。
博客原文:https://www.intersystems.com/pulse-blog/browsing-the-future-of-healthcare
文章
Nicky Zhu · 一月 11, 2021
本文将描述通过ObjectScript包管理器(见https://openexchange.intersystems.com/package/ObjectScript-Package-Manager-2)运行单元测试的过程,包括测试覆盖率测量(见https://openexchange.intersystems.com/package/Test-Coverage-Tool)。
## ObjectScript中的单元测试
关于在ObjectScript中编写单元测试,已经有很好的文档,因此我就不再赘述了。您可以在这里找到单元测试教程:https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=TUNT_preface
最好的做法是将单元测试代码单独放在源代码树中,无论它只是“/tests”还是其他名字。在InterSystems中,我们最终使用/internal/testing/unit_tests/作为我们事实上的标准,这是有意义的,因为测试是内部/非发布的,而且除了单元测试还有其他类型的测试,但这对于简单的开源项目来说可能有点复杂。您可以在我们的一些GitHub仓库中看到这种结构。
从工作流的角度来看,这在VSCode中非常简单,您只需创建目录并将类放在里面。对于较老的以服务器为中心的源代码控制方法(Studio中使用的方法),您需要正确地地映射这个包,使用的方法会因源代码控制程序而异。
从单元测试类命名的角度来看,我个人的偏好(以及我的团队的最佳实践)是:
UnitTest.[.]
例如,如果在类MyApplication.SomeClass 中对方法Foo进行单元测试,单元测试类将被命名为UnitTest.MyApplication.SomeClass.Foo;如果测试是针对整个类的,那么名字就是UnitTest.MyApplication.SomeClass。
## ObjectScript 包管理器中的单元测试
让ObjectScript包管理器知道您的单元测试,很简单!只需按如下所示向module.xml中添加一行代码(来自https://github.com/timleavitt/ObjectScript-Math/blob/master/module.xml - 这是Open Exchange上的@Peter Steiwer的出色数学扩展包,我以它作为简单的正面例子):
```objectscript
...
```
这些代码的意思是:
- 单元测试位于模块根目录下的“tests”目录中。
- 单元测试在“UnitTest.Math”包中。这样很直观,因为被测试的类就在“Math”包中。
- 单元测试在包生命周期的“测试”阶段运行。(当然还有一个可以运行它们的“验证”阶段,这里不赘述。)
## 运行单元测试
对于上述定义的单元测试,包管理器提供了一些实用工具来运行它们。您仍然可以像平常使用%UnitTest.Manager那样设置^UnitTestRoot等,但下面的方法可能更简单,尤其在同一个环境下做几个项目的时候。
您可以克隆上述objectscript-math仓库,然后用 ```zpm "load /path/to/cloned/repo/" ```加载,或者在您自己的包上使用包名(和测试名)替换“objectscript-math”来尝试所有这些方法。
重新加载模块,然后运行所有单元测试:
```zpm "objectscript-math test" ```
只运行单元测试(不重新加载):
```zpm "objectscript-math test -only" ```
只运行单元测试(不重新加载)并提供详细输出:
```zpm "objectscript-math test -only -verbose" ```
运行一个特定的测试套件(指一个测试目录 - 在本例中,是UnitTest/Math/Utils中的所有测试)而不重新加载,并提供详细输出:
```zpm "objectscript-math test -only -verbose -DUnitTest.Suite=UnitTest.Math.Utils" ```
运行一个特定的测试用例(在本例中,是UnitTest.Math.Utils.TestValidateRange)而不重新加载,并提供详细输出:
```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange" ```
如果您只是想解决单个测试方法中的小问题:
```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange -DUnitTest.Method=TestpValueNull" ```
## 通过ObjectScript包管理器进行测试覆盖率测量
怎样评估单元测试的质量?测量测试覆盖率虽然并不全面,但至少有参考意义。早在2018年的全球峰会上,我就展示过。 见 - https://youtu.be/nUSeGHwN5pc。
首先需要安装“测试覆盖率”包:
```zpm "install testcoverage" ```
注意,并不需要ObjectScript包管理器才能安装/运行;可以在Open Exchange上了解更多信息:https://openexchange.intersystems.com/package/Test-Coverage-Tool
不过如果您已经在用ObjectScript包管理器,那么您可以更好地利用这个“测试覆盖率”工具。
运行测试前,需要指定测试所覆盖的类/routine宏。这一点很重要,因为在非常大的代码库中(例如,HealthShare),测试和收集项目中所有文件的测试覆盖率所需要的内存可能超出您的系统内存。(提一句,如果您感兴趣,可以使用逐行监视器的gmheap。)
文件列表在您的单元测试根目录下的coverage.list文件中;单元测试的不同子目录(套件)可以拥有它们自己的副本,以覆盖在测试套件运行时将跟踪的类/例程。
有关objectscript-math的简单示例,见:https://github.com/timleavitt/ObjectScript-Math/blob/master/tests/UnitTest/coverage.list;测试覆盖率工具用户指南有更详细的介绍。
要在启用测试覆盖率测量的情况下运行单元测试,只需再向命令添加一个参数,指定应使用TestCoverage.Manager而非%UnitTest.Manager 来运行测试:
```zpm "objectscript-math test -only -DUnitTest.ManagerClass=TestCoverage.Manager" ```
输出(即使是非详细模式)将包括一个URL,供您查看您的类/routine(宏)的哪些行被单元测试覆盖了,以及一些汇总统计信息。
## 接下来的步骤
这些能不能在CI中自动化?能不能报告单元测试的结果和覆盖率分数/差异?答案是:能!您可以使用Docker,Travis CI和codecov.io来试一下这个简单示例,见https://github.com/timleavitt/ObjectScript-Math;我打算以后写篇文章来详细讲讲,介绍几种不同的方法。
文章
姚 鑫 · 六月 14, 2023
# 第五十七章 镜像中断程序 - 在手动故障转移之前确定备份是否处于活动状态
## 在手动故障转移之前确定备份是否处于活动状态
假设有两个名为 `IRIS A` 和`IRIS B` 的故障转移成员。如果 `^MIRROR` 例程确认备份 (`IRIS B`) 在与主 (`IRIS A`) 丢失联系时处于活动状态,因此具有最新的来自 `IRIS A` 的日志数据,可以使用单个过程手动进行故障转移。当连接因主要故障而丢失时,不会造成数据丢失的风险。但是,当发生多个故障时,活动备份可能没有来自主服务器的所有最新日志数据,因为主服务器在连接丢失后继续运行了一段时间。
使用以下过程确定备份是否处于活动状态:
1. 确认 `IRIS` 实例 `IRIS A` 上的 `ISCAgent` 实际上已关闭(并确保它们在整个手动故障转移过程中保持关闭状态)。
2. 在 `IRIS B` 上,在终端的 `%SYS` 命名空间中运行 `^MIRROR` 例程(请参阅使用 `^MIRROR` 例程)。
3. 在主菜单中选择镜像管理,显示如下子菜单:
```java
1) Add mirrored database(s)
2) Remove mirrored database(s)
3) Activate or Catchup mirrored database(s)
4) Change No Failover State
5) Try to make this the primary
6) Connect to Mirror
7) Stop mirroring on this member
8) Modify Database Size Field(s)
9) Force this node to become the primary
10) Promote Async DR member to Failover member
11) Demote Backup member to Async DR member
12) Mark an inactive database as caught up
13) Manage mirror dejournaling on async member (disabled)
14) Pause dejournaling for database(s)
```
4. 选择 `Force this node to become the primary` 选项。如果在联系丢失时备份处于活动状态,则会显示如下消息:
```java
This instance was an active backup member the last time it was
connected so if the primary has not done any work since that time,
this instance can take over without having to rebuild the mirror
when the primary reconnects. If the primary has done any work
beyond this point (file #98),
C:\InterSystems\MyIRIS\mgr\journal\MIRROR-GFS-20180815.009
then the consequence of forcing this instance to become the primary is
that some operations may be lost and the other mirror member may need
to be rebuilt from a backup of this node before it can join as
a backup node again.
Do you want to continue?
```
如果有权访问主要文件的日志文件,则可以在继续之前确认引用的文件是最新的。
如果在与主服务器失去联系时备份未处于活动状态,则会显示如下消息:
```java
Warning, this action can result in forcing this node to become
the primary when it does not have all of the journal data which
has been generated in the mirror. The consequence of this is that
some operations may be lost and the other mirror member may need
to be rebuilt from a backup of this node before it can join as
a backup node again.
Do you want to continue?
```
## 手动故障转移到活动备份
如果 `^MIRROR` 例程的 `Force this node to become the primary` 选项确认备份在失去与主节点的连接时处于活动状态,请完成手动故障转移过程,如下所示:
1. 在要继续吗?提示继续该过程。 `Force this node to become the primary` 选项等待 `60` 秒以使镜像成员成为主要节点。如果操作未在 `60` 秒内成功完成,`^MIRROR` 报告操作可能未成功并指示您检查消息日志以确定操作是失败还是仍在进行中。
2. 一旦 `^MIRROR` 例程确认备份已成为主要备份,请在可以这样做时重新启动 `IRIS A`。当 `IRIS` 实例重新启动时, `IRIS A` 作为备份加入镜像。
## 备份不活动时手动故障转移
即使 `^MIRROR` 例程未确认备份 ( `IRIS B`) 在与主 ( `IRIS A`) 失去连接时处于活动状态,仍然可以使用以下过程继续手动故障转移过程,但是如果这样做,会有数据丢失的风险。如本程序所述,可以在手动故障转移之前将最新的镜像日志文件从 `IRIS A`(如果有权访问)复制到 `IRIS` B,从而最大限度地降低这种风险。
1. 如果有权访问主服务器的镜像日志文件,请将最新的文件复制到 `IRIS B`,从 `IRIS B` 上的最新日志文件开始,然后包括来自 `IRIS A` 的任何后续文件。例如,如果 `MIRROR-MIRRORA-20180220.001`是 `IRIS B` 上的最新文件,复制 `MIRROR-MIRRORA-20180220.001` 和 `IRIS A` 上的任何更新文件。检查文件的权限和所有权,并在必要时更改它们以匹配现有日志文件。
2. 如果接受数据丢失的风险,请在提示时输入 `y` 以确认要继续;备份成为主要的。 `Force this node to become the primary` 选项等待 `60` 秒以使镜像成员成为主要节点。如果操作未在 `60` 秒内成功完成,`^MIRROR` 报告操作可能未成功并指示您检查消息日志以确定操作是失败还是仍在进行中。
3. 一旦 `^MIRROR` 例程确认备份已成为主要备份,请在可以这样做时重新启动 `IRIS A`。
- 如果 `IRIS A` 在 `IRIS` 实例重新启动时加入镜像作为备份,则不需要进一步的步骤。任何在故障成员上但不在当前主成员上的日志数据都已被丢弃。
- 如果在 `IRIS` 实例重新启动时 `IRIS A` 无法加入镜像,如重建镜像成员中描述的引用不一致数据的消息日志消息所示 `IRIS A` 上的最新数据库更改晚于最新的日志数据当 `IRIS B` 被迫成为主服务器时,它会出现在 `IRIS B` 上。要解决此问题,请按照该部分中的描述重建 `IRIS A`。
文章
Michael Lei · 六月 18, 2023
在数字化时代,数据的重要性无可置疑。数据作为新型生产要素,不仅在宏观政策层面得到党和政府的大力推动,也是医院高质量发展的关键和改变医疗行业的驱动力。随着医疗信息化的迅猛发展,我们正迈向一个数据随处可及、人人可用易用的医疗信息化时代。这一时代将数据与人的需求相结合,致力于让数据能“主动”找到需要他们的医护人员和患者,每一个行业从业者,都应致力于为医护人员和患者提供简单易用的软件解决方案,减少工作量,提高效率,推动医疗行业的进步。
数据与人的融合是实现医疗行业数字化转型的核心。当然,医疗数据的收集、存储和管理对于提供高质量的医疗服务至关重要。然而,仅仅有大量的数据并不足够,我们需要将数据与人的需求紧密结合起来。这意味着我们应该让更多的数据关联起来,并且能服务于更多的人群,让患者能够随时随地访问他们的电子病历,让医生和科研人员也能及时有效地获取病人在医院围墙内外进行治疗和健康管理的数据,并且以直观易懂的方式呈现给医护人员和患者,使他们能够快速、准确地获取所需的信息。数据的融合还包括将不同来源的数据整合起来,为医护人员提供全面、完整的视图,同时基于医疗诊断的规则,不管是通过CDSS的形式,还是通过ChatBot(聊天机器人),帮助他们做出更好的决策。
实现数据和人的融合要按照人的需求投放数据。数字化转型的重要目标是为医护人员和患者提供所需的数据,以支持决策和治疗过程。这意味着我们应该了解用户的需求,将数据按照他们的角色、职责和关注点进行分类和投放。医生可能需要即时的患者数据、病历历史和最新的医学研究,而患者可能需要查看自己的健康记录、预约医生和接收个性化的健康建议。通过根据人的需求进行数据投放,新型软件可以提供个性化的服务和支持,形成千人千面,为每个用户提供有价值的信息。
简单易用是实现数字化转型成功的另一个关键。医护人员和患者使用的软件解决方案应该简单易用,不需要复杂的培训和技术知识。界面应该简单、直观、友好,操作流程简化和优化,以确保用户能够快速上手并高效地使用软件。简单易用的软件不仅能够减少用户的学习曲线和工作负担,还能提高用户满意度和工作效率。(比如Apple的医疗软件Apple Health,通过FHIR 技术,通过一个app能够连接数千家医院的病历数据,让患者可以通过一个app实现多家医院的互联网服务和数据整合)
无论是数字化转型还是高质量发展,软件为人服务始终是医疗信息化的核心宗旨。我们应该将软件看作是为人服务的工具,旨在帮助医护人员提供更好的医疗服务,提升患者的体验和健康结果。软件应该以用户体验为中心,并不断优化和改进,不断进行供给侧改革,以满足不断变化和不同人群的需求,而不是增加负担。
最后,数据会在安全可靠的前提下进行传递和流通。在互联网发展的早期时代,由于无法可依,野蛮生长,数据的滥用、隐私保护等存在很大问题。但随着《数据安全法》等法律法规的发布,相信未来的医疗行业数据一定会在更加安全、可靠、合规的前提下进行有序流动。
在未来的医疗信息化发展中,数据与人的关系将变得更加密不可分。通过数据的融合、按需投放、简单易用、安全可靠和以人为本的新一代软件,我们可以实现数据随处可及、人人可用易用的医疗信息化目标。这将为医护人员和患者提供更好的工作环境和医疗体验,推动整个医疗行业向前迈进。InterSystems公司作为创新性的数据平台解决方案供应商,我们始终致力于助力合作伙伴开发创新的解决方案,与合作伙伴一起共同实现这一愿景,改善医疗服务的质量和效率,提高患者体验的获得感的同时帮助医院降本增效,实现高质量发展。
文章
Jingwei Wang · 九月 16, 2022
连接前准备:
Python 开发环境
DB-API驱动:irispython wheel 文件
Connection String
步骤:
安装irispython wheel 文件
pip install intersystems_irispython-3.2.0-py3-none-any.whl
Connection String:其中import iris 用来导入iris, connection = iris.connect是connection string。connection.close()用来断开连接。
import iris
def main():
connection_string = "localhost:1972/USER"
username = "SQLAdmin"
password = "deployment-password"
connection = iris.connect(connection_string, username, password)
# when finished, use the line below to close the connection
# connection.close()
if __name__ == "__main__":
main()
文章
Michael Lei · 五月 30, 2022
我的团队在在红帽OpenShift容器平台上运行IRIS互操作性解决方案。我想在数据被存储在Mirror的数据pods中的情况下,测试运行中的webgateway pods和计算节点 pods能处理多少消息。
为了增加测试难度,我部署了多个feeder容器,并在每个feeder上安排了任务,以在同一时间发送大量的消息。为了进入下一阶段的测试,我希望有多种类型的测试文件可以按需使用。我创建了test-data应用程序,能够请求生成大量的多种类型的文件。
我早期的一些测试依赖于复制一个样本文件和处理它。这在一次只复制一份的情况下效果不错。为了获得同一样本文件的许多副本,MakeFile函数获取一个样本文件、保存其副本、并以唯一的时间戳进行重命名。MakeFiles函数有一个参数,用于确定要制作的文件数量。
我找到了一个样本文件,它的输入和输出都是带分隔符和固定符的。我把它包含在我的应用程序中,并添加了一个转换来操作测试数据文件。在这种情况下,我把测试文件中的识别ID号替换成在一个类方法中生成的识别ID号,并且是随着文件而递增的。
我想在处理后审查测试文件中的数据,我喜欢看到一系列有顺序的数字,而不是一系列随机数字。
具体代码下载:https://openexchange.intersystems.com/package/test-data
文章
姚 鑫 · 六月 10, 2022
# 第五章 数据类型(四)
# Strings
`%Library.String` 数据类型支持的最大字符串长度为 `3,641,144` 个字符。通常,极长的字符串应分配为 `%Stream.GlobalCharacter` 数据类型之一。
因为 IRIS 支持 xDBC 协议 50 和更高版本,所以没有强制执行 ODBC 或 JDBC 字符串长度限制。如果 IRIS 实例和 ODBC 驱动程序支持不同的协议,则使用两个协议中较低的一个。实际使用的协议记录在 ODBC 日志中。
请注意,默认情况下 IRIS 建立系统范围的 ODBC `VARCHAR` 最大长度为 `4096`;此 ODBC 最大长度是可配置的。
# 列表结构
IRIS 支持列表结构数据类型 `%List`(数据类型类 `%Library.List`)。这是一种压缩的二进制格式,不会映射到 SQL 的相应本机数据类型。在其内部表示中,它对应于数据类型 `VARBINARY`,默认 `MAXLEN` 为 `32749`。 IRIS 支持列表结构数据类型 `%ListOfBinary`(数据类型类 `%Library.ListOfBinary`)对应于数据类型 `VARBINARY`,默认 `MAXLEN` 为 `4096`。
因此,动态 SQL 不能在 `WHERE` 子句比较中使用 `%List` 数据。也不能使用 `INSERT` 或 `UPDATE` 来设置 `%List` 类型的属性值。
动态 SQL 将列表结构化数据的数据类型返回为 `VARCHAR`。要确定查询中的字段是数据类型 `%List` 还是 `%ListOfBinary`,可以使用 `select-item columns metadata isList` 布尔标志。这些数据类型的 `CType`(客户端数据类型)整数代码是 `6`。
如果使用 ODBC 或 JDBC 客户端,则使用 `LogicalToOdbc` 转换将 `%List` 数据投影到 `VARCHAR` 字符串数据。列表被投影为一个字符串,其元素由逗号分隔。这种类型的数据可以用在 `WHERE` 子句以及 `INSERT` 和 `UPDATE` 语句中。请注意,默认情况下,IRIS 建立系统范围的 ODBC `VARCHAR` 最大长度为 `4096`;此 ODBC 最大长度是可配置的。
SQL 支持八种列表函数:`$LIST`、`$LISTBUILD`、`$LISTDATA`、`$LISTFIND`、`$LISTFROMSTRING`、`$LISTGET`、`$LISTLENGTH` 和 `$LISTTOSTRING`。 ObjectScript 支持三个额外的列表函数:`$LISTVALID` 用于确定表达式是否为列表,`$LISTSAME` 用于比较两个列表,以及 `$LISTNEXT` 用于从列表中顺序检索元素。
# 位数据类型
`BIT (%Library.Boolean)` 数据类型接受 `0`、`1` 和 `NULL` 作为有效值。
- 在逻辑和 ODBC 模式下,唯一接受的值是 `0`、`1` 和 `NULL`。
- 在显示模式下,`DisplayToLogical` 方法首先将非空输入值转换为 `0` 或 `1`,如下所示:
- 非零数字或数字字符串 = 1,例如 `3`, `'0.1'`, `'-1'`, `'7dwarves'`
- 非数字字符串 = 0。例如,`“true”`或`“false”`。
- 空字符串 = 0。例如`''`。
# 流数据类型
`Stream` 数据类型对应于 IRIS 类属性数据类型 `%Stream.GlobalCharacter`(用于 `CLOB`)和 `%Stream.GlobalBinary`(用于 `BLOB`)。这些数据类型类可以使用指定的 `LOCATION` 参数定义流字段,或者省略该参数并默认为系统定义的存储位置。
具有 `Stream` 数据类型的字段不能用作大多数 SQL 标量、聚合或一元函数的参数。尝试这样做会生成 `SQLCODE -37` 错误代码。
具有 `Stream` 数据类型的字段不能用作大多数 SQL 谓词条件的参数。尝试这样做会生成 `SQLCODE -313` 错误代码。
Stream 数据类型在索引中的使用以及在执行插入和更新时也受到限制。
# 串行数据类型
具有 `SERIAL (%Library.Counter)` 数据类型的字段可以采用用户指定的正整数值,或者 IRIS 可以为其分配一个连续的正整数值。 `%Library.Counter` 扩展了 `%Library.BigInt`。
`INSERT` 操作为 `SERIAL` 字段指定以下值之一:
- 无值、0(零)或非数字值: IRIS 忽略指定值,而是将此字段的当前串行计数器值增加 1,并将结果整数插入该字段。
- 正整数值:IRIS 将用户指定的值插入到字段中,并将该字段的串行计数器值更改为此整数值。
因此,`SERIAL` 字段包含一系列增量整数值。这些值不一定是连续的或唯一的。例如,以下是 SERIAL 字段的有效值系列:`1、2、3、17、18、25、25、26、27`。连续整数要么是 IRIS 生成的,要么是用户提供的;非连续整数是用户提供的。如果希望 `SERIAL` 字段值是唯一的,则必须对该字段应用 `UNIQUE` 约束。
`UPDATE` 操作对自动分配的 `SERIAL` 计数器字段值没有影响。但是,使用 `INSERT OR UPDATE` 执行的更新会导致对 `SERIAL` 字段的后续插入操作跳过整数序列。
如果该字段当前没有值(`NULL`),或者它的值为 `0`,则 `UPDATE` 操作只能更改串行字段值。否则,将生成 `SQLCODE -105` 错误。
IRIS 对表中的 `SERIAL` 字段的数量没有限制。
# ROWVERSION 数据类型
`ROWVERSION` 数据类型定义了一个只读字段,该字段包含一个唯一的系统分配的正整数,从 1 开始。 IRIS 分配顺序整数作为每个插入、更新或 `%Save` 操作的一部分。这些值不是用户可修改的。
IRIS 在命名空间范围内维护一个单行版本计数器。命名空间中包含 `ROWVERSION` 字段的所有表共享相同的行版本计数器。因此,`ROWVERSION` 字段提供行级版本控制,允许确定对命名空间中一个或多个表中的行进行更改的顺序。
每个表只能指定一个 `ROWVERSION` 数据类型的字段。
`ROWVERSION` 字段不应包含在唯一键或主键中。 `ROWVERSION` 字段不能是 `IDKey` 索引的一部分。
# ROWVERSION 和 SERIAL 计数器
作为 `INSERT` 操作的一部分,`ROWVERSION` 和 `SERIAL` `(%Library.Counter)` 数据类型字段都从内部计数器接收顺序整数。但是这两个计数器有很大的不同,并且用于不同的目的:
- `ROWVERSION` 计数器位于命名空间级别。 `SERIAL` 计数器位于表级别。这两个计数器完全相互独立,独立于 `RowID` 计数器。
- `ROWVERSION` 计数器通过插入、更新或 `%Save` 操作递增。 `SERIAL` 计数器仅由插入操作递增。使用 `INSERT OR UPDATE` 执行的更新可能会导致 `SERIAL` 计数器序列出现间隙。
- `ROWVERSION` 字段值不能由用户指定;该值始终由 `ROWVERSION` 计数器提供。如果没有为该字段指定值,则在插入期间从表的内部计数器提供一个 `SERIAL` 字段值。如果插入提供了一个 `SERIAL` 整数值,则插入该值而不是当前计数器值:
- 如果插入提供的 `SERIAL` 字段值大于当前内部计数器值, IRIS 将该值插入该字段并将内部计数器重置为该值。
- 如果插入提供的 `SERIAL` 字段值小于当前计数器值, IRIS 不会重置内部计数器。
- 插入可以提供 `SERIAL` 字段值作为负整数或小数。 IRIS 将小数截断为其整数部分。如果提供的 `SERIAL` 字段值为 `0` 或 `NULL`, IRIS 将忽略用户提供的值并插入当前的内部计数器值。
- 不能更新现有的 `SERIAL` 字段值。
- `ROWVERSION` 字段值始终是唯一的。因为可以插入用户指定的 `SERIAL` 字段值,所以必须指定 `UNIQUE` 字段约束以保证唯一的 `SERIAL` 字段值。
- 无法重置 `ROWVERSION` 计数器。 `TRUNCATE TABLE` 重置 `SERIAL` 计数器;对所有行执行 `DELETE` 不会重置 `SERIAL` 计数器。
- 每个表只允许一个 `ROWVERSION` 字段。可以在一个表中指定多个 `SERIAL` 字段。
# ODBC / JDBC 公开的 DDL 数据类型
ODBC 公开了 DDL 数据类型的子集,并将其他数据类型映射到该数据类型的子集。这些映射是不可逆的。例如,语句 `CREATE TABLE mytable (f1 BINARY)` 创建一个 IRIS 类,该类作为 `mytable (f1 VARBINARY)` 投影到 ODBC。 IRIS 列表数据类型作为 `VARCHAR` 字符串投影到 ODBC。
ODBC 公开以下数据类型:`BIGINT`、`BIT`、`DATE`、`DOUBLE`、`GUID`、`INTEGER`、`LONGVARBINARY`、`LONGVARCHAR`、`NUMERIC`、`OREF`、`POSIXTIME`、`SMALLINT`、`TIME`、`TIMESTAMP`、`TINYINT`、`VARBINARY`、`VARCHAR`。请注意,默认情况下 IRIS 建立系统范围的 ODBC `VARCHAR` 最大长度为 `4096`;此 ODBC 最大长度是可配置的。
当这些 ODBC/JDBC 数据类型值之一映射到 SQL 时,会发生以下操作: 使用 `$DOUBLE` 强制转换 `DOUBLE` 数据。 `NUMERIC` 数据使用 `$DECIMAL` 进行转换。
`GUID` 数据类型对应于 `SQL UNIQUEIDENTIFIER` 数据类型。未能为 `GUID / UNIQUEIDENTIFIER` 字段指定有效值会生成 `#7212` 一般错误。要生成 `GUID` 值,请使用 `%SYSTEM.Util.CreateGUID()` 方法。
# 查询元数据返回数据类型
可以使用动态 SQL 返回有关查询的元数据,包括查询中指定列的数据类型。
以下动态 SQL 示例为 `Sample.Person` 和 `Sample.Employee` 中的每个列返回列名和 ODBC 数据类型的整数代码:
```java
/// d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType()
ClassMethod QueryMetadataReturnsDataType()
{
s myquery = "SELECT * FROM Sample.Person"
s tStatement = ##class(%SQL.Statement).%New()
s tStatus = tStatement.%Prepare(myquery)
s x = tStatement.%Metadata.columnCount
while x > 0 {
s column = tStatement.%Metadata.columns.GetAt(x)
w !,x," ",column.colName," ",column.ODBCType
s x = x-1
}
w !,"end of columns"
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType()
16 Office_Zip 12
15 Office_Street 12
14 Office_State 12
13 Office_City 12
12 Home_Zip 12
11 Home_Street 12
10 Home_State 12
9 Home_City 12
8 Spouse 4
7 SSN 12
6 Name 12
5 FavoriteColors 12
4 DOB 9
3 Age 4
2 AddDateTime 11
1 ID 4
end of columns
```
```java
/// d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType1()
ClassMethod QueryMetadataReturnsDataType1()
{
s myquery = "SELECT * FROM Sample.Employee"
s tStatement = ##class(%SQL.Statement).%New()
s tStatus = tStatement.%Prepare(myquery)
s x = tStatement.%Metadata.columnCount
while x > 0 {
s column = tStatement.%Metadata.columns.GetAt(x)
w !,x," ",column.colName," ",column.ODBCType
s x = x - 1
}
w !,"end of columns"
}
```
```java
DHC-APP>d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType1()
20 Office_Zip 12
19 Office_Street 12
18 Office_State 12
17 Office_City 12
16 Home_Zip 12
15 Home_Street 12
14 Home_State 12
13 Home_City 12
12 Title 12
11 Spouse 4
10 Salary 4
9 SSN 12
8 Picture -4
7 Notes -1
6 Name 12
5 FavoriteColors 12
4 DOB 9
3 Company 4
2 Age 4
1 ID 4
end of columns
```
列出结构化数据(例如 `Sample.Person` 中的 `FavoriteColors` 列)返回数据类型 `12 (VARCHAR)`,因为 ODBC 将 ObjectScript `%List` 数据类型值表示为逗号分隔值的字符串。
`Steam` 数据(例如 `Sample.Employee` 中的 `Notes` 和 `Picture` 列)返回数据类型 -1 (`LONGVARCHAR`) 或 -4 (`LONGVARBINARY`)。
`ROWVERSION` 字段返回数据类型 `-5`,因为 `%Library.RowVersion` 是 `%Library.BigInt` 的子类。
文章
Hao Ma · 五月 17, 2023
Caché, IRIS在系统产生了最严重的问题时会产生错误信息并通知客户,但这并不足够。一是客户需要更多更灵活的通知消息,二是客户通常会有第3方的监控系统,因此得到Cache, IRIS的监控指标是必须的。
在所有的指标中,用户最关心的是以下几类:
- 硬件资源的使用,CPU, 内存, IO性能
- 数据库使用的硬盘的占用
- Cache, IRIS Journal的硬盘占有
- Mirror的状态
- License的使用情况
- Caché的性能指标
除此之外,第3方监控系统还需要获得Caché的一些系统信息,比如版本,instance名字等等。
## 指标的获得
有以下几个获得指标的方法
### 1. 系统仪表板及其Web服务
Caché的系统仪表板显示的数据包括:系统性能;系统运行状态 (运行时间,上一次备份,数据库,Journal状况等; 事务和进程情况;软件许可使用情况;任务,ECP等,还有就是错误和警告的数量。
系统仪表板包含4个子面板:Global和Routine统计数据;ECP统计数据;磁盘和缓冲器统计数据;系统资源统计数据。(IRIS和Caché稍有不同,缺少了“磁盘和缓冲器统计数据”) 。它们分别对应主仪表板中的相应模块,给出了更详细的数据。

Monitoring Web Service默认随系统的启动而开启,远程用户或者第3方系统可以访问该服务得到所以的系统监控指标,也就是系统仪表板上的所有数据。用户还可以使用该服务订阅Caché的警告消息。访问地址是:http://localhost:57772/csp/sys/SYS.WSMon.Service.cls, 或者IRIS : http://localhost:52773/csp/sys/SYS.WSMon.Service.cls
*Monitoring Web Service的WSDL*

调用服务方法的结果示例: GetDashboard()
```xml
204604.00
50183591134
2396232998
264876546
4040613709
60130
585372
77743.51
OK
0
OK
0
OK
OK
10d 4h 38m
Normal
Normal
Normal
18304233
Normal
Normal
39
1
0
0
256
HealthShare 2018.1.1, Enterprise:256, Concurrent User, Platform Independent, Multi, DeepSee, DSV Reporting, NLP
2
4
1
2
```
### 2. REST API
这是IRIS的新特性,参考文档在[这里](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_rest)。 下面是我的一个测试环境的指标列表:
```
iris_cpu_usage 0
iris_csp_activity{id="127.0.0.1:52773"} 44
iris_csp_actual_connections{id="127.0.0.1:52773"} 7
iris_csp_gateway_latency{id="127.0.0.1:52773"} .772
iris_csp_in_use_connections{id="127.0.0.1:52773"} 1
iris_csp_private_connections{id="127.0.0.1:52773"} 0
iris_csp_sessions 1
iris_cache_efficiency 57.260
iris_db_expansion_size_mb{id="DEMO"} 0
iris_db_expansion_size_mb{id="ENSLIB"} 0
iris_db_expansion_size_mb{id="HCC"} 0
iris_db_expansion_size_mb{id="HSCUSTOM"} 0
iris_db_expansion_size_mb{id="HSLIB"} 0
iris_db_expansion_size_mb{id="HSSYS"} 0
iris_db_expansion_size_mb{id="IRISAUDIT"} 0
iris_db_expansion_size_mb{id="IRISLOCALDATA"} 0
iris_db_expansion_size_mb{id="IRISSYS"} 0
iris_db_expansion_size_mb{id="IRISTEMP"} 0
iris_db_expansion_size_mb{id="MOCKSYS"} 0
iris_db_expansion_size_mb{id="OEESP"} 0
iris_db_expansion_size_mb{id="SMART"} 0
iris_db_expansion_size_mb{id="USER"} 0
iris_db_free_space{id="DEMO"} 10
iris_db_free_space{id="ENSLIB"} 17
iris_db_free_space{id="HCC"} 11
iris_db_free_space{id="HSCUSTOM"} 9.5
iris_db_free_space{id="HSLIB"} 131
iris_db_free_space{id="HSSYS"} 9
iris_db_free_space{id="IRISAUDIT"} 8.7
iris_db_free_space{id="IRISLOCALDATA"} 19
iris_db_free_space{id="IRISSYS"} 9.3
iris_db_free_space{id="IRISTEMP"} 161
iris_db_free_space{id="MOCKSYS"} 8.7
iris_db_free_space{id="OEESP"} 6.9
iris_db_free_space{id="SMART"} 16
iris_db_free_space{id="USER"} 4.9
iris_db_latency{id="DEMO"} 0.566
iris_db_latency{id="ENSLIB"} 0.143
iris_db_latency{id="HCC"} 0.191
iris_db_latency{id="HSCUSTOM"} 0.163
iris_db_latency{id="HSLIB"} 0.153
iris_db_latency{id="HSSYS"} 0.143
iris_db_latency{id="IRISAUDIT"} 0.133
iris_db_latency{id="IRISSYS"} 0.200
iris_db_latency{id="IRISTEMP"} 0.145
iris_db_latency{id="MOCKSYS"} 0.157
iris_db_latency{id="OEESP"} 0.565
iris_db_latency{id="SMART"} 1.113
iris_db_latency{id="USER"} 0.236
iris_db_max_size_mb{id="DEMO"} 0
iris_db_max_size_mb{id="ENSLIB"} 0
iris_db_max_size_mb{id="HCC"} 0
iris_db_max_size_mb{id="HSCUSTOM"} 0
iris_db_max_size_mb{id="HSLIB"} 0
iris_db_max_size_mb{id="HSSYS"} 0
iris_db_max_size_mb{id="IRISAUDIT"} 0
iris_db_max_size_mb{id="IRISLOCALDATA"} 0
iris_db_max_size_mb{id="IRISSYS"} 0
iris_db_max_size_mb{id="IRISTEMP"} 0
iris_db_max_size_mb{id="MOCKSYS"} 0
iris_db_max_size_mb{id="OEESP"} 0
iris_db_max_size_mb{id="SMART"} 0
iris_db_max_size_mb{id="USER"} 0
iris_db_size_mb{id="HCC",dir="/usr/irissys/mgr/HCC/"} 365
iris_db_size_mb{id="DEMO",dir="/external/demo/"} 229
iris_db_size_mb{id="USER",dir="/usr/irissys/mgr/user/"} 11
iris_db_size_mb{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 1219
iris_db_size_mb{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 21
iris_db_size_mb{id="OEESP",dir="/external/oeesp/"} 102
iris_db_size_mb{id="SMART",dir="/external/smart/"} 162
iris_db_size_mb{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 209
iris_db_size_mb{id="IRISSYS",dir="/usr/irissys/mgr/"} 127
iris_db_size_mb{id="MOCKSYS",dir="/usr/irissys/mgr/MOCKSYS/"} 11
iris_db_size_mb{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 21
iris_db_size_mb{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 162
iris_db_size_mb{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 11
iris_db_size_mb{id="IRISLOCALDATA",dir="/usr/irissys/mgr/irislocaldata/"} 21
iris_directory_space{id="HCC",dir="/usr/irissys/mgr/HCC/"} 17142
iris_directory_space{id="DEMO",dir="/external/demo/"} 34188
iris_directory_space{id="USER",dir="/usr/irissys/mgr/user/"} 17142
iris_directory_space{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 17142
iris_directory_space{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 17142
iris_directory_space{id="OEESP",dir="/external/oeesp/"} 34188
iris_directory_space{id="SMART",dir="/external/smart/"} 34188
iris_directory_space{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 17142
iris_directory_space{id="IRISSYS",dir="/usr/irissys/mgr/"} 17142
iris_directory_space{id="MOCKSYS",dir="/usr/irissys/mgr/MOCKSYS/"} 17142
iris_directory_space{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 17142
iris_directory_space{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 17142
iris_directory_space{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 17142
iris_disk_percent_full{id="HCC",dir="/usr/irissys/mgr/HCC/"} 83.61
iris_disk_percent_full{id="DEMO",dir="/external/demo/"} 92.83
iris_disk_percent_full{id="USER",dir="/usr/irissys/mgr/user/"} 83.61
iris_disk_percent_full{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 83.61
iris_disk_percent_full{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 83.61
iris_disk_percent_full{id="OEESP",dir="/external/oeesp/"} 92.83
iris_disk_percent_full{id="SMART",dir="/external/smart/"} 92.83
iris_disk_percent_full{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 83.61
iris_disk_percent_full{id="IRISSYS",dir="/usr/irissys/mgr/"} 83.61
iris_disk_percent_full{id="MOCKSYS",dir="/usr/irissys/mgr/MOCKSYS/"} 83.61
iris_disk_percent_full{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 83.61
iris_disk_percent_full{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 83.61
iris_disk_percent_full{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 83.61
iris_ecp_conn 0
iris_ecp_conn_max 2
iris_ecp_connections 0
iris_ecp_latency 0
iris_ecps_conn 0
iris_ecps_conn_max 1
iris_glo_a_seize_per_sec 0
iris_glo_n_seize_per_sec 0
iris_glo_ref_per_sec 0
iris_glo_ref_rem_per_sec 0
iris_glo_seize_per_sec 0
iris_glo_update_per_sec 0
iris_glo_update_rem_per_sec 0
iris_jrn_block_per_sec 0
iris_jrn_entry_per_sec 0
iris_jrn_free_space{id="WIJ",dir="default"} 11802.25
iris_jrn_free_space{id="primary",dir="/usr/irissys/mgr/journal/"} 11802.25
iris_jrn_free_space{id="secondary",dir="/usr/irissys/mgr/journal/"} 11802.25
iris_jrn_size{id="WIJ"} 237
iris_jrn_size{id="primary"} 1
iris_jrn_size{id="secondary"} 0
iris_license_available 4
iris_license_consumed 1
iris_license_percent_used 20
iris_log_reads_per_sec 0
iris_obj_a_seize_per_sec 0
iris_obj_del_per_sec 0
iris_obj_hit_per_sec 0
iris_obj_load_per_sec 0
iris_obj_miss_per_sec 0
iris_obj_new_per_sec 0
iris_obj_seize_per_sec 0
iris_page_space_percent_used 30
iris_phys_mem_percent_used 61
iris_phys_reads_per_sec 0
iris_phys_writes_per_sec 0
iris_process_count 30
iris_rtn_a_seize_per_sec 0
iris_rtn_call_local_per_sec 0
iris_rtn_call_miss_per_sec 0
iris_rtn_call_remote_per_sec 0
iris_rtn_load_per_sec 0
iris_rtn_load_rem_per_sec 0
iris_rtn_seize_per_sec 0
iris_sam_get_db_sensors_seconds .021465
iris_sam_get_interop_sensors_seconds .000165
iris_sam_get_jrn_sensors_seconds .002873
iris_sam_get_sql_sensors_seconds .000258
iris_sam_get_wqm_sensors_seconds .000125
iris_smh_available{id="Classes_Instantiated"} 47360
iris_smh_available{id="DB_Name_&_Directory"} 64326
iris_smh_available{id="Global_Mapping"} 60384
iris_smh_available{id="Lock_Table"} 192816
iris_smh_available{id="Routine_Buffer_In_Use_Table"} 61952
iris_smh_available{id="Security_System"} 0
iris_smh_available{id="Semaphores_objects"} 61440
iris_smh_available{id="TTY_Hash_Table"} 32760
iris_smh_percent_full{id="Classes_Instantiated"} 98
iris_smh_percent_full{id="DB_Name_&_Directory"} 2
iris_smh_percent_full{id="Global_Mapping"} 69
iris_smh_percent_full{id="Lock_Table"} 2
iris_smh_percent_full{id="Routine_Buffer_In_Use_Table"} 5
iris_smh_percent_full{id="Semaphores_objects"} 6
iris_smh_percent_full{id="TTY_Hash_Table"} 50
iris_smh_total 3801088
iris_smh_total_percent_full 6
iris_smh_used{id="Classes_Instantiated"} 1984256
iris_smh_used{id="DB_Name_&_Directory"} 1210
iris_smh_used{id="Global_Mapping"} 136224
iris_smh_used{id="Lock_Table"} 3792
iris_smh_used{id="Routine_Buffer_In_Use_Table"} 3584
iris_smh_used{id="Security_System"} 65536
iris_smh_used{id="Semaphores_objects"} 4096
iris_smh_used{id="TTY_Hash_Table"} 32776
iris_sql_queries_avg_runtime{id="%SYS"} .000057375
iris_sql_queries_avg_runtime{id="all"} .000057375
iris_sql_queries_avg_runtime_std_dev{id="%SYS"} .00000000001060414620168293732
iris_sql_queries_avg_runtime_std_dev{id="all"} .00000000001060414620168293732
iris_sql_queries_per_second{id="%SYS"} .1333333333333333333
iris_sql_queries_per_second{id="all"} .1333333333333333333
iris_system_alerts 3
iris_system_alerts_log 3
iris_system_alerts_new 1
iris_system_state 2
iris_trans_open_count 0
iris_trans_open_secs 0
iris_trans_open_secs_max 0
iris_wd_buffer_redirty 2
iris_wd_buffer_write 2
iris_wd_cycle_time 6
iris_wd_proc_in_global 0
iris_wd_size_write 16
iris_wd_sleep 9969
iris_wd_temp_queue 51
iris_wd_temp_write 0
iris_wdwij_time 3
iris_wd_write_time 2
iris_wij_writes_per_sec 0
iris_wqm_active_worker_jobs{id="SYS"} 0
iris_wqm_commands_per_sec{id="SYS"} 103
iris_wqm_globals_per_sec{id="SYS"} 4
iris_wqm_max_active_worker_jobs{id="SYS"} 0
iris_wqm_max_work_queue_depth{id="SYS"} 0
iris_wqm_waiting_worker_jobs{id="SYS"} 1
```
### 3. 通过SNMP
在Caceh' 和IRIS的安装文件夹的SNMP子目录下, 你可以找到.mib文件, 分别是ISC-cache.mib或者ISC-IRIS.mib。如果是ensemble或者health connect, 还会有isc-ensemble.mib。
通过SNMP GET, 您可以使用第3方工具的SNMP客户端获得Caché和IRIS的全部指标。
### 4. 历史监视器(History Monitor)
如果用户没有专用的监控系统,那么使用历史监视器可以是一个很好的选择。 历史监视器把数据库的读写和系统使用情况的历史数据存在一个表里, 用户可以通过SQL访问。
它的统计的内容为:
- CPU Usage
- 数据库的大小和Journal的大小
- Global Reference and Updates
- 物理读写(Physical reads and writes)
- License使用率
这是一个轻量级的监控工具,几乎不对生产环境增加有意义的开销。它收集数据的表的尺寸也很小, 因此SQL查询的速度很快。 (即使收集小时级别的统计数据,每年的硬盘占用也只有130MB左右)。所有它经常被用于建立一个系统性能的基准线,便于日后的性能问题分析,以及容量规划 (capacity planning)。
**启动Caché History Monitor操作**
```
%SYS>do ^%SYSMONMGR
1) Start/Stop System Monitor
2) Set System Monitor Options
3) Configure System Monitor Classes
4) View System Monitor State
5) Manage Application Monitor
6) Manage Health Monitor
7) View System Data
8) Exit
Option? 5
1) Set Sample Interval
2) Manage Monitor Classes
3) Change Default Notification Method
4) Manage Email Options
5) Manage Alerts
6) Exit
Option? 2
1) Activate/Deactivate Monitor Class
2) List Monitor Classes
3) Register Monitor System Classes
4) Remove/Purge Monitor Class
5) Set Class Sample Interval
6) Debug Monitor Classes
7) Exit
Option? 1
Class? %Monitor.System.HistoryMemory
Activate class? Yes => yes
1) Activate/Deactivate Monitor Class
2) List Monitor Classes
3) Register Monitor System Classes
4) Remove/Purge Monitor Class
5) Set Class Sample Interval
6) Debug Monitor Classes
7) Exit
Option? 1
Class? %Monitor.System.HistoryPerf
Activate class? Yes => yes
1) Activate/Deactivate Monitor Class
2) List Monitor Classes
3) Register Monitor System Classes
4) Remove/Purge Monitor Class
5) Set Class Sample Interval
6) Debug Monitor Classes
7) Exit
Option? 1
Class? %Monitor.System.HistorySys
Activate class? Yes => yes
1) Activate/Deactivate Monitor Class
2) List Monitor Classes
3) Register Monitor System Classes
4) Remove/Purge Monitor Class
5) Set Class Sample Interval
6) Debug Monitor Classes
7) Exit
Option? 1
Class? %Monitor.System.HistoryUser
Activate class? Yes => yes
1) Activate/Deactivate Monitor Class
2) List Monitor Classes
3) Register Monitor System Classes
4) Remove/Purge Monitor Class
5) Set Class Sample Interval
6) Debug Monitor Classes
7) Exit
Option? 2
Class Active SampleInterval
----- ------ --------------
%Monitor.System.HistoryMemory Y default
%Monitor.System.HistoryPerf Y default
%Monitor.System.HistorySys Y default
%Monitor.System.HistoryUser Y default
%Monitor.System.AuditCount N default
%Monitor.System.AuditEvents N default
%Monitor.System.Clients N default
%Monitor.System.Diskspace N default
%Monitor.System.Freespace N default
%Monitor.System.Globals N default
%Monitor.System.Journals N default
%Monitor.System.License N default
%Monitor.System.LockTable N default
%Monitor.System.Processes N default
%Monitor.System.Routines N default
%Monitor.System.Servers N default
%Monitor.System.SystemMetrics N default
%Monitor.System.CSPGateway N default
MyMetric.Freespace N default
```
注意: 激活后需要重新启动System Monitor。统计数据默认保留60天, 如果希望保留更长的时间, 下面的命令设置为保留一年:
`%SYS>do ##class(SYS.History.Hourly).SetPurge(365)`
**读取History Monitor数据**
历史监视器把数据存在SQL表里, 表明和其中的字段请参考[在线文档: History Monitor](https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_historymon)
!!!注意: SYS_History.PerfData给出采样间隔内的测量值,而daily, hourly的值为计算出的每秒的平均值。
这里给出使用历史监视器数据获得数据的2个例子
1. 读取平均CPU占用
```sql
SELECT Substr(DS.Daily,1,5) as DateH, (100-DS.Sys_CPUIdle) as AvgCPUBusy
FROM SYS_History.Daily_SYS DS
WHERE element_key='Avg'
ORDER BY DateH
```
2. 读取每天的CPU占用;9am-12am的CPU占用,global reference and update
```sql
SELECT Substr(DS.Daily,1,5) Day,
(100-DS.Sys_CPUIdle) as Daily_Avg_CPU,
Round(AVG(100-H1.Sys_CPUIdle),2) Morning_Avg_CPU ,
DP.Perf_GloRef, DP.Perf_GloUpdate
FROM SYS_History.Daily_Perf DP,
SYS_History.Daily_SYS DS,
SYS_History.Hourly_Sys H1
WHERE DP.Daily=DS.Daily and DP.element_key='Avg' and DS.element_key='Avg'
and H1.element_key='Avg'and substr(DS.Daily,1,5)=Substr(H1.Hourly,1,5)
and Substr(H1.Hourly,8,12) in (32400,36000,39600)
GROUP BY DS.daily
```
### 其他
除了上面的4个方式,Caché和IRIS还提供了另外的采集指标的方法,比如说执行routine, 存储过程,查看其他SQL表等等, 后面的文章中会对不同类型的指标做更近一步的介绍。
文章
姚 鑫 · 四月 16, 2021
# 第二章 定义和构建索引(四)
# 位片索引
当数字数据字段用于某些数值运算时,位片索引用于该字段。位片索引将每个数值数据值表示为二进制位串。位片索引不是使用布尔标志来索引数值数据值(如在位图索引中那样),而是以二进制值表示每个值,并为二进制值中的每个数字创建一个位图,以记录哪些行的该二进制数字具有1。这是一种高度专门化的索引类型,可以显著提高以下操作的性能:
- `SUM`、`COUNT`或`AVG` Aggregate计算。(位片索引不用于`COUNT(*)`计算。)。位片索引不用于其他聚合函数。
- 指定的字段 `TOP n ... ORDER BY field`
- 在范围条件运算中指定的字段,`WHERE field > n` 或 `WHERE field BETWEEN lownum AND highnum`、
SQL优化器确定是否应该使用定义的位片索引。通常,优化器仅在处理大量(数千)行时才使用位片索引。
可以为字符串数据字段创建位片索引,但位片索引将这些数据值表示为规范数字。换句话说,任何非数字字符串(如`“abc”`)都将被索引为0。这种类型的位片索引可用于快速计数具有字符串字段值的记录,而不计算那些为空的记录。
在下面的例子中,`Salary`是位片索引的候选项:
```sql
SELECT AVG(Salary) FROM SalesPerson
```
位片索引可用于使用`WHERE`子句的查询中的聚合计算。如果`WHERE`子句包含大量记录,则这是最有效的。在下面的示例中,SQL优化器可能会使用`Salary`上的位片索引(如果已定义);如果定义了位片索引,它还会使用`REGION`上的位图索引,使用定义的位图或为`REGION`生成位图临时文件:
```sql
SELECT AVG(Salary) FROM SalesPerson WHERE Region=2
```
但是,当索引无法满足`WHERE`条件时,不使用位片索引,而必须通过读取包含要聚合的字段的表来执行。以下示例将不使用`Salary`的位片索引:
```sql
SELECT AVG(Salary) FROM SalesPerson WHERE Name LIKE '%Mc%'
```
可以为任何包含数值的字段定义位片索引。InterSystems SQL使用`Scale`参数将小数转换为位字符串,如ObjectScript `$factor`函数中所述。可以为数据类型字符串的字段定义位片索引;在这种情况下,出于位片索引的目的,非数字字符串数据值被视为`0`。
可以为系统分配的行ID为正整数值的表中的字段定义位片索引,也可以为使用`%BID`属性定义以支持位图(和位片)索引的表中的字段定义位片索引。
位片索引只能为单个字段名定义,不能为多个字段的连接定义。
不能指定`WITH DATA`子句。
下面的例子比较了位片索引和位图索引。
如果你为1、5和22行创建一个位图索引,它会为这些值创建一个索引:
```java
^gloI("bitmap",1,1)= "100"
^gloI("bitmap",5,1)= "010"
^gloI("bitmap",22,1)="001"
```
如果为第1、2和3行的值1、5和22创建位切片索引,则会首先将这些值转换为位值:
```java
1 = 00001
5 = 00101
22 = 10110
```
然后,它为这些位创建索引:
```java
^gloI("bitslice",1,1)="110"
^gloI("bitslice",2,1)="001"
^gloI("bitslice",3,1)="011"
^gloI("bitslice",4,1)="000"
^gloI("bitslice",5,1)="001"
```
在本例中,位图索引中的值22需要设置1个全局节点;位片索引中的值22需要设置3个全局节点。
请注意,插入或更新需要在所有`n`个位片中设置一个位,而不是设置单个位串。这些附加的全局设置操作可能会影响涉及填充位片索引的插入和更新操作的性能。使用`INSERT`、`UPDATE`或`DELETE`操作填充和维护位片索引比填充位图索引或常规索引慢。维护多个位片索引和/或在频繁更新的字段上维护位片索引可能具有显著的性能成本。
在易失性表(执行许多插入、更新和删除操作)中,位片索引的存储效率可能会逐渐降低。`%SYS.Maint.Bitmap`实用程序方法同时压缩位图索引和位片索引,从而提高了还原效率。
# 重建索引
可以按如下方式构建/重新构建索引:
- 使用`BUILD INDEX` SQL命令构建指定索引,或构建为表、架构或当前命名空间定义的所有索引。
- 使用管理门户重建指定类(表)的所有索引。
- 使用`%BuildIndices()`(或`%BuildIndicesAsync()`)方法,如本节所述。
当前数据库访问确定应如何重建现有索引:
- 非活动系统(在索引构建或重建期间没有其他进程访问数据)
- `READONLY`活动系统(能够在索引构建或重建期间查询数据的其他进程)
- 读写活动系统(能够在索引构建或重建期间修改数据和查询数据的其他进程)
构建索引的首选方法是使用`%BuildIndices()`方法或`%BuildIndicesAsync()`方法。
- `%Library.Persistent.%BuildIndices()`:`%BuildIndices()`作为后台进程执行,但调用方必须等待`%BuildIndices()`完成才能接收回控制。
- `%Library.Persistent.%BuildIndicesAsync()`:`%BuildIndicesAsync()`将`%BuildIndices()`作为后台进程启动,调用方立即收到控制权。`%BuildIndicesAsync()`的第一个参数是`eueToken`输出参数。其余参数与`%BuildIndices()`相同。
`%BuildIndicesAsync()`返回`%Status`值:`Success`表示`%BuildIndices()`辅助作业已成功排队;失败表示该辅助作业未成功排队。
`%BuildIndicesAsync()`向`eueToken`输出参数返回一个值,该值指示`%BuildIndices()`完成状态。要获取完成状态,请通过引用将`eueToken`值传递`给%BuildIndicesAsyncResponse()`方法。还可以指定等待布尔值。如果`wait=1`,则`%BuildIndicesAsyncResponse()`将等待,直到由`eueToken`标识的`%BuildIndices()` JOB 完成。如果`wait=0`,`%BuildIndicesAsyncResponse()`将尽快返回状态值。如果返回时`%BuildIndicesAsyncResponse() ``eueToken`不为空,则`%BuildIndices()` job尚未完成。在这种情况下,可以使用`eueToken`再次调用`%BuildIndicesAsyncResponse()`。当`%BuildIndicesAsyncResponse()``eueToken`最终为`NULL`时,返回的`%BuildIndicesAsyncResponse()``%Status`值是`%BuildIndicesAsync()`调用的job的完成状态。
## 在非活动系统上构建索引
系统自动生成方法(由`%Persistent`类提供),这些方法构建或清除为类(表)定义的每个索引。可以通过以下两种方式之一使用这些方法:
- 通过管理门户进行交互。
- 以编程方式,作为方法调用。
构建索引执行以下操作:
1. 删除索引的当前内容。
2. 扫描(读取每一行)主表,并为表中的每一行添加索引项。如果可能,使用特殊的`$SortBegin`和`$SortEnd`函数来确保高效地构建大型索引。在构建标准索引时,除了在内存中缓存数据之外,使用`$SortBegin`/`$SortEnd`还可以使用`IRISTEMP`数据库中的空间。因此,在构建非常大的标准索引时,InterSystems IRIS可能需要`IRISTEMP`中大致等于最终索引大小的空间。
注:构建索引的方法仅为使用InterSystems IRIS默认存储结构的类(表)提供。映射到遗留存储结构的类不支持索引构建,因为它假定遗留应用程序管理索引的创建。
### 使用管理门户构建索引
可以通过执行以下操作来构建表的现有索引(重建索引):
1. 从管理门户中选择系统资源管理器,然后选择SQL。使用页面顶部的切换选项选择一个命名空间;这将显示可用命名空间的列表。选择命名空间后,选择屏幕左侧的`Schema`下拉列表。这将显示当前名称空间中的模式列表,其中带有布尔标志,指示是否有任何表或视图与每个模式相关联。
2. 从此列表中选择一个架构;该架构将显示在架构框中。它的正上方是一个下拉列表,允许选择属于该模式的表、系统表、视图、过程或所有这些。选择“表”或“全部”,然后打开“表”文件夹以列出此架构中的表。如果没有表,则打开文件夹将显示空白页。(如果未选择“表”或“全部”,则打开“表”文件夹将列出整个命名空间的表。)
3. 选择其中一个列出的表。这将显示表的目录详细信息。
- 要重建所有索引:单击操作下拉列表,然后选择重建表的索引。
- 要重建单个索引:单击索引按钮以显示现有索引。每个列出的索引都有重建索引的选项。
**注意:当其他用户正在访问表的数据时,不要重建索引。要在活动系统上重建索引,请参阅在活动系统上构建索引。**
### 以编程方式构建索引
为非活动表构建索引的首选方法是使用随表的`Persistent`类提供的`%BuildIndices()`(或`%BuildIndicesAsync()`)方法。
**若要以编程方式生成一个或多个索引,请使用`%Library.Persistent.%BuildIndices()`方法。**
生成所有索引:调用`%BuildIndices()`,不带参数生成为给定类(表)定义的所有索引(为其提供值):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices()
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
生成指定索引:调用`%BuildIndices()`,并将`$LIST`索引名作为第一个参数,为给定类(表)生成指定的已定义索引(为其提供值):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX","SSNKey"))
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
生成除以下项之外的所有索引:调用`%BuildIndices()`,并将索引名称的`$LIST`作为第七个参数来构建(为其提供值)给定类(表)的所有已定义索引(指定索引除外):
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices("",,,,,,$ListBuild("NameIDX","SSNKey"))
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
`%BuildIndices()`方法执行以下操作:
1. 对要重建的任何(非位图)索引调用`$SortBegin`函数(这将启动对这些索引的高性能排序操作)。
2. 循环遍历类(表)的主要数据,收集索引使用的值,并将这些值添加到索引(通过适当的排序转换)。
3. 调用`$SortEnd`函数来完成索引排序过程。
如果索引已经有值,则必须使用两个参数调用`%BuildIndices()`,其中第二个参数的值为1。
为此参数指定1将导致该方法在重新生成值之前清除这些值。
例如:
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices(,1)
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
清除并重建所有的索引。
你也可以清除并重建索引的子集,例如:
```java
SET sc = ##class(MyApp.SalesPerson).%BuildIndices($ListBuild("NameIDX","SSNKey"),1)
IF sc=1 {
WRITE !,"成功构建索引"
} ELSE {
WRITE !,"索引构建失败",!
DO $System.Status.DisplayError(sc) QUIT
}
```
注意:当表的数据被其他用户访问时,不要重建索引。
若要在活动系统上重建索引,请参见在活动系统上构建索引。
## 在活动系统上构建索引
在活动系统上构建(或重建)索引时,有两个问题:
- 除非正在构建的索引对`SELECT` `Query`隐藏,否则活动`Query`可能返回不正确的结果。这是在构建索引之前使用`SetMapSelecability()`方法处理的。
- 索引构建期间对数据的活动更新不会反映在索引条目中。这是通过在生成索引时使生成操作锁定单个行来处理的。
**注意:如果应用程序在单个事务内对数据执行大量更新,则可能会出现锁表争用问题。**
### 在Readonly主动系统上构建索引
如果表当前仅用于查询操作(`READONLY`),则可以在不中断查询操作的情况下构建新索引或重建现有索引。这是通过在重建索引时使索引对查询优化器不可用来实现的。
如果要为其构建一个或多个索引的所有类当前都是`READONLY`,请使用“在读写活动系统上构建索引”中描述的相同系列操作,但有以下区别:使用`%BuildIndices()`时,设置`pLockFlag=3`(共享区锁定)。
### 在读写活动系统上构建索引
如果持久化类(表)当前正在使用并且可用于读写访问(查询和数据修改),则可以在不中断这些操作的情况下构建新索引或重建现有索引。如果要为其重建一个或多个索引的类当前可读写访问,则构建索引的首选方法是使用与表的持久类一起提供的`%BuildIndices()`(或`%BuildIndicesAsync()`)方法。
**注意:以下信息适用于动态SQL查询,而不适用于嵌入式SQL。嵌入式SQL在编译时(而不是在运行时)检查`MapSelecability`设置。因此,关闭索引的`MapSelecability`对已经编译的嵌入式SQL查询没有任何影响。因此,嵌入式SQL查询仍可能尝试使用禁用的索引,并将给出不正确的结果。**
在并发读写访问期间,需要执行以下一系列操作来构建一个或多个索引:
1. 望构建的索引对查询不可用(读取访问权限)。这是使用`SetMapSelecability()`完成的。这使得查询优化器无法使用该索引。在重建现有索引和创建新索引时都应执行此操作。例如:
```java
SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents","StudentNameIDX",0)
```
- 第一个参数是`Schema.Table`名称,它是`SqlTableName`,而不是持久类名称。例如,默认模式是`SQLUser`,而不是`User`。该值区分大小写。
- 第二个参数是SQL索引映射名称。这通常是索引的名称,指的是磁盘上存储索引的名称。对于新索引,这是在创建索引时将使用的名称。该值不区分大小写。
- 第三个参数是`MapSelecability`标志,其中0将索引映射定义为不可选择(`OFF`),1将索引映射定义为可选择(`ON`)。指定0。
可以通过调用`GetMapSelecability()`方法来确定索引是否不可选。如果已将索引显式标记为不可选,则此方法返回0。在所有其他情况下,它返回1;它不执行表或索引是否存在的验证检查。请注意,`Schema.Table`名称是`SqlTableName`,并且区分大小写。
`SetMapSelecability()`和`GetMapSelecability()`仅适用于当前命名空间中的索引映射。如果该表映射到多个命名空间,并且需要在每个命名空间中构建索引,则应该在每个命名空间中调用`SetMapSelecability()`。
2. 在索引构建期间建立并发操作:
- 对于新索引:在类中创建索引定义(或在类的`%Storage.SQL`中创建新的SQL Index Map规范)。编译类。此时,索引存在于表定义中;这意味着对象保存、SQL `INSERT`操作和SQL `UPDATE`操作都记录在索引中。但是,由于在步骤1中调用了`SetMapSelecability()`,因此不会为任何数据检索选择此索引映射。`SetMapSelecability()`阻止查询使用区索引,但是数据映射将被投影到SQL以使用索引全局和数据全局。对于新索引,这是合适的,因为索引尚未填充。在对表运行查询之前,需要填充区索引。
- 对于现有索引:清除任何引用该表的缓存查询。索引构建执行的第一个操作是终止索引。因此,在重新生成索引时,不能依赖任何经过优化以使用该索引的代码。
3. 使用`pLockFlag=2`(行级锁定)的持久化类(表)的`%BuildIndices()`方法构建一个或多个索引。`PLockFlag=2`标志在重建过程中在单个行上建立独占写锁,以便并发数据修改操作与构建索引操作相协调。
默认情况下,`%BuildIndices()`构建为持久类定义的所有索引;可以使用`pIgnoreIndexList`从重建中排除索引。
默认情况下,`%BuildIndices()`为所有`ID`构建索引项。但是,可以使用`pStartID`和`pEndID`来定义`ID`范围。`%BuildIndices()`将仅为该范围内(含)的ID构建索引项。例如,如果使用带有`%NOINDEX`限制的`INSERT`将一系列新记录添加到表中,则可以稍后使用具有ID范围的`%BuildIndices()`为这些新记录构建索引项。还可以使用`pStartID`和`pEndID`在节中构建极大的索引。
`%BuildIndices()`返回`%Status`值。如果`%BuildIndices()`因检索数据时出现问题而失败,系统将生成一个`SQLCODE`错误和一条消息(`%msg`),其中包含遇到错误的`%ROWID`。
4. 构建完索引后,启用映射以供查询优化器选择。将第三个参数`MapSelecability`标志设置为1,如下例所示:
```java
SET status=$SYSTEM.SQL.Util.SetMapSelectability("Sample.MyStudents","StudentNameIDX",1)
```
5. 再次清除引用该表的所有缓存查询。这将消除在此程序中创建的缓存查询,这些查询无法使用索引,因此不如使用索引的相同查询最佳。
这就完成了这个过程。索引已完全填充,查询优化器能够考虑该索引。
注意:`%BuildIndices()`只能用于重建`ID`值为正整数的表的索引。如果父表具有正整数`ID`值,还可以使用`%BuildIndices()`重建子表中的索引。对于其他表,请使用`%ValidateIndices()`方法,如验证索引中所述。因为`%ValidateIndices()`是构建索引的最慢方法,所以只有在没有其他选项的情况下才应该使用它。
文章
姚 鑫 · 六月 24, 2021
# 第十七章 加密XML文档
本章介绍如何加密XML文档。
提示:发现在此命名空间中启用`SOAP`日志记录非常有用,这样就可以收到有关任何错误的更多信息。
# 关于加密的XML文档
加密的XML文档包括以下元素:
- ``元素,其中包含由随机生成的对称密钥加密的加密数据。(使用对称密钥加密比使用公钥加密更有效。)
- 至少有一个``元素。每个``元素携带用于加密数据的对称密钥的加密副本;它还包含一个带有公钥的`X.509`证书。拥有匹配私钥的接收方可以解密对称密钥,然后解密``元素。
- (可选)其他明文元素。
```xml
MIICnDCCAYQCAWUwDQYJKo... content omitted
J2DjVgcB8vQx3UCy5uejMB ... content omitted
LmoBK7+nDelTOsC3 ... content omitted
```
要创建加密文档,请使用类`%XML.Security.EncryptedData`和`%XML.Security.EncryptedKey`。这些启用XML的类投影到适当名称空间中的有效``和``元素。
# 创建加密的XML文档
创建加密的XML文档的最简单方法如下:
1. 定义并使用可以直接投影到所需XML文档的通用容器类。
2. 创建包含要加密的XML的流。
3. 加密该流,并将其与相应的加密密钥一起写入容器类的相应属性。
4. 为容器类生成XML输出。
## 加密的前提条件
在加密文档之前,必须创建包含要将加密文档发送到的实体的证书的 IRIS凭据集。在这种情况下,不需要(也不应该拥有)关联的私钥。
## 容器类的要求
一个通用容器类必须包括以下内容:
- 类型为`%XML.Security`的属性。
被投影为``元素的`EncryptedData`。
这个属性将携带加密的数据。
- 至少一个类型为`%XML.Security`的属性。被投影为``元素的`EncryptedKey`。
这些属性将携带相应的密钥信息。
示例如下:
```java
Class XMLEncryption.Container Extends (%RegisteredObject, %XML.Adaptor)
{
Property Data As %XML.Security.EncryptedData(XMLNAME = "EncryptedData");
Property Key As %XML.Security.EncryptedKey(XMLNAME = "EncryptedKey");
Parameter NAMESPACE = "http://www.w3.org/2001/04/xmlenc#";
}
```
## 生成加密的XML文档
要生成并编写加密文档,请执行以下操作:
1. 创建包含XML文档的流。
为此,通常使用`%XML.Writer`将启用XML的对象的输出写入流。
2. 创建`%SYS.X509Credentials`的至少一个实例,将访问要向其提供加密文档的实体的InterSystems IRIS凭据集。为此,请调用此类的`GetByAlias()`类方法。例如:
```java
set credset=##class(%SYS.X509Credentials).GetByAlias("recipient")
```
若要运行此方法,必须以该凭据集的`OwnerList`中包含的用户身份登录,否则`OwnerList`必须为空。
3. 至少创建`%XML.Security.EncryptedKey`实例。若要创建此类的实例,请使用此类的`CreateX509()`类方法。例如:
```java
set enckey=##class(%XML.Security.EncryptedKey).Createx509(credset,encryptionOptions,referenceOption)
```
- `credset`是`%SYS`的实例。
`x509credentials`在刚刚创建的新窗口中打开。
- `encryptionOptions`是`$$$SOAPWSIncludeNone`(还有其他选项,但它们不适用于此场景)。
此宏在`%soap.inc`包含文件中定义。
- `referenceOption`指定了对加密元素的引用的性质。
这里使用的宏在`%soap.inc`包含文件中定义。
4. 在创建`%Library.ListOfObjects`实例,并使用其`Insert()`方法在刚创建插入`%XML.Security.EncryptedKey`实例。
5. 使用`%New()`方法创建`%XML.Security.EncryptedData`实例。例如:
```java
set encdata=##class(%XML.Security.EncryptedData).%New()
```
6. 使用`%XML.Security.EncryptedData的EncryptStream()`实例方法加密在步骤2中创建的流。例如:
```java
set status=encdata.EncryptStream(stream,encryptedKeys)
```
- stream 流是在步骤1中创建的流。
- encryptedKeys是在步骤4中创建的密钥列表。
7. 创建并更新容器类的实例。
- 将键列表写入此类的相应属性。
- 将 `%XML.Security.EncryptedData`的实例写入此类的相应属性。
8. 使用`%XML.Writer`为容器类生成输出。
例如,前面显示的`CONTAINER`类还包括以下方法:
```java
/// w ##class(XMLEncryption.Container).Demo("E:\temp\SecurityXml.txt")
ClassMethod Demo(filename = "", obj = "")
{
#Include %soap
if (obj = "") {
s obj = ##class(MyApp.Person).%OpenId(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 }
set status = writer.RootObject(obj)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
do stream.Rewind()
set container = ..%New() ; 这就是我们要写出的对象
set cred = ##class(%SYS.X509Credentials).GetByAlias("servercred")
set parts =$$$SOAPWSIncludeNone
set ref = $$$KeyInfoX509Certificate
set key = ##class(%XML.Security.EncryptedKey).CreateX509(cred, parts, ref)
set container.Key = key ; 这个细节取决于类
//需要创建一个键列表(本例中仅为一个)
set keys = ##class(%Collection.ListOfObj).%New()
do keys.Insert(key)
set encdata = ##class(%XML.Security.EncryptedData).%New()
set status = encdata.EncryptStream(stream, keys)
set container.Data = encdata ; 这个细节取决于类
// 为容器写输出
set writer = ##class(%XML.Writer).%New()
set writer.Indent = 1
if (filename'="") {
set status = writer.OutputToFile(filename)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
}
set status = writer.RootObject(container)
if $$$ISERR(status) {do $system.OBJ.DisplayError(status) quit}
}
```
此方法可以接受任何启用XML的类的`OREF`;如果没有提供,则使用默认值。
# 解密加密的XML文件
## 解密的前提条件
在解密加密的`XML`文档之前,必须同时提供以下两项:
- IRIS要使用的受信任证书。
- IRIS凭据集,其私钥与加密中使用的公钥匹配。
## 解密文档
要解密加密的XML文档,请执行以下操作:
1. 创建`%XML.Reader`实例打开并使用它打开文档。
2. 获取`Document`属性,`%XML.Reader`实例。
其中包含作为DOM的XML文档。
3. 使用阅读器的`correlation()`方法将``元素或元素与类`%XML.Security.EncryptedKey`关联起来。
例如:
```java
do reader.Correlate("EncryptedKey","%XML.Security.EncryptedKey")
```
4. 遍历文档以读取``元素或多个元素。
为此,可以使用阅读器的`Next()`方法,该方法通过引用返回一个导入的对象(如果有的话)。
例如:
```java
if 'reader.Next(.ikey,.status) {
write !,"Unable to import key",!
do $system.OBJ.DisplayError(status)
quit
}
```
导入的对象`是%XML.Security.EncryptedKey`的实例。
5. 创建`%Library.ListOfObjects`的实例。
并使用它的`Insert()`方法插入`%XML.Security.EncryptedKey`的实例。
刚从文档中获得的。
6. 调用类`%XML.Security.EncryptedData`的`ValidateDocument()`方法
```java
set status=##class(%XML.Security.EncryptedData).ValidateDocument(.doc,keys)
```
第一个参数(通过引用返回)是在第2步中检索到的DOM的修改版本。
第二个参数是上一步中的键列表。
7. 可以选择使用`%XML.Writer`为修改后的DOM生成输出。
例如,前面显示的`CONTAINER`类包含以下类方法:
```java
ClassMethod DecryptDoc(filename As %String)
{
#Include %soap
set reader = ##class(%XML.Reader).%New()
set status = reader.OpenFile(filename)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit }
set doc = reader.Document
//获取元素
do reader.Correlate("EncryptedKey","%XML.Security.EncryptedKey")
if 'reader.Next(.ikey,.status) {
write !,"无法导入密钥",!
do $system.OBJ.DisplayError(status)
quit
}
set keys = ##class(%Collection.ListOfObj).%New()
do keys.Insert(ikey)
// 以下步骤返回解密的文档
set status = ##class(%XML.Security.EncryptedData).ValidateDocument(.doc,keys)
set writer = ##class(%XML.Writer).%New()
set writer.Indent = 1
do writer.Document(doc)
quit $$$OK
}
```
文章
姚 鑫 · 七月 21, 2021
# 第五章 参数定义
描述参数定义的结构。
# 介绍
参数定义定义了一个给定类的所有对象都可用的常数值。创建类定义时(或在编译前的任何时候),可以设置其类参数的值。默认情况下,每个参数的值都是空字符串,但是可以在参数定义中指定一个非空值。在编译时,为类的所有实例建立参数值。除了极少数例外,该值不能在运行时更改。
# 详解
参数定义具有以下结构:
```java
/// description
Parameter name As parameter_type [ keyword_list ] = value ;
```
- `description`描述(可选)旨在显示在“类参考”中。默认情况下,描述为空白。
- `name`(必需)是参数的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。
- `parameter_type`(可选)指定参数的用户界面类型,由Studio用于在检查器内为参数提供输入验证。
**这不是类名;参见下一节。在大多数情况下,编译器会忽略这个关键字。**
如果省略参数类型,也要省略单词`As`
- `value`(可选)指定参数的值。如果省略值,也要省略等号=
- `keyword_list`(可选)是以逗号分隔的关键字列表,用于进一步定义参数。
如果省略此列表,也要省略方括号。
# 参数的允许类型
参数类型`parameter_type` 选项可以是下列值之一:
- `BOOLEAN` — true(1)或false(0)值。
- `CLASSNAME` — 有效的类名。
- `COSCODE` — ObjectScript代码。
- `COSEXPRESSION` — 有效的ObjectScript表达式。
**如果参数是`COSEXPRESSION`类型,则在运行时计算该表达式。**
与形参`Type`关键字的大多数其他值不同,这个值影响编译器。
- `COSIDENTIFIER` — 有效的ObjectScript标识符。
- `INTEGER` — 整数值。
- `SQL` — SQL语句
- `SQLIDENTIFIER` — 有效的SQL标识符。
- `STRING` —字符串值。
- `TEXT` — 多行文本值。
- `CONFIGVALUE` -可以在类定义之外修改的参数。
与形参`Type`关键字的大多数其他值不同,这个值影响编译器。
如果参数的类型是`CONFIGVALUE`,那么可以通过`$SYSTEM.OBJ.UpdateConfigParam()`修改参数。
例如,下面的代码更改了参数`MYPARM`(在类`MyApp`中)的值。
`MyClass`的新值为`42`:
```java
set sc=$system.OBJ.UpdateConfigParam("MyApp.MyClass","MYPARM",42)
```
**注意,`$SYSTEM.OBJ.UpdateConfigParam()`影响任何新进程所使用的生成的类描述符,但不影响类定义。
如果重新编译类,InterSystems IRIS将重新生成类描述符,该描述符现在将使用包含在类定义中的这个参数的值(从而覆盖通过`$SYSTEM.OBJ.UpdateConfigParam()`所做的更改)。**
也可以省略`parameter_type`,在这种情况下`Inspector`将允许参数的任何值。
```
/// web服务的名称。
Parameter SERVICENAME = "SOAPDemo" ;
```
# 第六章 映射定义
描述投影定义的结构。
# 介绍
投影定义指示类编译器在编译或删除类定义时执行指定的操作。
投影定义投影类的名称(来自`%Projection.AbstractProjection`)实现方法称为类的编译完成后,当一个类定义中删除(因为它被删除或者因为类即将重新编译)。
# 详情
投影定义有以下结构:
```java
/// description
Projection name As projection_class (parameter_list) ;
```
- `description`(可选)用于在类引用中显示(但请注意投影目前没有显示在类引用中)。
说明默认为空。
- `Name`(必需)是投影的名称。
这必须是一个有效的类成员名,并且不能与任何其他类成员名冲突。
- `projection_class` (required)是投影类的名称,它是`%Projection.AbstractProjection`的子类。
- `parameter_list`(可选)是一个以逗号分隔的参数及其值列表。
如果指定,这些应该是`projection_class`使用的参数。
如果省略了这个列表,也省略了括号。
- `Keyword_list`(可选)是一个逗号分隔的关键字列表,这些关键字进一步定义了投影。
如果省略了这个列表,也可以省略方括号。
# 第七章 属性定义
描述属性定义的结构。注意,关系是一种属性。
# 介绍
属性包含与类实例相关的信息。可以向对象类添加属性定义。它们在其他类中没有意义。
# 详情
属性定义有以下结构:
```java
/// description
Property name As classname (parameter_list) [ keyword_list ] ;
```
或者(对于列表属性):
```java
/// description
Property name As List Of classname (parameter_list) [ keyword_list ] ;
```
或者(对于数组属性):
```java
/// description
Property name As Array Of classname (parameter_list) [ keyword_list ] ;
```
或者(对于关系属性):
```java
/// description
Relationship name As classname [ keyword_list ] ;
```
- `description`(可选)用于在类引用中显示。说明默认为空。
- `name`(必需)是属性的名称。
这必须是一个有效的类成员名,并且不能与任何其他类成员名冲突。
- `classname`(可选)是该属性所基于的类的名称。
- `parameter_list`(可选)是参数及其值的逗号分隔列表。如果指定,这些应该是由类名使用的参数,或者是对所有属性都可用的参数。
如果省略此列表,也要省略括号。
- `keyword_list`(对于关系属性是必需的,但在其他方面是可选的)是一个逗号分隔的关键字列表,用于进一步定义属性。
如果省略此列表,也要省略方括号。
注意:分片类不支持属性关系。
```java
Property SSN As %String(PATTERN = "3N1""-""2N1""-""4N") [ Required ];
```
# 第八章 查询定义
描述查询定义的结构。
# 介绍
类查询是作为类结构一部分的命名查询,可以通过动态SQL进行访问。
**可以在任何类中定义类查询;不需要将它们包含在持久类中。**
# 详解
查询定义具有以下结构:
```java
/// description
Query name(formal_spec) As classname [ keyword_list ]
{ implementation }
```
- `description`描述(可选)旨在显示在“类别参考”中。默认情况下,描述为空白。
- `name`(必需)是查询的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。
- `formal_spec`(可选)指定传递给查询的参数列表。
具体来说,这是通过关联查询类的`Execute()`方法传递给查询的参数列表。
- `classname`(必需)指定此查询使用的查询类。
**对于基于SQL的查询,该值通常为`%SQLQuery`,对于自定义查询,该值通常为%Query。**
**注意:分片类不支持自定义类查询。**
- `keyword_list`(可选)是逗号分隔的关键字列表,用于进一步定义查询。
如果省略此列表,也要省略方括号。
- `implementation` 实现(可选)是定义查询的零行或多行代码。