0 关注者 · 478 帖子

SQL 是在关系数据库中存储、操作和检索数据的标准语言。

文章 姚 鑫 · 九月 2, 2021 4m read

第四章 SQL命令 ALTER VIEW

修改视图。

大纲

ALTER VIEW view-name [(column-commalist)] AS query [WITH READ ONLY]

ALTER VIEW view-name [(column-commalist)] AS query [WITH [level] CHECK OPTION]

参数

参数描述
view-name被修改的视图,它具有与表名相同的命名规则。视图名可以是限定的(schema.viewname),也可以是非限定的(viewname)。非限定视图名接受默认模式名。
column-commalist可选-组成视图的列名。如果这里没有指定,可以在查询中指定列名,如下所示。
query作为视图基础的结果集(来自查询)。
WITH READ ONLY可选-指定不能通过此视图对视图所基于的表执行插入、更新或删除操作。默认情况下,允许通过视图进行这些操作,约束如下所示。
WITH level CHECK OPTION可选-指定如何通过此视图对视图所基于的表执行插入、更新或删除操作。级别可以是关键字LOCALCASCADED。如果没有指定级别,则WITH CHECK OPTION默认为级联。

描述

ALTER VIEW命令用于修改视图。 视图基于由SELECT语句或由两个或多个SELECT语句组成的UNION组成的查询的结果集。

要确定当前名称空间中是否存在指定的视图,请使用$SYSTEM.SQL.Schema.ViewExists()方法。

可选的column-commalist指定视图中包含的列的名称。 它们必须在数量和顺序上与SELECT语句中指定的表列相对应。 还可以在SELECT语句中指定这些视图列名作为列名别名。 如果两者都不指定,则使用表列名作为视图列名。

以下示例显示了指定视图列名的两种方法:

ALTER VIEW MyView (MyViewCol1,MyViewCol2,MyViewCol3) AS
     SELECT TableCol1, TableCol2, TableCol3 FROM MyTable

等于:

ALTER VIEW MyView AS SELECT TableCol1 AS ViewCol1,
     TableCol2 AS ViewCol2,
     TableCol3 AS ViewCol3
     FROM MyTable

列规范将替换为视图指定的任何先前的列。

视图查询不能包含主机变量或包含INTO关键字。 如果试图在查询中引用主机变量,系统将生成SQLCODE -148错误。

权限

ALTER VIEW命令是一个特权操作。 用户必须具有%ALTER_VIEW管理权限才能执行ALTER VIEW。 如果不这样做,将导致与的SQLCODE -99错误%msg User 'name' does not have %ALTER_VIEW privileges.

用户必须在指定的视图上拥有%ALTER权限。 如果用户是视图的Owner(创建者),则自动授予该用户该视图的%ALTER权限。 否则,用户必须被授予视图%ALTER权限。 如果不这样做,将导致带有%msgSQLCODE -99错误User 'name' does not have privilege to ALTER the view 'Schema.ViewName'.

可以使用GRANT命令分配%ALTER_VIEW%ALTER权限,如果拥有适当的授予权限。

可以通过调用%CHECKPRIV命令来确定当前用户是否具有%ALTER权限。 可以通过调用$SYSTEM.SQL.Security.CheckPrivilege()方法来确定指定的用户是否具有%ALTER权限。

在嵌入式SQL中,可以使用$SYSTEM.Security.Login()方法以具有适当权限的用户登录:

   DO $SYSTEM.Security.Login("_SYSTEM","SYS")
   &sql(      )

必须具有%Service_Login:Use权限才能调用 $SYSTEM.Security.Login ,

不能在基于从部署的持久类投射的表的视图上使用ALTER VIEW。 此操作失败,并出现一个带有%msgSQLCODE -400错误Unable to execute DDL that modifies a deployed class: 'classname'.

示例

下面的示例创建一个视图,然后更改该视图。 提供了查询视图和删除视图的程序。 注意,改变视图将列列表替换为一个新的列列表; 它不保留前面的列列表。

/// w ##class(PHA.TEST.SQLCommand).AlterView()
ClassMethod AlterView()
{
	if $SYSTEM.SQL.Schema.ViewExists("MassFolks") {
		w "视图已存在,请运行删除视图"  
		q
	}
	&sql(
		CREATE VIEW MassFolks (vFullName) AS
		SELECT Name FROM Sample.Person WHERE Home_State='MA'
	)
	if SQLCODE=0 { 
		w !,"创建视图",! 
	} else { 
		w "创建视图错误SQLCODE=",SQLCODE 
	}
}
SELECT * FROM MassFolks

image

ClassMethod AlterView1()
{
	if 0 = $SYSTEM.SQL.Schema.ViewExists("MassFolks") {
		w "视图不存在"  
		q
	}
	&sql(
		ALTER VIEW MassFolks (vMassAbbrev,vCity) AS
		SELECT Home_State,Home_City FROM Sample.Person WHERE Home_State='MA'
	)
	if SQLCODE = 0 { 
		WRITE !,"修改视图",! 
	} ELSE { 
		WRITE "修改视图错误SQLCODE=",SQLCODE 
	}
}

image

DROP VIEW MassFolks

下面的嵌入式SQL示例使用WITH CHECK OPTION查询更改视图:

ClassMethod AlterView2()
{
	DO $SYSTEM.Security.Login("_SYSTEM","SYS")
	&sql(
		ALTER VIEW Sample.MyTestView AS
		SELECT FIRSTWORD FROM Sample.MyTest WITH CHECK OPTION
	)
	if SQLCODE = 0 { 
		WRITE !,"修改视图",! 
	} ELSE { 
		WRITE "修改视图错误SQLCODE=",SQLCODE 
	}
}
0
0 91
文章 姚 鑫 · 九月 1, 2021 3m read

第三章 SQL命令 ALTER USER

修改用户密码。

大纲

ALTER USER user-name IDENTIFY BY password

ALTER USER user-name IDENTIFIED BY password

参数

参数描述
user-name待修改密码的已有用户名。用户名不区分大小写。
password用户的新密码。密码必须至少包含3个字符,并且不能超过32个字符。密码区分大小写。密码可以包含Unicode字符。

描述

ALTER USER命令允许更改用户的密码。可以随时更改自己的密码。要更改其他用户的密码,必须拥有%Admin_Secure:USE系统权限。

IDENTIFY BYIDENTIFIED BY关键字是同义词。

user-name必须是已存在的用户。 指定一个不存在的用户将生成一个带有%msgSQLCODE -400错误,如下所示:ERROR #838: User badname does not exist。可以通过调用$SYSTEM.SQL.Security.UserExists()方法来确定用户是否存在。

作为分隔标识符指定的用户名可以是SQL保留字,可以包含逗号()、句号(.)、插入符号(^)和两个字符的箭头序列(->)。 它可以以除星号(*)以外的任何有效字符开头。

密码可以是字符串字面值、数字或标识符。 字符串字面值必须用引号括起来,并且可以包含任何字符组合,包括空格。 数字或标识符不需要用引号括起来。 数字只能由字符0到9组成。 标识符必须以字母(大写或小写)或%(百分号)开头; 后面可以是字母、数字或以下任何符号的组合:_(下划线)、&(和号)、$(美元符号)或@(@符号)。

如果新密码与现有密码相同,则ALTER USER不会发出错误代码。 它设置SQLCODE = 0(成功完成)。

可以使用$SYSTEM.Security.ChangePassword()方法修改用户密码:

$SYSTEM.Security.ChangePassword(args)

权限

ALTER USER命令是特权操作。在嵌入式SQL中使用ALTER USER之前,必须以具有适当权限的用户身份登录。否则将导致SQLCODE-99错误(特权冲突)。使用$SYSTEM.Security.Login()方法分配具有适当权限的用户:

   DO $SYSTEM.Security.Login("_SYSTEM","SYS")
   &sql(      )

必须具有%Service_Login:Use权限才能调用$SYSTEM.Security.Login方法。

示例

下面的嵌入式SQL示例将用户yaoxin的密码从"temp_pw"修改为" 8888888":

ClassMethod AlterUser()
{
Main
	d $SYSTEM.Security.Login("_SYSTEM","SYS")
	&sql(
		CREATE USER yaoxin IDENTIFY BY temp_pw
	)
	if SQLCODE=0 { 
		w !,"创建用户" 
	} else { 
		w "创建用户错误SQLCODE=",SQLCODE,! 
	}
	&sql(
		ALTER USER BILL IDENTIFY BY 8888888
	)
	if SQLCODE=0 { 
		w !,"修改用户密码" 
	} else { 
		w "修改用户错误SQLCODE=",SQLCODE,! 
	}
	q
Cleanup
	s toggle = $RANDOM(2)
	if toggle = 0 { 
		&sql(
			DROP USER Bill
		)
		if SQLCODE = 0 { 
			w !,"删除用户" 
		} else { 
			w "删除用户错误SQLCODE=",SQLCODE 
		}
	} else { 
		w !,"无删除!"
		q 
	}
}
0
0 80
文章 姚 鑫 · 八月 31, 2021 10m read

第二章 SQL命令 ALTER TABLE(二)

删除列限制

DROP COLUMN可以删除指定为逗号分隔列表的多个列定义。每个列出的列名后面必须紧跟其RESTORYCASCADE(如果未指定,则默认为RESTRICE)和%DELDATA%NODELDATE(如果未指定,则默认为%NODELDATA)选项。

默认情况下,删除列定义不会从数据映射中删除存储在该列中的任何数据。 要同时删除列定义和数据,请指定%DELDATA选项。

删除列定义并不删除相应的列级特权。 例如,授予用户在该列上插入、更新或删除数据的权限。 这将产生以下后果:

  • 如果删除了一个列,然后添加了另一个同名的列,那么用户和角色将在新列上拥有与旧列相同的特权。
  • 删除列后,不可能撤销该列的对象特权。

由于这些原因,通常建议在删除列定义之前使用REVOKE命令从列中撤销列级特权。

RESTRICT关键字(或无关键字):如果列出现在索引中,或者定义在外键约束或其他唯一约束中,则不能删除该列。 为该列尝试DROP COLUMN失败,并出现SQLCODE -322错误。 默认为RESTRICT

CASCADE关键字:如果该列出现在索引中,该索引将被删除。 可能有多个索引。 如果列出现在外键中,则将删除外键。 可能有多个外键。

如果列在COMPUTECODECOMPUTEONCHANGE子句中使用,则不能删除该列。 尝试这样做会导致SQLCODE -400错误。

添加约束限制

可以向以逗号分隔的字段列表添加约束。 例如,可以添加UNIQUE (FName,SurName)约束,它在两个字段FNameSurName的组合值上建立一个UNIQUE约束。 类似地,可以在以逗号分隔的字段列表上添加主键约束或外键约束。

约束可以命名,也可以不命名。 如果未命名,SQL将使用表名生成约束名称。 例如,MYTABLE_Unique1MYTABLE_PKEY1

下面的示例创建了两个未命名的约束,将惟一约束和主键约束添加到以逗号分隔的字段列表中:

 ALTER TABLE SQLUser.MyStudents 
    ADD UNIQUE (FName,SurName),PRIMARY KEY (Fname,Surname)  
  • 字段必须存在才能在约束中使用。 指定不存在的字段将产生SQLCODE -31错误。
  • 不能在约束中使用RowId字段。指定RowId(ID)字段会生成SQLCODE-31错误。
  • 不能在约束中使用流字段。指定流字段会生成SQLCODE-400错误:“invalid index attribute”
  • 一个约束只能应用于一个字段一次。对一个字段指定同一约束两次会生成SQLCODE-400错误:“index name conflict”

通过使用可选的CONSTRAINT标识符关键字子句,可以创建一个命名约束。命名约束必须是有效的标识符;约束名称不区分大小写。这为约束提供了一个名称供将来使用。这在以下示例中显示:

 ALTER TABLE SQLUser.MyStudents 
    ADD CONSTRAINT UnqFullName UNIQUE (FName,SurName)  

可以将多个约束指定为逗号分隔的列表;约束名称应用于第一个约束,其他约束接收默认名称。

约束名称对于表必须是唯一的。为字段指定两次相同的约束名称会生成SQLCODE -400错误:“index name conflict”

添加主键限制

主键值是必需且唯一的。因此,向现有字段或字段组合添加主键约束会使这些字段中的每个字段都成为必填字段。如果向现有字段列表添加主键约束,则这些字段的组合值必须是唯一的。如果现有字段允许空值,则不能向该字段添加主键约束。如果字段(或字段列表)包含非唯一值,则不能向该字段(或字段列表)添加主键约束。

如果向现有字段添加主键约束,则该字段也可能自动定义为IDKey索引。这取决于数据是否存在,以及通过以下方式之一建立的配置设置:

  • SQL SET OPTION PKEY_IS_IDKEY语句。
  • 系统范围的$SYSTEM.SQL.Util.SetOption()方法配置选项DDLPKeyNotIDKey。要确定当前设置,调用$SYSTEM.SQL.CurrentSettings(),它显示通过DDL创建的是主键而不是ID键;默认值是1
  • 进入管理门户,选择系统管理,配置,SQL和对象设置,SQL。 查看通过DDL创建的表的将主键定义为ID键的当前设置。
    • 如果未选中该复选框(默认设置),则主键不会成为类定义中的IDKey索引。使用不是IDKEY的主键访问记录的效率要低得多;但是,这种类型的主键值是可以修改的。
    • 如果选中该复选框,则当通过DDL指定主键约束并且该字段不包含数据时,主键索引也将定义为IDKey索引。如果该字段包含数据,则未定义IDKey索引。如果将主键定义为IDKey索引,则数据访问效率更高,但主键值一旦设置,就永远不能修改。

如果CREATE TABLE定义了位图区索引,然后使用ALTER TABLE添加同时也是IDKey的主键,则系统会自动删除位图区索引。

已存在时添加主键

只能定义一个主键。默认情况下,当主键已经存在时, IRIS拒绝定义主键,或者拒绝定义同一主键两次,并发出SQLCODE-307错误。即使主键的第二个定义与第一个定义相同,也会发出SQLCODE-307错误。要确定当前配置,请调用$SYSTEM.SQL.CurrentSettings(),该函数显示当键存在时允许通过DDL创建主键设置。默认值为0(否),这是建议的配置设置。如果此选项设置为1(是),ALTER TABLE ADD PRIMARY KEY将导致 IRIS从类定义中删除主键索引,然后使用指定的主键字段重新创建此索引。

在管理门户、系统管理、配置、SQL和对象设置中,通过选中忽略冗余DDL语句复选框,可以在系统范围内设置此选项(以及其他类似的创建、更改和删除选项)。

但是,即使将此选项设置为允许在主键已存在的情况下创建主键,如果主键索引也是IDKEY索引并且表包含数据,则不能重新创建主键索引。尝试这样做会生成SQLCODE-307错误。

添加外键限制

默认情况下,不能有两个同名的外键。这样做会生成SQLCODE-311错误。要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings(),它将显示“当存在外键时允许DDL添加外键约束”设置。默认值为0(否),这是此选项的推荐设置。为1(是)时,即使已存在同名外键,也可以通过DDL添加外键。

在管理门户、系统管理、配置、SQL和对象设置中,通过选中忽略冗余DDL语句复选框,可以在系统范围内设置此选项(以及其他类似的创建、更改和删除选项)。

表定义不应该有两个名称不同的外键,这两个外键引用相同的字段-公共字段并执行相互矛盾的引用操作。根据ANSI标准,如果定义了对同一字段执行相互矛盾的引用操作的两个外键(例如,ON DELETE CASCADEON DELETE SET NULL), SQL不会发出错误。相反,当DELETEUPDATE操作遇到这些相互矛盾的外键定义时,SQL会发出错误。

指定不存在的外键字段的添加外键会生成SQLCODE-31错误。

引用不存在的父键表的添加外键会生成SQLCODE-310错误。引用现有父键表中不存在的字段的添加外键会生成SQLCODE-316错误。如果未指定父键字段,则默认为ID字段。

在发出ADD外键之前,用户必须对被引用的表或被引用的表的列具有REFERENCES特权。如果通过动态SQL或xDBC执行ALTER TABLE,则需要REFERENCES权限。

引用可以采用非唯一值的字段(或字段组合)的添加外键会生成SQLCODE-314错误,并通过%msg提供更多详细信息。

NO ACTION是分片表唯一支持的引用操作。

当表中已经存在数据时,ADD外键将受到约束。 要更改此默认约束行为,请参考SET option命令的COMPILEMODE=NOCHECK选项。

当为单个字段定义ADD FOREIGN KEY约束且外键引用引用表的idkey时, IRIS将外键中的属性转换为引用属性。 此转换受以下限制:

  • 该表不能包含任何数据。
  • 外键上的属性不能是持久类(也就是说,它不能已经是引用属性)。
  • 外键字段与引用的idkey字段的数据类型和数据类型参数必须相同。
  • 外键字段不能是IDENTITY字段。

减少约束限制

默认情况下,如果外键约束引用唯一键约束或主键约束,则不能删除该约束。这样做会导致SQLCODE -317错误。要更改此默认外键约束行为,请参考SEToption命令的COMPILEMODE=NOCHECK选项。

删除主键约束的效果取决于主键也是ID键设置的设置(如上所述):

  • 如果PrimaryKey索引不是IDKey索引,则删除PRIMARY KEY约束将删除索引定义。
  • 如果PrimaryKey索引也是IDKey索引,并且表中没有数据,则删除PRIMARY KEY约束将删除整个索引定义。
  • 如果PrimaryKey索引也是IDKey索引,并且表中有数据,则删除PRIMARYKEY约束只会从IDKey索引定义中删除PRIMARYKEY限定符。

不存在时删除约束

默认情况下,IRIS拒绝在没有该约束的字段上删除字段约束的尝试,并发出SQLCODE-315错误。要确定当前设置,请调用$SYSTEM.SQL.CurrentSettings(),它显示允许DDL丢弃不存在的约束设置。默认值为0(否),这是推荐设置。如果此选项设置为1(是),ALTER TABLE DROP CONSTRAINT将导致 IRIS不执行任何操作,也不发出错误消息。

在管理门户、系统管理、配置、SQL和对象设置中,通过选中忽略冗余DDL语句复选框,可以在系统范围内设置此选项(以及其他类似的创建、更改和删除选项)。

示例

以下示例使用嵌入式SQL程序创建表,填充两行,然后更改表定义。

为了演示这一点,请按显示的顺序运行前两个嵌入式SQL程序。(这里有必要使用两个嵌入式SQL程序,因为除非引用的表已经存在,否则嵌入式SQL无法编译INSERT语句。)

ClassMethod AlterTable()
{
	DO $SYSTEM.Security.Login("_SYSTEM","SYS")
	&sql(
		DROP TABLE SQLUser.MyStudents
	)
	IF SQLCODE = 0 { 
		WRITE !,"已删除表" 
	} ELSE { 
		WRITE "DROP TABLE错误SQLCODE=",SQLCODE 
	}
	&sql(
		CREATE TABLE SQLUser.MyStudents 
		(
			FirstName VARCHAR(35) NOT NULL,
			LastName VARCHAR(35) NOT NULL
		)
	)
	IF SQLCODE = 0 { 
		WRITE !,"已创建表" 
	} ELSE { 
		WRITE "CREATE TABLE错误SQLCODE=",SQLCODE 
	}
}
ClassMethod AlterTable1()
{
	DO $SYSTEM.Security.Login("_SYSTEM","SYS")
	NEW SQLCODE, %msg
	&sql(
		INSERT INTO SQLUser.MyStudents 
		(
			FirstName, LastName
		) 
		VALUES 
		(
			'Yao','Vanderbilt'
		)
	)
	IF SQLCODE = 0 { 
		WRITE !,"Inserted data in table"
	} ELSE { 
		WRITE !,"SQLCODE=",SQLCODE,": ",%msg 
	}
	&sql(
		INSERT INTO SQLUser.MyStudents 
		(
			FirstName, LastName
		)
		VALUES 
		(
			'Xin','Smith'
		)
	)
	IF SQLCODE = 0 { 
		WRITE !,"Inserted data in table"
	} ELSE { 
		WRITE !,"SQLCODE=",SQLCODE,": ",%msg 
	}
}

下面的示例使用ALTER TABLE添加ColorPreference列。 因为列定义指定了默认值,所以系统会为表中已有的两行填充ColorPreference的值'Blue':

ClassMethod AlterTable2()
{
	NEW SQLCODE,%msg
	&sql(
		ALTER TABLE SQLUser.MyStudents 
		ADD COLUMN ColorPreference VARCHAR(16) NOT NULL DEFAULT 'Blue'
	)
	IF SQLCODE = 0 {
		WRITE !,"添加一列",! 
	} ELSEIF SQLCODE = -306 {
		WRITE !,"SQLCODE=",SQLCODE,": ",%msg 
	} ELSE { 
		WRITE "SQLCODE error=",SQLCODE 
	}
}

下面的示例使用ALTER TABLE添加两个计算列:FLNameLFName。 对于已存在的行,这些列没有值。 对于任何随后插入的行,将为每一列计算一个值:

ClassMethod AlterTable3()
{
	NEW SQLCODE,%msg
	&sql(
		ALTER TABLE SQLUser.MyStudents 
			ADD COLUMN FLName VARCHAR(71) COMPUTECODE 
			{ 
				SET {FLName}={FirstName}_" "_{LastName}
			} 
			COMPUTEONCHANGE 
			(
				FirstName,LastName
			),
			COLUMN LFName VARCHAR(71) COMPUTECODE 
			{ 
				SET {LFName}={LastName}_ "," _{FirstName}
			} 
			COMPUTEONCHANGE 
			(
				FirstName,LastName
			) 
		)
	IF SQLCODE=0 {
		WRITE !,"添加两个计算列",! 
	} ELSE { 
		WRITE "SQLCODE error=",SQLCODE 
	}
}

DDL创建的 User.MyStudents 表:

Class User.MyStudents Extends %Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {yx}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = MyStudents ]
{

Property FirstName As %Library.String(MAXLEN = 35) [ Required, SqlColumnNumber = 2 ];

Property LastName As %Library.String(MAXLEN = 35) [ Required, SqlColumnNumber = 3 ];

Property ColorPreference As %Library.String(MAXLEN = 16) [ InitialExpression = "Blue", Required, SqlColumnNumber = 4 ];

Property FLName As %Library.String(MAXLEN = 71) [ SqlColumnNumber = 5, SqlComputeCode = {	SET {FLName}={FirstName}_" "_{LastName}
}, SqlComputed ];

Property LFName As %Library.String(MAXLEN = 71) [ SqlColumnNumber = 6, SqlComputeCode = {	SET {LFName}={LastName}_ "," _{FirstName}
}, SqlComputed ];

/// Bitmap Extent Index auto-generated by DDL CREATE TABLE statement.  Do not edit the SqlName of this index.
Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ];

}

image

0
0 90
文章 姚 鑫 · 八月 30, 2021 9m read

第一章 SQL命令 ALTER TABLE(一)

修改表。

大纲

ALTER TABLE table alter-action

where alter-action is one of the following:
     ADD  [(] add-action {,add-action} [)] |
     DROP [COLUMN ] drop-column-action {,drop-column-action} |
     DROP drop-action |
     DELETE drop-action |
     ALTER [COLUMN] field alter-column-action |
     MODIFY modification-spec {,modification-spec}
     RENAME table

add-action ::= 
     [CONSTRAINT identifier]
     [(] FOREIGN KEY (field-commalist) 
          REFERENCES table (field-commalist)
          [ON DELETE ref-action] [ON UPDATE ref-action] 
          [NOCHECK]  [)]
     |
     [(] UNIQUE (field-commalist)  [)] 
     |
     [(] PRIMARY KEY (field-commalist) [)] 
     | 
     DEFAULT [(] default-spec [)] FOR field
     |
     [COLUMN] [(] field datatype  [sqlcollation] 
           [%DESCRIPTION string]
           [DEFAULT [(] default-spec [)] ]
           [ON UPDATE update-spec ]
           [UNIQUE] [NOT NULL]
           [REFERENCES table (field-commalist) 
              [ON DELETE ref-action] [ON UPDATE ref-action] 
              [NOCHECK]  ]
           [)]

drop-column-action ::= 
       [COLUMN] field [RESTRICT | CASCADE] [%DELDATA | %NODELDATA] 

drop-action ::= 
     FOREIGN KEY identifier |
     PRIMARY KEY |
     CONSTRAINT identifier |

alter-column-action ::= 
     RENAME newfieldname |
     datatype | 
     [SET] DEFAULT [(] default-spec [)]  |  DROP DEFAULT | 
     NULL |  NOT NULL | 
     COLLATE sqlcollation

modification-spec ::=
     oldfieldname RENAME newfieldname |
     field [datatype] 
          [DEFAULT [(] default-spec [)]]
          [CONSTRAINT identifier] [NULL] [NOT NULL]

sqlcollation ::=
     { %EXACT | %MINUS | %MVR | %PLUS | %SPACE |   
        %SQLSTRING [(maxlen)] | %SQLUPPER [(maxlen)] |
        %TRUNCATE[(maxlen)]  }

参数

参数描述
table要更改的表的名称。表名可以是限定的(schema.table),也可以是非限定的(table)。非限定的表名采用默认的架构名。不使用架构搜索路径值。
identifier分配给约束的唯一名称。必须是有效的标识符。
field要更改(添加、修改、删除)的列的名称。必须是有效的标识符。
field-commalist列的名称或逗号分隔的列列表。即使只指定了一列,字段命令列表也必须用括号括起来。
datatype有效的SQL数据类型。
default-spec如果未被用户提供的数据值覆盖,则自动为此字段提供的默认数据值。允许的值有:文字值;以下关键字选项之一(NULL, USER, CURRENT_USER, SESSION_USER, SYSTEM_USER, CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP);或者OBJECTSCRIPT表达式。不要将SQL零长度字符串用作默认值。
update-specCREATE TABLE中的更新。
COLLATE sqlcollation可选-指定以下SQL排序规则类型之一:%EXACT, %MINUS, %PLUS, %SPACE, %SQLSTRING, %SQLUPPER, %TRUNCATE, %MVR。默认值为名称空间默认排序规则(除非更改,否则为%SQLUPPER)。%SQLSTRING%SQLUPPER%TRUNCATE可以使用可选的最大长度截断参数(括在圆括号中的整数)指定。这些排序规则参数关键字的百分比符号(%)前缀是可选的。COLLATE关键词是可选的。

描述

ALTER TABLE语句修改表定义;它可以添加元素、删除元素或修改现有元素。在每个ALTER TABLE语句中只能执行一种类型的操作。

  • RENAME可以重命名表,也可以使用ALTER COLUMNMODIFY语法重命名表中的现有列。
  • Add可以向表中添加多个列和/或约束。只需指定一次ADD关键字,后跟一个逗号分隔的列表。可以使用逗号分隔的列表向表中添加多个新列,向现有列中添加约束条件列表,或者同时向现有列中添加新列和约束条件。
  • DROP COLUMN可以从表中删除多列。只需指定一次DROP关键字,然后是一个逗号分隔的列列表,每个列都有可选的级联和/或数据删除选项。
  • ALTER COLUMN可以更改单个列的定义。它不能更改多列。
  • MODIFY 可以更改单个列或逗号分隔的列列表的定义。它不支持ALTER COLUMN提供的所有选项。
  • 删除可以从一个或一组字段中删除约束。DROP只能对单个约束进行操作。

ALTER TABLE DROP关键字和ALTER TABLE DELETE关键字是同义词。

要确定当前命名空间中是否存在指定表,请使用$SYSTEM.SQL.Schema.TableExists()方法。

权限和锁

ALTER TABLE命令是特权操作。用户必须具有%ALTER_TABLE管理权限才能执行ALTER TABLE。否则将导致SQLCODE-99错误 the %msg User 'name' does not have %ALTER_TABLE privileges.。

用户必须对指定表拥有%ALTER特权。如果用户是表的所有者(创建者),则会自动授予该用户对该表的%ALTER权限。否则,必须授予用户对该表的%ALTER特权。否则将导致SQLCODE-99错误 %msg User 'name' does not have required %ALTER privilege needed to change the table definition for 'Schema.TableName'.

要确定当前用户是否具有%ALTER特权,请调用%CHECKPRIV命令。要确定指定用户是否具有%ALTER权限,请调用$SYSTEM.SQL.Security.CheckPrivileve()方法。

要分配所需的管理权限,请使用具有%ALTER_TABLE权限的GRANT命令;这需要适当的授予权限。要分配%ALTER OBJECT权限,可以使用:

  • 具有%ALTER权限的GRANT命令。这需要适当的授予权限。
  • 在用于编辑角色或用户的页面上,管理门户中的SQL表选项卡上的表的更改复选框。这需要适当的授予权限。

在嵌入式SQL中,可以使用$SYSTEM.Security.Login()方法以具有适当权限的用户身份登录:

   DO $SYSTEM.Security.Login("_SYSTEM","SYS")
   &sql(      )

必须具有%Service_Login:Use权限才能调用$SYSTEM.Security.Login方法。

  • 除非表类定义包括[DdlAllowed],否则不能对从持久类投影的表使用ALTER TABLE。否则,操作将失败,并显示SQLCODE-300错误the %msg DDL not enabled for class 'Schema.tablename'.
  • ALTER TABLE不能用于从部署的持久类投射的表。此操作失败,并显示SQLCODE-400错误the %msg Unable to execute DDL that modifies a deployed class: 'classname'.

ALTER TABLE获取对TABLE的表级锁。这可以防止其他进程修改表的数据。此锁在ALTER TABLE操作结束时自动释放。当ALTER TABLE锁相应的类定义时,它使用当前进程的SQL Lock超时设置。

若要更改表,表不能在独占模式或共享模式下被另一个进程锁定。更改锁定的表会导致SQLCODE-110错误,a %msg such as the following: Unable to acquire exclusive table lock for table 'Sample.MyTest'.

重命名表

可以使用以下语法重命名现有表:

ALTER TABLE schema.TableName RENAME NewTableName

此操作重命名其现有架构中的现有表。只能更改表名,而不能更改表架构。在NewTableName中指定架构名称会导致SQLCODE-1错误。为旧表和新表指定相同的表名会生成SQLCODE-201错误。

重命名表会更改SQL表名。它不会更改相应的永久类名。

重命名表不会更改对触发器中旧表名的引用。

如果视图引用现有表名称,则重命名该表将失败。这是因为尝试重命名表是一个原子操作,会导致视图重新编译,从而生成SQLCODE-30错误。 “Table 'schema.oldname' not found”.

添加列限制

添加列可以添加单个列,也可以添加逗号分隔的列列表。

如果尝试通过ALTER TABLE TABLE NAME ADD COLUMN语句将字段添加到表中:

  • 如果该名称的列已经存在,则该语句将失败,并显示SQLCODE-306错误。
  • 如果语句对列指定了NOT NULL约束,并且该列没有默认值,则如果表中已存在数据,则该语句将失败。这是因为,在完成DDL语句之后,不满足所有预先存在的行的NOT NULL约束。这将生成错误代码SQLCODE-304(试图向包含数据的表中添加一个没有默认值的非空字段)。
  • 如果语句对列指定了NOT NULL约束,并且该列有默认值,则该语句将更新表中的所有现有行,并将该列的默认值分配给该字段。这包括CURRENT_TIMESTAMP等默认值。
  • 如果该语句没有对列指定NOT NULL约束,并且该列有默认值,则在任何现有行中都不会更新该列。这些行的列值为NULL

要更改此默认的NOT NULL约束行为,参考SET OPTION命令的COMPILEMODE=NOCHECK选项。

如果指定了名为“ID”的普通数据字段,而RowID字段已经命名为“ID”(默认值),则添加列操作将成功。ALTER TABLE添加ID数据列,并将RowId列重命名为“ID1”以避免重复名称。

添加整数计数器

如果通过ALTER TABLE TABLE NAME ADD COLUMN语句将整数计数器字段添加到表中:

  • 如果表没有标识字段,则可以向该表添加标识字段。如果表已有标识字段,则ALTER TABLE操作将失败,并显示SQLCODE-400错误,并显示如下%msg:ERROR #5281: Class has multiple identity properties: 'Sample.MyTest::MyIdent2'。使用添加列定义此字段时, IRIS将使用相应的RowID整数值填充此字段的现有数据行。

如果CREATE TABLE定义了位图区索引,然后将标识字段添加到表中,并且标识字段不是MINVAL1或更高的类型%BigInt%Integer%SmallInt%TinyInt,并且表中没有数据,则系统会自动删除位图区索引。

  • 可以向表中添加一个或多个序列(%Library.Counter)字段。使用“添加列”定义此字段时,此字段的现有数据行为空。可以使用UPDATE向此字段为NULL的现有数据行提供值;不能使用UPDATE更改非NULL值。
  • 如果表没有ROWVERSION字段,则可以向该表添加ROWVERSION字段。如果表已具有ROWVERSION字段,则ALTER TABLE操作将失败,并显示SQLCODE-400错误,并显示如下%msg:: ERROR #5320: Class 'Sample.MyTest' has more than one property of type %Library.RowVersion. Only one is allowed. Properties: MyVer,MyVer2.使用添加列定义此字段时,此字段的现有数据行为NULL;不能更新为NULLROWVERSION值。

更改列限制

ALTER COLUMN可以修改单个列的定义:

  • 使用语法ALTER TABLE TABLE NAME ALTER COLUMN oldname rename newname重命名列。重命名列会更改SQL字段名称。它不会更改相应的持久类属性名称。ALTER COLUMN OLDNAME RENAME NEWNAME替换触发器代码和ComputeCode中的旧字段名称引用。
  • 更改列特征:数据类型、默认值、NULL/NOT NULL和排序规则类型。

如果表包含数据,则不能更改包含数据的列的数据类型,如果更改将导致流数据类型为非流数据或非流数据类型为流数据。 尝试这样做会导致SQLCODE -374错误。 如果没有现有数据,则允许这种类型的数据类型更改。

可以使用ALTER COLUMN添加、更改或删除字段默认值。

如果表包含数据,如果列包含空值,则不能指定NOT NULL; 这将导致SQLCODE -305错误。

如果更改包含数据的列的排序规则类型,则必须重新构建该列的所有索引。

修改列的限制

MODIFY可以修改单个列或用逗号分隔的列列表的定义。

  • 使用语法ALTER TABLE tablename MODIFY oldname RENAME newname重命名该列。重命名列会更改SQL字段名称。它不会更改相应的持久类属性名称。Modify oldname重命名newname替换触发器代码和ComputeCode中的旧字段名称引用。
  • 更改列特征:数据类型、默认值和其他特征。

如果表包含数据,则不能将包含数据的列的数据类型更改为不兼容的数据类型:

  • 数据类型优先级较低(包含较少)的数据类型,如果这与现有数据值冲突。尝试这样做会导致SQLCODE-104错误,其中%msg指定哪个字段和哪个数据值导致错误。
  • 具有较小MAXLENMAXVAL/MINVAL(如果这与现有数据值冲突)的数据类型。尝试这样做会导致SQLCODE-104错误,其中%msg指定哪个字段和哪个数据值导致错误。
  • 数据类型从流数据类型改变为非流数据类型或从非流数据类型改变为流数据类型。尝试这样做会导致SQLCODE-374错误。如果没有现有数据,则允许这种类型的数据类型更改。

可以使用修改来添加或更改字段默认值。不能使用修改来删除字段默认值。

如果表包含数据,如果列包含空值,则不能为该列指定NOT NULL;这会导致SQLCODE-305错误。语法形式 ALTER TABLE mytable MODIFY field1 NOT NULL and ALTER TABLE mytable MODIFY field1 CONSTRAINT nevernull NOT NULL执行相同的操作。可选的约束标识符子句是为兼容而提供的无操作。不保留或使用此字段约束名称。试图通过指定此字段约束名称删除此字段约束会导致SQLCODE-315错误。

0
0 169
文章 姚 鑫 · 五月 30, 2021 8m read

第十一章 发送和接收IBM WebSphere MQ消息

InterSystems IRIS为IBM WebSphere MQ提供了一个接口,可以使用该接口在InterSystems IRIS和IBM WebSphere MQ的消息队列之间交换消息。要使用此接口,必须能够访问IBM WebSphere MQ服务器,并且IBM WebSphere MQ客户端必须与InterSystems IRIS在同一台计算机上运行。

该接口由%Net.MQSend%Net.MQRecv类组成,这两个类都是%Net.abstractMQ的子类。这些类使用由InterSystems IRIS在所有合适的平台上自动安装的动态链接库。(这是Windows上的MQInterface.dll;其他平台的文件扩展名不同。)。反过来,InterSystems IRIS动态链接库需要IBM WebSphere MQ动态链接库。

该界面仅支持发送和接收文本数据,不支持二进制数据。

使用IBM WebSphere MQ的RIS接口

通常,要使用IBM WebSphere MQ的InterSystems IRIS接口,请执行以下操作:

  1. 确保有权访问IBM WebSphereMQv7.x或更高版本。具体而言:
  • IBM WebSphere MQ客户端必须与InterSystems IRIS安装在同一台计算机上。请注意,安装程序会根据需要更新PATH环境变量并添加其他系统变量。
  • 确保在安装客户端后重新启动计算机,以便InterSystems IRIS能够识别该客户端。
  • 客户端必须能够访问IBM WebSphere MQ服务器。
  • 将用来访问服务器的用户名必须具有使用队列管理器和计划使用的队列的权限。
  1. 创建%Net.MQSend%Net.MQRecv的新实例,具体取决于要发送还是接收消息。
  2. 连接到IBM WebSphere MQ服务器。执行此操作时,您需要提供以下信息:
  • 队列管理器的名称。
  • 要使用的队列的名称。
  • 与该队列通信的通道。可以指定IBM WebSphere MQ服务器的通道名称、传输机制以及IP地址和端口。

如果正在使用IBM WebSphere MQ的身份验证功能,还可以提供名称和密码。

  1. 调用%Net.MQSend%Net.MQRecv的相应方法来发送或接收消息。

注意:要在64位Linux平台上使用IBM Websphere MQ,必须设置LD_LIBRARY_PATH以包括MQ库的位置。因为必须为任何使用MQ接口的InterSystems IRIS进程设置路径,所以如果正在运行后台进程,则必须在启动InterSystems IRIS之前设置该路径,并在运行IRIS终端之前在任何UNIX®终端中设置该路径。

获取错误代码

%Net.MQSend%Net.MQRecv的方法如果成功则返回1,如果不成功则返回0。在出现错误的情况下,调用%GetLastError()方法,该方法返回IBM WebSphere MQ给出的最后一个原因代码。

创建连接对象

在可以通过IBM WebSphere MQ发送或接收消息之前,必须创建一个Connection对象,该对象可以建立到队列管理器的连接、打开通道和打开队列以供使用。有两种方法可以做到这一点:

  • 可以使用%Init方法,该方法接受指定所有所需信息的参数。
  • 可以在首次设置指定所有所需信息的属性后使用%Connect方法。

使用%Init()方法

要使用%Init()方法创建连接对象,请执行以下操作:

  1. 创建%Net.MQSend(如果要发送消息)或%Net.MQRecv(如果要接收消息)的实例。本主题将此实例称为连接对象。

注意:如果收到 <DYNAMIC LIBRARY LOAD>错误,则表示缺少动态链接库,并且messages.log文件(在系统管理器的目录中)有更多详细信息。

  1. 如果需要身份验证,请设置Connection对象的以下属性:
  • 用户名-指定有权使用此频道的用户名。
  • 密码-指定给定用户的密码。
  1. 调用Connection对象的%Init()方法。此方法按顺序接受以下参数。

a. 指定队列名称的字符串;这应该是指定队列管理器的有效队列。

b. 指定队列管理器的字符串;它应该是IBM WebSphere MQ服务器上的有效队列管理器。

如果省略此参数,系统将使用IBM WebSphere MQ中配置的默认队列管理器。或者,如果IBM WebSphere MQ已配置为队列管理器由队列名称确定,则系统将使用适合给定队列名称的队列管理器。

c. 指定频道规范的字符串,格式如下:

"channel_name/transport/host_name(port)"

这里,channel_name是要使用的通道的名称,Transport是通道使用的传输,host_name是运行IBM WebSphere MQ服务器的服务器名称(或IP地址),port是该通道应该使用的端口。

传输可以是以下之一:TCPLU62NETBIOSSPX

例如:

"CHAN_1/TCP/rodan(1401)"
"CHAN_1/TCP/127.0.0.1(1401)"

如果省略此参数,系统将使用IBM WebSphere MQ中配置的默认通道规范。或者,如果系统已配置为通道由队列名称确定,则系统使用适合给定队列名称的通道。

d. 一个可选字符串,它指定要向其中写入错误消息的日志文件。默认情况下,不进行日志记录。

  1. 检查%Init()方法返回的值。如果该方法返回1,则表明连接已成功建立,可以使用Connection对象发送或接收消息(具体取决于使用的类)。

使用%Connect()方法

在某些情况下,可能更喜欢单独指定连接的所有详细信息。为此,请使用%Connect()方法,如下所示:

  1. 创建%Net.MQSend(如果要发送消息)或%Net.MQRecv(如果要接收消息)的实例。如前所述,本主题将此实例称为连接对象。

注意:如果收到<DYNAMIC LIBRARY LOAD> 错误,则表示缺少动态链接库,并且messages.log文件(在系统管理器的目录中)有更多详细信息。

  1. 设置Connection对象的以下属性:
  • QName-(必选)指定队列名称;这应该是指定队列管理器的有效队列。
  • QMgr-指定要使用的队列管理器;它应该是IBM WebSphere MQ服务器上的有效队列管理器。

如果省略此参数,系统将使用IBM WebSphere MQ中配置的默认队列管理器。或者,如果IBM WebSphere MQ已配置为队列管理器由队列名称确定,则系统将使用适合给定队列名称的队列管理器。

  1. 或者,通过设置Connection对象的以下属性来指定要使用的频道:
  • Connection - 指定IBM WebSphere MQ服务器的主机和端口。例如:"127.0.0.1:1401"
  • Channel - 指定要使用的频道的名称。这必须是IBM WebSphere MQ服务器上的有效通道。
  • Transport - 指定通道使用的传输。此属性可以是以下之一: "TCP", "LU62", "NETBIOS", "SPX"

如果省略这些参数,系统将使用IBM WebSphere MQ中配置的默认通道规范。或者,如果系统已配置为通道由队列名称确定,则系统使用适合给定队列名称的通道。

  1. 如果频道需要身份验证,请设置Connection对象的以下属性:
  • 用户名-指定有权使用此频道的用户名。
  • 密码-指定给定用户的密码。
  1. 调用Connection对象的%ErrLog()方法。此方法接受一个参数,即要用于此连接对象的日志文件的名称。
  2. 检查%ErrLog()方法返回的值。
  3. 调用Connection对象的%Connect()方法。
  4. 检查%Connect()方法返回的值。如果该方法返回1,则表明连接已成功建立,可以使用Connection对象发送或接收消息(具体取决于您使用的类)。

指定字符集(CCSID)

要设置用于消息转换的字符集,请调用Connection对象的%SetCharSet()方法。指定在IBM WebSphere MQ中使用的整数编码字符集ID(CCSID)

  • 如果正在发送消息,这应该是这些消息的字符集。如果不指定字符集,则MQ系统假定消息使用为MQ客户端指定的默认字符集。
  • 如果要检索邮件,则这是要将这些邮件翻译为的字符集。

要获取当前正在使用的CCSID,请调用%charset()方法。此方法通过引用返回CCSID,并返回1或0以指示是否成功.

指定其他消息选项

要指定消息描述符选项,可以选择设置连接对象的以下属性:

  • ApplIdentityData指定应用程序标识消息描述符选项。
  • PutApplType指定PUT Application Type消息描述符选项。

发送消息

要发送邮件,请执行以下操作:

  1. 按照“创建连接对象”中的说明创建连接对象。在这种情况下,请创建%Net.MQSend的实例。Connection对象有一个消息队列,可以向该队列发送消息。
  2. 根据需要调用以下方法:
  • %put()-给定一个字符串,此方法将该字符串写入消息队列。
  • %PutStream()-给定初始化的文件字符流,此方法将该字符串写入消息队列。请注意,必须设置流的Filename属性才能对其进行初始化。不支持二进制流。
  • %SetMsgId()-给定一个字符串,此方法使用该字符串作为发送的下一条消息的消息ID。
  1. 检查调用的方法返回的值。
  2. 检索完消息后,调用Connection对象的%Close()方法以释放动态链接库的句柄。

示例1:SendString()

下面的类方法使用队列管理器QM_antigua和名为 S_antigua的队列通道向队列mqtest发送一条简单的字符串消息。通道使用TCP传输,IBM WebSphere MQ服务器运行在名为Antigua的机器上,并侦听端口1401。

///Method returns reason code from IBM WebSphere MQ
ClassMethod SendString() As %Integer
{
 Set send=##class(%Net.MQSend).%New()
 Set queue="mqtest"
 Set qm="QM_antigua"
 Set chan="S_antigua/TCP/antigua(1414)"
 Set logfile="c:\mq-send-log.txt"

 Set check=send.%Init(queue,qm,chan,logfile)
 If 'check  Quit send.%GetLastError()
 
 //send a unique message
 Set check=send.%Put("This is a test message "_$h)

 If 'check  Quit send.%GetLastError()
 Quit check
}

示例2:SendCharacterStream()

下面的类方法发送文件字符流的内容。它使用的队列与上一个示例中使用的队列相同:

///Method returns reason code from IBM WebSphere MQ
ClassMethod SendCharacterStream() As %Integer
{
 Set send=##class(%Net.MQSend).%New()
 Set queue="mqtest"
 Set qm="QM_antigua"
 Set chan="S_antigua/TCP/antigua(1414)"
 Set logfile="c:\mq-send-log.txt"

 Set check=send.%Init(queue,qm,chan,logfile)
 If 'check  Quit send.%GetLastError()
 
 //initialize the stream and tell it what file to use
 Set longmsg=##class(%FileCharacterStream).%New()
 Set longmsg.Filename="c:\input-sample.txt"
 
 Set check=send.%PutStream(longmsg)

 If 'check  Quit send.%GetLastError()
 Quit check
}

示例3:从终端发送消息

以下示例显示了向IBM WebSphere MQ队列发送消息的终端会话。这只能在配置了IBM WebSphere MQ客户端的计算机上运行。

Set MySendQ = ##class(%Net.MQSend).%New()

Do MySendQ.%Init("Q_1", "QM_1","QC_1/TCP/127.0.0.1(1401)","C:\mq.log")

Do MySendQ.%Put("Hello from tester")

Set MyRecvQ =##class(%Net.MQRecv).%New()

Do MyRecvQ.%Init("Q_1", "QM_1","QC_1","C:\mq.log")

Do MyRecvQ.%Get(.msg, 10000)

Write msg,!
0
0 471
文章 姚 鑫 · 五月 28, 2021 5m read

第九章 创建、编写和阅读MIME邮件

Iris提供了一个可以用于创建MultiPart Mime消息(%Net.MimePart)的类。创建要添加到SOAP消息的附件时,请使用此类;请参阅创建Web服务和Web客户端。因为MIME是一个常见的标准,所以有许多其他可能的应用程序,例如电子邮件处理和HTTP Multipart Post。

MIME消息概述

MIME格式的文档被称为MIME部分。每个MIME部件都有标题,包含邮件正文(文本或二进制)或包含额外的MIME部件。具有MIME版本标题的MIME部分可以用作顶级文档,称为MIME消息。下图显示了示例:

image

在该示例中,EF具有未显示的附加子部分。

要表示MIME部件,请使用 %Net.MIMEPart类,该类提供用于设置零件的标题和内容的属性。

创建MIME部分

要创建一个MIME部件,执行以下步骤:

  1. 创建%Net.MIMEPart的实例。
  2. 做以下其中一项:
  • 添加文本或二进制正文。为此,请创建流(文本或二进制)的实例,并将MIME部分的Body属性设置为等于该流。使用标准流接口将数据写入此流。不要为Parts属性指定值。
  • 添加MIME部件列表。为此,请按此处所述创建MIME部件,并将Parts属性设置为等于这些部件的列表。不要为Body属性指定值。
  1. 可以选择按照“设置和获取MIME部件标头”中的说明设置标头。

设置和获取MIME部件标头

可以设置和获取HTTP标头的值。%Net.MIMEPart的以下属性会影响MIME标头:

  • ContentType - Content-Type标头的Internet媒体类型(MIME类型)。这指定正文数据的Internet媒体类型。例如:"text/plain", "text/html", "image/jpeg","multipart/mixed"等。
  • ContentCharset - Content-Type标题的字符集部分。如果设置此属性,则必须首先设置ContentType属性。对于包含文本正文的每个MIME部分,请确保适当设置ContentCharset属性以指示正文中使用的字符集。此属性应声明已使用的字符集,因为%Net.MIMEPart不执行任何转换。
  • ContentId-规范化的Content-ID头,不带尖括号(<>)以及任何前导空格和尾随空格。
  • ContentLocation-标准化的Content-Location标头,没有任何前导空格和尾随空格。
  • ContentTransferEncoding - Content-Transfer-Encoding标头。此属性可以是以下属性之一:"base64""quoted-printable""7bit" "8bit"

重要提示:请注意,如果内容为“Base64”编码,则不能包含任何Unicode字符。如果要发送的内容包括Unicode字符,请确保使用$ZCONVERT将内容转换为UTF-8,然后对其进行base-64编码。例如:

set BinaryText=$ZCONVERT(UnicodeText,"O","UTF8")
set Base64Encoded=$system.Encryption.Base64Encode(BinaryText)

收件人必须使用相反的过程来解码文本:

set BinaryText=$system.Encryption.Base64Decode(Base64Encoded)
set UnicodeText=$ZCONVERT(BinaryText,"I","UTF8")

%Net.MIMEPart类提供可用于管理MIME标头的常规方法:

  • GetHeader()返回头的值。
  • NextHeader()获取下一个标头。
  • SetHeader()设置标题的值。通常,可以使用它来设置非标准标头。
  • RemoveHeader()删除标题。

指定可选的消息边界值

默认情况下,邮件边界是自动生成的。如果需要,可以指定消息边界。要执行此操作,请指定边界属性的值。请确保使用极不可能在任何消息部分中使用的字符串。

编写MIME邮件

要编写MIME邮件,请使用%Net.MIMEWriter,如下所示:

  1. 创建%Net.MIMEWriter类的实例。
  2. (可选)指定输出目标。为此,请使用编写器实例的以下方法之一:OutputToDevice()(默认值)、OutputToFile()OutputToStream()
  3. 调用编写器的方法,根据需要编写输出:
  • 给定标头名称和值后,WriteHeader()将写入该标头。
  • 给定%Net.MIMEPart的实例,WriteMIMEBody()写入消息正文,消息正文可以有多个部分。

如果消息是多部分的,则此方法不写入任何标头;编写它们是责任。但是,如果消息不是多部分的,则该方法会写入标头。

  • 给定%Net.MIMEPart的实例,WriteMIMEMessage()写入MIME消息,包括所有标头。

对于单部分消息,WriteMIMEBody()WriteMIMEMessage()产生相同的输出。

示例:WriteMIMEMessage()

下面的示例演示WriteMIMEMessage()的用法:


ClassMethod WriteMIMEMessage(text As %String, header As %String) As %Status
{
	Set msg=##class(%Net.MIMEPart).%New()
	Set msg.Body=##class(%GlobalCharacterStream).%New()
	Do msg.Body.Write(text)

	//specify some headers
	Set msg.ContentType="text/html"
	Set msg.ContentCharset="us-ascii"
	Do msg.SetHeader("Custom-header",header)

	//create MIME writer; write MIME message
	Set writer=##class(%Net.MIMEWriter).%New()
	Set status=writer.WriteMIMEMessage(msg)

	If $$$ISERR(status) do $system.Status.DisplayError(status)
	Quit $$$OK
}

以下终端会话显示了此方法的使用情况: java

DHC-APP>w ##class(PHA.TEST.HTTP).WriteMIMEMessage("message text","my header value")                                                                             CONTENT-TYPE: text/html; charset=us-ascii
Custom-header: my header value
 
message text
1

阅读MIME邮件

要读取MIME邮件,请使用%Net.MIMEReader,如下所示:

  1. 创建%Net.MIMEReader类的实例。
  2. 指定输入源。为此,请使用读取器实例的以下方法之一:OpenFile()OpenStream()
  3. 调用读取器实例的ReadMIMEMessage()方法。此方法通过引用返回%Net.MIMEPart的实例作为第一个参数。它返回一个状态,应该检查该状态。
0
0 164
文章 姚 鑫 · 五月 27, 2021 5m read

第八章 处理收到的电子邮件

处理收到的电子邮件

本节介绍如何处理通过%Net.POP3检索到的电子邮件(%Net.MailMessage)。

Message Basics

检索电子邮件(%Net.MailMessage)后,通常首先确定它是哪种类型的邮件以及如何阅读它;也就是说,它是否是多部分邮件以及各部分是否是二进制的。在此步骤中,您可以使用ContentType属性。或者,可以使用IsBinaryIsHTMLIsMultiPart属性,它们间接提供与contentType相同的信息。

如果消息是多部分消息,则每个部分都是%Net.MailMessagePart的一个实例。

Message Headers

消息本身和消息的每个部分都有一组标头。

%Net.MailMessage%Net.MailMessagePart类提供的属性使可以轻松访问最常用的标头。例如,%Net.MailMessage提供收件人、发件人、主题和日期等属性。Headers数组属性允许访问任何自定义标题.

此外,如果已通过%Net.POP3检索到消息,则可以使用GetAttribute()方法。在给定标头名称和属性的情况下,此方法返回该属性的值。

Message Contents

了解常规消息结构后,请使用以下技术检索内容:

  • 对于多部分消息,请使用Parts属性,该属性是部分的数组。Parts.Count()给出部件的数量。每个部件的键都是一个整数,从1开始。使用GetAt()方法检索给定的部件。消息部分是%Net.MailMessagePart的实例。
  • 对于二进制消息(或消息部分),请使用BinaryData属性。
  • 对于文本消息(或消息部分),请使用TextData属性。
    • 如果IsHTML为0,则TextData属性为普通文本字符串。
    • 如果IsHTML为1,则TextData属性为HTML文本字符串。

请注意,发送邮件的电子邮件客户端确定邮件中的任何包装。邮件服务器无法控制这一点,

其他消息信息

MessageSize属性表示邮件的总长度(不包括任何附加的电子邮件)。

以下方法提供有关消息的其他信息:

GetLocalDateTime()

返回检索消息的日期和时间,并转换为$HOROLOG格式的本地时间。

GetUTCDateTime()

返回检索消息的日期和时间,并以$HOROLOG格式转换为UTC。

GetUTCSeconds()

返回自1840年12月31日以来检索消息的日期和时间(秒)。

以下类方法也可用于时间/日期转换:

HToSeconds()

$HOROLOG格式的日期/时间转换为自1840年12月31日以来的秒的类方法。

SecondsToH()

将自1840年12月31日以来的秒数转换为$HOROLOG格式的日期/时间的类方法。

示例1:ShowMsgInfo()

ClassMethod ShowMsgInfo(msg as %Net.MailMessage)
{
    Write "Message details *****",!
    Write "To (count): ", msg.To.Count(),!
    Write "From: ", msg.From,!
    Write "Cc (count): ", msg.Cc.Count(),!
    Write "Bcc (count): ", msg.Bcc.Count(),!
    Write "Date: ", msg.Date,!
    Write "Subject: ", msg.Subject,!
    Write "Sender: ", msg.Sender,!
    Write "IsMultipart: ", msg.IsMultiPart,!
    Write "Number of parts: ", msg.Parts.Count(),!
    Write "Number of headers: ", msg.Headers.Count(),!
    Write "IsBinary: ", msg.IsBinary,!
    Write "IsHTML: ", msg.IsHTML,!
    Write "TextData: ", msg.TextData.Read(),!
    Write "BinaryData: ", msg.BinaryData.Read(),!
}

此方法产生类似于以下内容的输出:

Message details *****
To (count): 1
From: "XXX XXX" <XXX@XXX.com>
Cc (count): 0
Bcc (count): 0
Date: Fri, 16 Nov 2007 11:57:46 -0500
Subject: test 5
Sender:
IsMultipart: 0
Number of parts: 0
Number of headers: 16
IsBinary: 0
IsHTML: 0
TextData: This is test number 5, which is plain text.
BinaryData:

示例2:ShowMsgPartInfo()

以下方法写入有关消息一部分的信息:

ClassMethod ShowMsgPartInfo(msg as %Net.MailMessage, partno as %Integer)
{
    Set part=msg.Parts.GetAt(partno)
    Write "Message part details *****",!
    Write "Message part: ", partno,!
    Write "IsMultipart: ", part.IsMultiPart,!
    Write "Number of parts: ", part.Parts.Count(),!
    Write "Number of headers: ", part.Headers.Count(),!
    Write "IsBinary: ", part.IsBinary,!
    Write "IsHTML: ", part.IsHTML,!
    Write "TextData: ", part.TextData.Read(),!
    Write "BinaryData: ", part.BinaryData.Read(),!
}

这将产生与以下内容类似的输出(给定的消息与前面显示的不同):

Message part details *****
Message part: 1
IsMultipart: 0
Number of parts: 0
Number of headers: 2
IsBinary: 0
IsHTML: 0
TextData: 1 test string
 
 
BinaryData:

示例3:ShowMsgHeaders()

下面的方法写入有关消息头的信息;可以编写一个类似的方法,对消息部分执行相同的操作。

ClassMethod ShowMsgHeaders(msg as %Net.MailMessage)
{
    Set headers=msg.Headers
    Write "Number of headers: ", headers.Count(),!
    
    //iterate through the headers
    Set key=""
    For {
        Set value=headers.GetNext(.key) 
        Quit:key=""  
        Write "Header:",key,!
        Write "Value: ",value,!!
    }
}

这将产生类似于以下内容的输出:

Number of headers: 16
Header: content-class
Value: urn:content-classes:message
 
Header: content-type
Value: multipart/alternative; boundary="----_=_NextPart_001_01C8286D.D9A7F3B1"
 
Header: date
Value: Fri, 16 Nov 2007 11:29:24 -0500
 
Header: from
Value: "XXX XXX" <XXX@XXX.com>
 
Header: message-id
Value: <895A9EF10DBA1F46A2DDB3AAF061ECD501801E86@Exchange1_backup>
 
Header: mime-version
Value: 1.0
 
...

自动编码和字符翻译

电子邮件部分包含有关使用的字符集和使用的内容传输编码(如果有的话)的信息。作为参考,本节介绍如何使用此信息。

外发电子邮件

%Net.SMTP检查每个部分的字符集属性,然后应用适当的转换表。

如果未指定给定部件的字符集属性,InterSystems IRIS将使用UTF-8。

%Net.SMTP还检查ContentTransferEncoding属性。如果此属性为 "base64""quoted-printable",则在创建消息时,%Net.SMTP会根据需要对正文进行编码。(如果内容传输编码为 "7bit""7bit",则不需要编码。)

重要提示:请注意,如果内容为“Base64”编码,则不能包含任何Unicode字符。如果要发送的内容包括Unicode字符,请确保使用$ZCONVERT将内容转换为UTF-8。

传入电子邮件

%Net.POP3检查每个邮件部分的Content-Transfer-Encoding标头,并根据需要对正文进行解码。

然后%Net.POP3检查每个邮件部分的Content-Type标头。这会影响消息部分的字符集属性,还会控制在InterSystems IRIS中创建消息部分时使用的转换表。

0
0 192
文章 姚 鑫 · 五月 26, 2021 6m read

第七章 从POP3服务器提取电子邮件

将附件另存为文件

Content-Disposition标头可以指定附件,可以带文件名,也可以不带文件名。例如:

Content-Disposition: attachment; filename=genome.jpeg;

如果Content-Disposition标头确实指定了附件,则%Net.POP3实例可以将邮件中的所有附件保存到文件。要实现这一点,请执行以下操作:

  1. 指定%Net.POP3实例的以下属性:
  • StoreAttachToFile指定为%1
  • StoreInlineToFile指定为%1
  • AttachDir指定有效目录。根据操作系统的不同,确保使用斜杠(/)或反斜杠(\)结束目录名称。还要确保该目录已经存在,并且用户对其具有写访问权限。
  1. 调用%Net.POP3实例的Fetch()FetchMessage()

每个文件名的确定方式如下:

  1. 如果Content-Disposition标头指定了文件名,则使用该文件名。
  2. 否则,如果Content-Type标头指定了文件名,则使用该文件名。
  3. 否则,系统将创建格式为ATTxxxxx.dat的名称。

请注意以下几点:

  • 如果该文件已存在,则不会下载附件。
  • AttachDir没有默认值。
  • 附件的大小不受IRIS的限制,但可能受文件系统的限制。
  • 这里不使用DirFileName属性。它们仅在将附件上载到邮件时才相关,如向邮件添加附件中所述。

示例:GetMsg()

下面的示例方法在给定%Net.POP3实例和邮件编号的情况下检索整个邮件:

ClassMethod GetMsg(server as %Net.POP3,msgno as %Integer) as %Net.MailMessage 
{
    Set status=server.Fetch(msgno,.msg)
    If $$$ISERR(status) {
       Do $System.Status.DisplayError(status) 
       Quit $$$NULLOREF
    }
    Quit msg
}

如果邮件有附件,并且指定了%Net.POP3服务器的StoreAttachToFileStoreInlineToFileAttachDir属性,则在调用此方法时,这些附件将保存到给定目录。

获取附加电子邮件

当连接到邮箱时,可以下载附加到收件箱中电子邮件的任何电子邮件。为此,请使用%Net.POP3实例的GetAttachedEmail()方法检索附带的电子邮件的内容。

在给定%Net.MailMessagePart实例的情况下,此方法返回包含该消息部分内容的单部分消息。具体地说,它返回(作为输出参数)%Net.MailMessage的实例,该实例使用从附加电子邮件中获取的数据进行初始化。

其他消息检索方法

本节列出了%Net.POP3可用于检查和检索邮件的所有方法。

Fetch()

method Fetch(MessageNumber As %Integer, 
             ByRef MailMsg As %Net.MailMessage,
             Delete As %Boolean = 0,
             messageStream As %BinaryStream) as %Status

返回(通过引用)由MessageNumber指示的消息,并选择性地将该消息标记为删除。请注意,如果邮件已标记为删除,则此方法将返回错误状态。

如果指定了messageStream,则原始消息将写入此二进制流。

FetchFromStream()

method FetchFromStream(messageStream As %BinaryStream, ByRef Msg As %Net.MailMessage) as %Status

此方法在为FETCH()指定messageStream参数时使用。

从给定的二进制流中检索单个电子邮件。MessageStream必须是包含消息的二进制流。消息在MSG中通过引用返回。这可能是一条由多部分组成的消息。

FetchMessageInfo()

method FetchMessageInfo(MessageNumber As %Integer, 
                        Lines As %Integer, 
                        ByRef From As %String, 
                        ByRef To As %String, 
                        ByRef Date As %String, 
                        ByRef Subject As %String, 
                        ByRef MessageSize As %Integer, 
                        ByRef MsgHeaders As %ArrayOfDataTypes, 
                        ByRef MessageText As %String) as %Status

在给定消息编号的情况下,此方法返回(通过引用)该消息的特定消息头、消息大小、消息头数组和给定的文本行数。如果邮件当前标记为删除,则此方法返回错误状态。

GetAttachedEmail()

method GetAttachedEmail(msgpart As %Net.MailMessagePart, 
       Output mailmsg As %Net.MailMessage) as %Status

在给定消息部分的情况下,此方法返回(作为输出参数)使用消息部分的数据初始化的单部分电子邮件消息。

GetMessageUID()

method GetMessageUID(MessageNumber As %Integer, 
                     ByRef UniqueID As %String) as %Status

在给定消息编号的情况下,通过引用返回消息的UID。有关消息编号和UID的详细信息,请参阅上一节。如果邮件当前标记为删除,则此方法返回错误状态。

GetMessageUIDArray()

method GetMessageUIDArray(MessageNumber As %String = "", 
                          ByRef ListOfUniqueIDs As %ArrayOfDataTypes) as %Status

如果给定空字符串作为第一个参数,此方法将通过引用返回有关邮箱中邮件的信息数组(不包括当前标记为删除的任何邮件)。此数组中的每个元素都包含有关一条消息的以下信息:

Array KeyArray Item
邮箱中当前状态的邮件编号。第一条消息是数字1,依此类推。给定消息的消息编号不能保证在所有会话中都相同。唯一消息标识符(UID),它是此消息在所有会话中可用的永久标识符。UID对于每个邮箱都是唯一的。

或者,在给定消息编号的情况下,此方法返回一个包含该消息的UID的单元素数组。在这种情况下,如果邮件当前被标记为删除,则该方法返回错误状态。

GetSizeOfMessages()

method GetSizeOfMessages(MessageNumber As %String = "", 
                         ByRef ListOfSizes As %ArrayOfDataTypes) as %Status

如果给定空字符串作为第一个参数,此方法将通过引用返回有关邮箱中邮件的信息数组(不包括当前标记为删除的任何邮件)。此数组中的每个元素都包含有关一条消息的以下信息:

Array KeyArray Item
邮箱中当前状态的邮件编号。此消息的大小(以字节为单位)。

或者,在给定消息编号的情况下,此方法返回一个包含该消息大小(以字节为单位)的单元素数组。在这种情况下,如果邮件当前标记为删除,此方法将返回错误状态。

删除邮件

当连接到邮箱时,可以在登录的邮箱中标记要删除的邮件。可以通过几种方式来实现这一点。

  • 可以使用DeleteMessage()方法。此方法接受一个参数,即要删除的消息编号。
  • 当使用Fetch()FetchMessage()方法检索邮件时,可以指定一个可选参数,告知POP3服务器在检索邮件后将其标记为删除。

请记住以下几点:

  • 这些方法不会删除邮件;它们会将其标记为删除。在使用QuitAndCommit()完成POP3事务之前,邮件不会被删除。如果只是断开与服务器的连接,所做的更改将被丢弃。
  • 可以调用RollbackDeletes()方法来更改消息,以便不再将它们标记为删除。
  • 这些方法中的每一个都返回一个状态,您应该检查该状态。

示例:GetMsgAndDelete()CommitteeChanges()

下面的示例检索一封邮件并将其标记为删除:

ClassMethod GetMsgAndDelete(ByRef server As %Net.POP3, msgno As %Integer) As %Net.MailMessage
{
  //third argument to Fetch says whether to 
  //mark for deletion
  Set status=server.Fetch(msgno,.msg,1)
  If $$$ISERR(status) {
    Do $System.Status.DisplayError(status) 
    Quit $$$NULLOREF
  }
  
  Quit msg
}

请注意,此消息返回(通过引用)%Net.POP3的更改版本;更改后的版本包含有关哪条消息被标记为删除的信息。

可以将上述方法与如下所示的方法一起使用:

ClassMethod CommitChanges(server As %Net.POP3) As %Status
{
  //commit all changes and log out
  Set status=server.QuitAndCommit()
  If $$$ISERR(status) {
    Do $System.Status.DisplayError(status) 
    Quit $$$ERROR($$$GeneralError,"Failed to commit changes")
  }
  Quit $$$OK
}

或者,可以使用RollbackDeletes()QuitAndRollback()回滚更改。

0
0 157
文章 姚 鑫 · 五月 25, 2021 7m read

第六章 从POP3服务器提取电子邮件

从POP3服务器提取电子邮件

与POP3服务器通信

如果拥有所需的权限,并且邮件服务器正在运行,则可以使用POP3协议从该服务器下载和处理电子邮件。通常,要与POP3服务器通信,请登录,执行一系列影响邮箱的操作,然后提交或回滚任何更改。要在系统间IRIS中执行此操作,请执行以下操作:

  1. 创建%Net.POP3的实例。此对象描述将使用的POP3服务器。
  2. 可以选择指定%Net.POP3实例的以下属性:
  • port -指定要使用的端口;默认值为110
  • timeout 超时-指定读取超时(以秒为单位);默认值为30秒。
  • StoreAttachToFile-指定在读取邮件时(当邮件包含Content-Disposition;附件标题时)是否将每个附件保存到文件。默认值为False。请注意,除非还设置了AttachDir,否则此设置不起任何作用。
  • StoreInlineToFile-指定在读取邮件时(当邮件包含Content-Disposition;内联标题时)是否将每个内联附件保存到文件中。默认值为False。请注意,除非还设置了AttachDir,否则此设置不起任何作用。
  • AttachDir-指定将附件保存到的目录。没有违约。根据操作系统的不同,确保使用斜杠(/)或反斜杠(\)结束目录名称。还要确保该目录已经存在,并且用户对其具有写访问权限。
  • IgnoreInvalidBase64Chars-指定是否忽略在base-64解码期间发现的无效字符。默认值为FALSE(无效字符会导致错误)。请注意,RFC 2045对于应忽略意外字符还是应在Base-64解码期间导致错误的问题含糊不清。
  1. 要使用SSL/TLS连接到POP3服务器,请执行以下操作:

a. 将SSLConfiguration属性设置为要使用的已激活SSL/TLS配置的名称。

b. 将UseSTARTTLS属性设置为0或1。

在大多数情况下,使用值0。如果服务器交互在普通TCP套接字上开始,然后在与普通套接字相同的端口上切换到TLS,则使用值1。

c. 或者,将SSLCheckServerIdentity属性设置为1。如果要验证证书中的主机服务器名称,请执行此操作。

  1. 调用实例的Connect()方法。此方法按顺序接受三个参数:

a. POP3服务器的名称

b. 用户名

c. 密码

  1. 使用实例的方法检查邮箱、检索邮件和删除邮件。以下各节提供了详细信息。
  2. 或者,要防止连接超时,请调用%Net.POP3实例的Ping()方法。
  3. 或者,如果已将邮件标记为要删除,但现在选择不删除它们,请调用%Net.POP3实例的RollbackDeletes()方法。
  4. 完成对邮箱的更改后,请调用以下方法之一:
  • QuitAndCommit()-提交更改并从邮件服务器注销。
  • QuitAndRollback()-回滚更改并从邮件服务器注销。

这些方法中的每一个都返回一个状态,应该在继续之前检查该状态。另请参阅%Net.POP3的类引用以获取完整的方法签名。

以下各节中的示例使用了本手册编写时可用的两种不同的免费POP3服务。选择这些服务并不意味着特别认可。还要注意的是,这些示例并没有显示实际的密码。

示例1:HotPOPAsPOP3()

以下方法使用以前为此设置的帐户登录到HotPOP POP3服务器:

ClassMethod HotPOPAsPOP3() As %Net.POP3
{
	Set server=##class(%Net.POP3).%New()

	//HotPOP POP3服务器使用默认端口,
	Set server.port=110

	//以防我们计划获取任何带有附件的邮件
	Set server.StoreAttachToFile=1
	Set server.StoreInlineToFile=1
	Set server.AttachDir="c:\DOWNLOADS\"

	Set servername="pop.hotpop.com"
	Set user="isctest@hotpop.com"
	Set pass="123pass"

	Set status=server.Connect(servername,user,pass)
	If $$$ISERR(status) {
		Do $System.Status.DisplayError(status) 
		Quit $$$NULLOREF
	}
	Quit server
}

此方法返回%Net.POP3服务器实例。本主题后面的许多示例都接受%Net.POP3实例作为参数。

示例2:YPOPsAsPOP3()

以下方法还返回%Net.POP3服务器实例。在本例中,我们使用的是YPOPS,这是一个客户端软件,提供对Yahoo电子邮件帐户的SMTPPOP3访问。它使用已为此目的设置的测试帐户:

ClassMethod YPOPsAsPOP3() As %Net.POP3
{
	Set server=##class(%Net.POP3).%New()

	//YPOPs uses the default port 
	//but let's set it anyway
	Set server.port=110

	//just in case we plan to fetch any messages
	//that have attachments
	Set server.StoreAttachToFile=1
	Set server.StoreInlineToFile=1
	Set server.AttachDir="c:\DOWNLOADS\"

	//local host acts as the server
	Set servername="127.0.0.1"
	//YPOPs works with a Yahoo email account
	Set user="isc.test@yahoo.com"
	Set pass="123pass"

	Set status=server.Connect(servername,user,pass)
	If $$$ISERR(status) {
		Do $System.Status.DisplayError(status) 
		Quit $$$NULLOREF
	}
	Quit server
}

获取有关邮箱的信息

当连接到POP3服务器时,将登录到一个用户帐户,并有权访问该用户帐户的邮箱。使用以下方法查找邮箱包含的内容:

GetMailBoxStatus()

通过引用返回邮箱中的邮件数和邮箱使用的字节数。

GetMessageUIDArray()

如果给定空字符串作为第一个参数,此方法将通过引用返回有关邮箱中邮件的信息数组(不包括当前标记为删除的任何邮件)。此数组中的每个元素都包含有关一条消息的以下信息:

Array KeyArray Item
邮箱中当前状态的邮件编号。第一条消息是数字1,依此类推。给定消息的消息编号不能保证在所有会话中都相同。唯一消息标识符(UID),它是此消息在所有会话中可用的永久标识符。UID对于每个邮箱都是唯一的。

GetSizeOfMessages()

如果给定空字符串作为第一个参数,此方法将通过引用返回有关邮箱中邮件的信息数组(不包括当前标记为删除的任何邮件)。此数组中的每个元素都包含有关一条消息的以下信息:

Array KeyArray Item
邮箱中当前状态的邮件编号。此消息的大小(以字节为单位)。

这些方法中的每一个都返回一个状态,应该在继续之前检查该状态。

示例:ShowMailbox()

例如,以下方法写入有关我们当前访问的邮箱的信息:

ClassMethod ShowMailbox(server as %Net.POP3) 
{
    Set status=server.GetMailBoxStatus(.count,.size)
    If $$$ISERR(status) {
       Do $System.Status.DisplayError(status) 
       Quit 
    }
    Write "Mailbox information *****",!
    Write "Number of messages in mailbox: ",count,!
    Write "Size of messages: ",size,!

    Set status=server.GetMessageUIDArray(,.uids)
    Set status=server.GetSizeOfMessages(,.sizes)
    
    //iterate through messages, get info, and write it
    For i=1:1:count {
        Set uid=uids.GetAt(i)
        Set size=sizes.GetAt(i)
        Write "Msg number:", i,"   UID:",uid, "   size:",size,!
    }

}

此方法会生成类似于以下内容的输出:

Mailbox information *****
Number of messages in mailbox: 4
Size of messages: 18634
Msg number:1   UID:6ef78df6fd660391   size:7245
Msg number:2   UID:7410041a6faf4a87   size:5409
Msg number:3   UID:5555af7fa489e406   size:5121
Msg number:4   UID:299ad2b54c01a6be   size:859

从邮箱提取邮件

要简单地获取消息,请使用%Net.POP3类的以下方法之一:

Fetch()

给定消息编号作为第一个参数,此方法返回(通过引用,作为第二个参数)包含该消息的%Net.MailMessage实例。

FetchMessage()

给定消息编号作为第一个参数,此方法返回(通过引用)诸如FromTo和其他公共标头等信息、包含所有标头(包括公共标头)的数组以及消息内容本身

这些方法中的每一个都返回一个状态,您应该在继续之前检查该状态。请注意,如果邮件当前被标记为删除,则这些方法将返回错误状态。

示例:FetchMailbox()

下面的示例是“获取有关邮箱的信息”中描述的ShowMailbox示例的变体。此方法使用fetch()方法,检查每封邮件,并写入每封邮件的主题行:

ClassMethod FetchMailbox(server As %Net.POP3)
{
	Set status=server.GetMailBoxStatus(.count,.size)
	If $$$ISERR(status) {
		Do $System.Status.DisplayError(status) 
		Quit $$$NULLOREF
	}
	Write "Mailbox information *****",!
	Write "Number of messages in mailbox: ",count,!
	Write "Size of messages: ",size,!

	Set status=server.GetMessageUIDArray(,.uids)
	Set status=server.GetSizeOfMessages(,.sizes)

	//iterate through messages, get info, and write it
	For i=1:1:count {
		Set uid=uids.GetAt(i)
		Set size=sizes.GetAt(i)
		Set status=server.Fetch(i,.msg)
		If $$$ISERR(status) {
			Set subj="***error***"
		} else{
			Set subj=msg.Subject
		}
		Write "Msg number:", i,"  UID:",uid, "  Size:",size
		Write "  Subject: ",subj,!
	}
}
0
0 144
文章 姚 鑫 · 五月 24, 2021 7m read

第五章 向邮件添加附件

向邮件添加附件

可以将附件添加到电子邮件或消息部分(具体地说,是添加到%Net.MailMessagePart%Net.MailMessage的实例)。要执行此操作,请使用以下方法:

这些方法中的每一种都会将附件添加到原始邮件(或邮件部分)的Parts数组中,并自动将IsMultiPart属性设置为1。

AttachFile()

method AttachFile(Dir As %String, 
                  File As %String, 
                  isBinary As %Boolean = 1, 
                  charset As %String = "", 
                  ByRef count As %Integer) as %Status

将给定文件附加到电子邮件。默认情况下,文件以二进制附件的形式发送,但您可以将其指定为文本。如果文件是文本,还可以指定该文件使用的字符集。

具体地说,此方法创建%Net.MailMessagePart的实例,并根据需要将文件内容放在BinaryDataTextData属性中,并根据需要设置CharSet属性和TextData.TranslateTable属性。该方法通过引用返回一个整数,该整数指示此新消息部分在部件数组中的位置。

此方法还设置消息或消息部分的DirFileName属性。

AttachStream()

method AttachStream(stream As %Stream.Object, 
                    Filename As %String, 
                    isBinary As %Boolean = 1, 
                    charset As %String = "", 
                    ByRef count As %Integer) as %Status

将给定流附加到电子邮件。如果指定了Filename,则附件被视为文件附件。否则,它将被视为内联附件。

AttachNewMessage()

method AttachNewMessage() as %Net.MailMessagePart

创建%Net.MailMessage的新实例,将其添加到消息中,并返回新修改的父消息或消息部分。

AttachEmail()

给定一封电子邮件(%Net.MailMessage的实例),此方法会将其添加到邮件中。此方法还设置消息或消息部分的DirFileName属性。

注意:此方法将contentType设置为"message/rfc822"。在这种情况下,不能添加任何其他附件。

示例:MessageWithAttach()

以下示例生成一封带有一个硬编码附件的简单电子邮件。它不为邮件提供任何地址;可以在实际发送邮件时提供该信息

/// w ##class(PHA.TEST.HTTP).MessageWithAttachment()
ClassMethod MessageWithAttachment() As %Net.MailMessage
{
	Set msg = ##class(%Net.MailMessage).%New()
	Set msg.Subject="Message with attachment "_$h
	Set msg.IsBinary=0
	Set msg.IsHTML=0
	Do msg.TextData.Write("This is the main message body.")

	//add an attachment
	Set status=msg.AttachFile("E:\", "HttpDemo.pdf")
	If $$$ISERR(status) {
		Do $System.Status.DisplayError(status)
		Quit $$$NULLOREF
	}
	b
	Quit msg
}

使用SMTP服务器发送电子邮件

如果有权访问SMTP服务器,则可以发送电子邮件。SMTP服务器必须正在运行,并且必须具有使用它所需的权限。要发送电子邮件,请执行以下操作:

  1. 创建%Net.SMTP实例并根据需要设置其属性,特别是以下属性:
  • Smtpserver是正在使用的SMTP服务器的名称。
  • 端口是在SMTP服务器上使用的端口;默认值为25。
  • 时区指定RFC 822指定的服务器时区,例如 "EST""-0400""LOCAL"。如果未设置,消息将使用世界时。

此对象描述将使用的SMTP服务器。

  1. 如果SMTP服务器需要身份验证,请指定必要的凭据。为此:

a. 创建%Net.Authenticator的实例。

b. 设置此对象的用户名和密码属性。

c. 将%Net.SMTP实例的验证器属性设置为等于此对象。

d. 如果邮件本身具有授权发件人,请设置%Net.SMTP实例的AuthFrom属性。

  1. 要使用到SMTP服务器的SSL/TLS连接,请执行以下操作:

a. 将SSLConfiguration属性设置为要使用的已激活SSL/TLS配置的名称。

SSL/TLS配置包括一个名为Configuration Name的选项,该选项是在此设置中使用的字符串。

b. 将UseSTARTTLS属性设置为0或1。

在大多数情况下,使用值0。如果服务器交互在普通TCP套接字上开始,然后在与普通套接字相同的端口上切换到TLS,则使用值1。

或者,将SSLCheckServerIdentity属性设置为1。如果要验证证书中的主机服务器名称,请执行此操作。

  1. 创建要发送的电子邮件(如“创建单部分电子邮件”和“创建多部分电子邮件”中所述)。
  2. 调用SMTP实例的send()方法。此方法返回一个状态,应该检查该状态。
  3. 如果返回的状态指示错误,请检查Error属性,该属性包含错误消息本身。
  4. 检查FailedSend属性,该属性包含发送操作失败的电子邮件地址列表。

以下各节中的示例使用了两种不同的免费SMTP服务,这些服务在编写本手册时是可用的。选择这些服务并不意味着特别认可。还要注意的是,这些示例并没有显示实际的密码。

Samples命名空间中还有其他示例。要查找它们,请在该命名空间中搜索%Net.SMTP

重要提示:%Net.SMTP将邮件正文写入临时文件流。默认情况下,该文件被写入命名空间目录,如果该目录需要特殊的写入权限,则不会创建该文件,并且您会得到一个空的消息正文。

可以为这些临时文件定义新路径,并选择不限制写访问的路径(例如,/tmp)。为此,请设置全局节点%SYS("StreamLocation",namespace),其中NAMESPACE是运行代码的名称空间。例如:

Set ^%SYS("StreamLocation","SAMPLES")="/tmp" 

如果%SYS("StreamLocation",namespace)NULL,则InterSystems IRIS使用%SYS("TempDir",namespace)指定的目录。如果未设置%SYS("TempDir",namespace),则IRIS使用 %SYS("TempDir")指定的目录

示例1:HotPOPAsSMTP()SendSimpleMessage()

此示例由一起使用的两个方法组成。第一个创建%Net.SMTP的实例,该实例使用已在HotPOP SMTP服务器上设置的测试帐户:


/// w ##class(PHA.TEST.HTTP).HotPOPAsSMTP()
ClassMethod HotPOPAsSMTP() As %Net.SMTP
{
  Set server=##class(%Net.SMTP).%New()
  Set server.smtpserver="smtp.hotpop.com"
  //HotPOP SMTP服务器使用默认端口(25)
  Set server.port=25
  
  //创建对象以进行身份验证
  Set auth=##class(%Net.Authenticator).%New()
  Set auth.UserName="isctest@hotpop.com"
  Set auth.Password="123pass"
  
  Set server.authenticator=auth
  Set server.AuthFrom=auth.UserName
  b
  Quit server
}

下一个方法使用提供的SMTP服务器作为参数发送一条简单、唯一的消息:

ClassMethod SendSimpleMessage(server As %Net.SMTP) As %List
{
  Set msg = ##class(%Net.MailMessage).%New()
  Set From=server.authenticator.UserName
  Set:From="" From="xxx@xxx.com"
  Set msg.From = From
  
  Do msg.To.Insert("xxx@xxx.com")
  //Do msg.Cc.Insert("yyy@yyy.com")
  //Do msg.Bcc.Insert("zzz@zzz.com")
  Set msg.Subject="Unique subject line here "_$H
  Set msg.IsBinary=0
  Set msg.IsHTML=0
  Do msg.TextData.Write("This is the message.")
  
  Set status=server.Send(msg)
  If $$$ISERR(status) {
    Do $System.Status.DisplayError(status)
    Write server.Error
    Quit ""
  }
  Quit server.FailedSend
}

示例2:YPOPsAsSMTP()

此示例创建使用YPOPS%Net.SMTP实例的实例,YPOPS是一种客户端软件,提供对Yahoo电子邮件帐户的SMTPPOP3访问。它使用已为此目的设置的测试帐户:

ClassMethod YPOPsAsSMTP() As %Net.SMTP
{
  Set server=##class(%Net.SMTP).%New()
  //local host acts as the server
  Set server.smtpserver="127.0.0.1"
  //YPOPs uses default port, apparently
  Set server.port=25
  
  //Create object to carry authentication
  Set auth=##class(%Net.Authenticator).%New()
  //YPOPs works with a Yahoo email account
  Set auth.UserName="isc.test@yahoo.com"
  Set auth.Password="123pass"
  
  Set server.authenticator=auth
  Set server.AuthFrom=auth.UserName
  Quit server
}

可以将其与上例中所示的SendSimpleMessage方法一起使用。

示例3:SendMessage()

以下更灵活的方法同时接受SMTP服务器和电子邮件。电子邮件应已包含主题行(如果SMTP服务器要求),但不必包含地址。然后,此方法将电子邮件发送到一组硬编码的测试目的地:

ClassMethod SendMessage(server As %Net.SMTP, msg As %Net.MailMessage) As %Status
{
  Set From=server.authenticator.UserName
  //make sure From: user is same as used in authentication
  Set msg.From = From
  
  //finish addressing the message
  Do msg.To.Insert("xxx@xxx.com")
  //send the message to various test email addresses
  Do msg.To.Insert("isctest@hotpop.com")
  Do msg.To.Insert("isc_test@hotmail.com")
  Do msg.To.Insert("isctest001@gmail.com")
  Do msg.To.Insert("isc.test@yahoo.com")
  
  Set status=server.Send(msg)
  If $$$ISERR(status) {
    Do $System.Status.DisplayError(status)
    Write server.Error
    Quit $$$ERROR($$$GeneralError,"Failed to send message")
  }
  Quit $$$OK
}

%Net.SMTP的其他属性

%Net.SMTP类还具有一些您可能需要的其他属性,具体取决于使用的SMTP服务器:

  • AllowHeaderEncoding指定Send()方法是否对非ASCII标头文本进行编码。默认值为1,这意味着非ASCII标头文本按照RFC 2047指定的方式进行编码。
  • ContinueAfterBadSend指定在检测到失败的电子邮件地址后是否继续尝试发送邮件。如果ContinueAfterBadSend为1,系统会将失败的电子邮件地址添加到FailedSend属性的列表中。默认值为0。
  • ShowBcc指定是否将密件抄送标头写入电子邮件。这些通常会被SMTP服务器过滤掉。
0
0 109
文章 姚 鑫 · 五月 23, 2021 7m read

第四章 收发电子邮件

本主题描述如何使用InterSystems IRIS发送和接收MIME电子邮件消息。

注意:本主题中的示例是经过组织的,因此管理电子邮件的方法可以用于不同的电子邮件服务器,这在测试和演示期间非常有用。这不一定是最适合生产需要的代码组织。

支持电子邮件协议

电子邮件使用标准协议通过Internet发送消息。 InterSystems IRIS支持以下三种协议:

  • InterSystems IRIS提供MIME电子邮件的对象表示形式。它支持文本和非文本附件、单部分或多部分邮件正文,以及ASCII和非ASCII字符集的标题。
  • 可以通过SMTP服务器发送电子邮件。SMTP(简单邮件传输协议)是发送电子邮件的Internet标准。
  • 还可以通过POP3从电子邮件服务器检索电子邮件,POP3是从远程服务器检索电子邮件的最常用标准。

注意:InterSystems IRIS不提供邮件服务器。相反,它提供了连接到邮件服务器并与之交互的功能。

InterSystems IRIS如何表示MIME电子邮件

首先,了解InterSystems IRIS如何表示MIME电子邮件非常有用。

通常,多部分MIME邮件由以下部分组成:

  • 一组邮件标头,每个标头都包含邮件发送到的地址等信息。这还包括整个消息的Mime-Type标头和Content-Type标头。

对于多部分消息,Content-Type头必须是多部分/混合或多部分的其他子类型;MIME标准有许多变体。

  • 多个消息部分,每个消息部分由以下部分组成:
    • 一组内容标头,包括Content-Type标头和特定于此部件的其他标头。
    • 一种正文,它可以是文本或二进制,并且可以使用与其它部分的正文不同的字符集。

InterSystems IRIS使用两个类来表示电子邮件:%Net.MailMessage%Net.MailMessagePart,即%Net.MailMessage的超类。下图显示了这些类之间的关系:

image

  • 要表示普通的、由一部分组成的消息,请使用%Net.MailMessage
  • 要表示多部分消息,请使用%Net.MailMessage作为父消息,并使用%Net.MailMessagePart的多个实例作为其部分。

创建由单个部分组成的电子邮件

要创建由单个部分组成的电子邮件,请使用%Net.MailMessage类。要创建邮件,请执行以下操作:

  1. 创建%Net.MailMessage的实例。

提示:可以将字符集指定为%New();的参数,如果这样做,则会设置消息的CharSet属性。

  1. 设置实例的ToFromSubject属性。
  • To收件人-此邮件将发送到的电子邮件地址列表。此属性是标准的InterSystems IRIS列表类;要使用它,需要使用标准列表方法:Insert()GetAt()RemoveAt()Count()Clear()
  • From发件人-此邮件的发件人电子邮件地址。
  • Subject主题-邮件的主题(如果您使用的SMTP服务器需要该主题)。
  1. 可以选择设置日期、抄送、密件抄送和其他属性。
  2. 如果邮件不是纯文本,请设置以下属性以指示您要创建的邮件的类型:
  • 如果这是一封HTML邮件,请将IsHTML属性设置为1。
  • 如果这是二进制消息,请将IsBinary属性设置为1。
  1. 若要指定消息及其标头的字符集,请根据需要设置CharSet属性。

重要提示:在添加消息内容之前指定字符集非常重要。

  1. 添加消息内容:
  • 对于纯文本或HTML,请使用TextData属性,该属性是%FileCharacterStream的实例。不需要指定此流的TranslateTable属性;当指定邮件的字符集时,该属性会自动发生。

  • 对于二进制数据,请使用BinaryData属性,该属性是%FileBinaryStream的实例。

提示:指定流的Filename属性时,请确保使用用户有权写入的目录。

要使用这些属性,请使用标准流方法:Write()WriteLine()Read()ReadLine()Rewind()MoveToEnd()Clear()。还可以使用流的Size属性,该属性提供消息内容的大小。

注意:应该了解正在使用的SMTP服务器的要求。例如,某些SMTP服务器要求包含主题标头。同样,某些SMTP服务器不允许任意FROM标头。 类似地,一些SMTP服务器识别优先级报头,而其他服务器则识别X-Priority

示例1:CreateTextMessage()

以下方法创建一条简单消息并为其指定地址:

ClassMethod CreateTextMessage() As %Net.MailMessage
{
    Set msg = ##class(%Net.MailMessage).%New()
    set msg.From = "test@test.com"
    Do msg.To.Insert("xxx@xxx.com")
    Do msg.Cc.Insert("yyy@yyy.com")
    Do msg.Bcc.Insert("zzz@zzz.com")
    Set msg.Subject="subject line here"
    Set msg.IsBinary=0
    Set msg.IsHTML=0
    Do msg.TextData.Write("This is the message.")
    
    Quit msg
}

示例2:SimpleMessage()

在实际发送邮件时指定地。上例的以下变体生成一条没有地址的文本消息:

ClassMethod SimpleMessage() As %Net.MailMessage
{
  Set msg = ##class(%Net.MailMessage).%New()
  Set msg.Subject="Simple message "_$h
  Set msg.IsBinary=0
  Set msg.IsHTML=0
  Do msg.TextData.Write("This is the message.")
  Quit msg
}

Samples命名空间中还有其他示例。要查找它们,请在该命名空间中搜索%Net.MailMessage

创建多部分电子邮件

要创建由多部分组成的电子邮件,请执行以下操作:

  1. 创建%Net.MailMessage的实例,并将其ToFromSubject属性设置为。可以选择设置其他属性以指定其他邮件标头。
  2. IsMultiPart属性设置为1。
  3. MultiPartType属性设置为以下值之一: "related", "alternative", 或 "mixed"。这会影响整个消息的Content-Type标头。
  4. 对于邮件应包含的每个部分,创建%Net.MailMessagePart的实例并指定其属性,如从步骤4开始的“创建由单个部分组成的电子邮件”中所述。
  5. 对于父电子邮件,设置Parts属性,该属性是一个数组。将每个子消息部分插入到此数组中。

发送邮件时,%Net.SMTP类会根据需要自动设置邮件的Content-Type标头(给定MultiPartType属性值)。

指定电子邮件标题

如前所述,消息本身和消息的每个部分都有一组标头。

%Net.MailMessage%Net.MailMessagePart类提供的属性使可以轻松访问最常用的标头,但可以添加所需的任何标头。本节提供有关所有标头以及如何创建自定义标头的信息。

给定消息部分的标头使用由该部分的CharSet属性指定的字符集。

注意:应该了解正在使用的SMTP服务器的要求。例如,某些SMTP服务器要求包含主题标头。同样,某些SMTP服务器不允许任意FROM标头。 类似地,一些SMTP服务器识别优先级报头,而其他服务器则识别X-Priority

指定基本电子邮件标题

设置以下属性(仅在%Net.MailMessage中)以设置邮件本身最常用的标头:

  • To-(必填)此邮件将发送到的电子邮件地址列表。此属性是标准的InterSystems IRIS列表;要使用它,请使用标准列表方法:Insert()GetAt()RemoveAt()Count()Clear()
  • From-(必填)发送此邮件的电子邮件地址。
  • Date-此消息的日期。
  • Subject-(必选)包含此邮件主题的字符串。
  • Sender-邮件的实际发件人。
  • Cc-此邮件将发送到的抄送地址列表。
  • Bcc-此邮件将被发送到的密件副本地址列表。

内容类型标题

发送邮件时,邮件和每个邮件部分的Content-Type标头会自动设置如下:

  • 如果消息是纯文本(IsHTML等于0,IsBinary等于0),则Content-Type标头被设置为 "text/plain
  • 如果消息是HTML(IsHTML等于1,IsBinary等于0),则Content-Type标头设置为“text/html”
  • 如果消息是二进制的(IsBinary等于1),则Content-Type报头设置为如果消息是二进制的(IsBinary等于1),则Content-Type报头设置为"application/octet-stream".
  • 如果邮件是多部分邮件,则会为MultiPartType属性的值适当设置Content-Type标头。

%Net.MailMessage%Net.MailMessagePart都提供了contentType属性,使可以访问Content-Type标头。

内容传输编码标头

%Net.MailMessage%Net.MailMessagePart都提供了ContentTransferEncoding属性,该属性提供了一种指定消息或消息部分的Content-Transfer-Encoding头的简单方法。

此属性可以是以下属性之一:"base64" "quoted-printable" "7bit" "8bit"

默认值如下:

对于二进制消息或消息部分:"base64"

重要提示:请注意,如果内容为“Base64”编码,则不能包含任何Unicode字符。如果要发送的内容包括Unicode字符,请确保使用$ZCONVERT将内容转换为UTF-8,然后对其进行base-64编码。例如:

set BinaryText=$ZCONVERT(UnicodeText,"O","UTF8")
set Base64Encoded=$system.Encryption.Base64Encode(BinaryText)

收件人必须使用相反的过程来解码文本:

set BinaryText=$system.Encryption.Base64Decode(Base64Encoded)
set UnicodeText=$ZCONVERT(BinaryText,"I","UTF8")

对于文本消息或消息部分:"quoted-printable"

自定义标题

使用%Net.MailMessage%Net.MailMessagePart,可以通过访问Headers属性设置或获取自定义标题,该属性是一个具有以下结构的数组:

数组键数组值
标头的名称,如“Priority”标头的值

此属性用于包含其他标头,如X-Priority和其他标头。例如:

 do msg.Headers.SetAt(1,"X-Priority")
 do msg.Headers.SetAt("High","X-MSMail-Priority")
 do msg.Headers.SetAt("High","Importance")

不同的电子邮件服务器和客户端可以识别不同的标头,因此设置多个相似的标头以确保服务器或客户端接收到的邮件具有它可以识别的标头是很有用的。

0
0 126
文章 姚 鑫 · 五月 23, 2021 6m read

第三章 发送HTTP请求

发送HTTP请求

创建HTTP请求后,使用以下方法之一发送该请求:

Delete()

method Delete(location As %String = "", 
              test As %Integer = 0, 
              reset As %Boolean = 1) as %Status

发出HTTP DELETE请求。

Get()

method Get(location As %String = "", 
           test As %Integer = 0, 
           reset As %Boolean = 1) as %Status

发出HTTP GET请求。此方法使Web服务器返回请求的页面。

Head()

method Head(location As %String, 
            test As %Integer = 0, 
            reset As %Boolean = 1) as %Status

发出HTTP Head请求。此方法使Web服务器仅返回响应头,而不返回正文。

Patch()

method Patch(location As %String = "", 
             test As %Integer = 0, 
             reset As %Boolean = 1) as %Status

发出HTTP修补程序请求。使用此方法可以对现有资源进行部分更改。

Post()

method Post(location As %String = "", 
            test As %Integer = 0, 
            reset As %Boolean = 1) as %Status

发出HTTP POST请求。使用此方法可将数据(如表单结果)发送到Web服务器,或上载文件。有关示例,请参阅“发送表单数据”。

Put()

method Put(location As %String = "", 
           test As %Integer = 0, 
           reset As %Boolean = 1) as %Status

发出HTTP PUT请求。使用此方法将数据上载到Web服务器。PUT请求并不常见。

Send()

method Send(type As %String, 
            location As %String, 
            test As %Integer = 0, 
            reset As %Boolean = 1) as %Status

将指定类型的HTTP请求发送到服务器。此方法通常由其他方法调用,但如果要使用不同的HTTP谓词,则提供此方法以供使用。此处type是指定HTTP谓词(如“POST”)的字符串。

在所有情况下:

  • 每个方法都返回一个状态,应该检查该状态。
  • 如果该方法正确完成,则对此请求的响应将位于HttpResponse属性中。
  • Location参数是要请求的URL,例如:"/test.html"
  • Location参数可以包含参数,假定这些参数已经URL转义,例如:"/test.html?PARAM=%25VALUE"PARAM设置为等于%VALUE
  • 使用test参数检查正在发送的是您预期要发送的内容:
    • 如果test为1,则该方法不会连接到远程计算机,而是将其本应发送到Web服务器的内容输出到当前设备。
    • 如果test2,则在发出HTTP请求后将响应输出到当前设备。
  • 在从服务器读取响应后,每个方法都会自动调用Reset()方法,除非test=1Reset=0

Reset()方法重置%Net.HttpRequest实例,以便它可以发出另一个请求。这比关闭此对象并创建新实例要快得多。这还会将Location标头的值移动到Referer标头。

 Set httprequest=##class(%Net.HttpRequest).%New()
 Set httprequest.Server="www.intersystems.com"
 Do httprequest.Get("/")
 

创建和发送多部分POST请求

要创建和发送多部分POST请求,请使用%Net.MIMEPart类,本书后面将详细讨论这些类。下面的示例发送包含两个部分的POST请求。第一部分包括文件二进制数据,第二部分包括文件名。

ClassMethod CorrectWriteMIMEMessage3(header As %String)
{
     // Create root MIMEPart
     Set RootMIMEPart=##class(%Net.MIMEPart).%New()

     //Create binary subpart and insert file data
     Set BinaryMIMEPart=##class(%Net.MIMEPart).%New()
     Set contentdisp="form-data; name="_$CHAR(34)_"file"_$CHAR(34)_"; filename="
                     _$CHAR(34)_"task4059.txt"_$CHAR(34)
     Do BinaryMIMEPart.SetHeader("Content-Disposition",contentdisp)

     Set stream=##class(%FileBinaryStream).%New()
     Set stream.Filename="/home/tabaiba/prueba.txt"
     Do stream.LinkToFile("/home/tabaiba/prueba.txt")

     Set BinaryMIMEPart.Body=stream
     Do BinaryMIMEPart.SetHeader("Content-Type","text/plain")

    // Create text subpart
    Set TextMIMEPart=##class(%Net.MIMEPart).%New()
    Set TextMIMEPart.Body=##class(%GlobalCharacterStream).%New()
    Do TextMIMEPart.Body.Write("/home/tabaiba/prueba.txt")

    // specify some headers
    Set TextMIMEPart.ContentType="text/plain"
    Set TextMIMEPart.ContentCharset="us-ascii"
    Do TextMIMEPart.SetHeader("Custom-header",header)

    // Insert both subparts into the root part
    Do RootMIMEPart.Parts.Insert(BinaryMIMEPart)

    // create MIME writer; write root MIME message
    Set writer=##class(%Net.MIMEWriter).%New()

    // Prepare outputting to the HttpRequestStream
    Set SentHttpRequest=##class(%Net.HttpRequest).%New()
    Set status=writer.OutputToStream(SentHttpRequest.EntityBody)
    if $$$ISERR(status) {do $SYSTEM.Status.DisplayError(status) Quit}

    // Now write down the content
    Set status=writer.WriteMIMEBody(RootMIMEPart)
    if $$$ISERR(status) {do $SYSTEM.Status.DisplayError(status) Quit}

    Set SentHttpRequest.Server="congrio"
    Set SentHttpRequest.Port = 8080

    Set ContentType= "multipart/form-data; boundary="_RootMIMEPart.Boundary
    Set SentHttpRequest.ContentType=ContentType

    set url="alfresco/service/sample/upload.json?"
            _"alf_ticket=TICKET_caee62bf36f0ea5bd51194fce161f99092b75f62"

    set status=SentHttpRequest.Post(url,0) 
    if $$$ISERR(status) {do $SYSTEM.Status.DisplayError(status) Quit}
}

访问HTTP响应

发送HTTP请求后,请求的HttpResponse属性将更新。此属性是%Net.HttpResponse的实例。本节介绍如何使用Response对象。它包括以下主题:

访问响应的数据

HTTP响应的正文包含在响应的Data属性中。此属性包含流对象(特别是%GlobalBinaryStream)。要使用此流,请使用标准流方法:Write()WriteLine()Read()ReadLine()Rewind()MoveToEnd()Clear()。还可以使用流的Size属性。

请求的ReadRawMode属性控制如何读取响应正文。

  • 默认情况下,此属性为False,并且InterSystems IRIS假定正文在响应的HTTP标头中指定的字符集内(并相应地转换该字符集)。
  • 如果此属性为true,InterSystems IRIS将以原始模式读取正文(不执行字符集转换)。

还可以使用OutputToDevice()方法,该方法将完整响应写入当前设备。标头的顺序与Web服务器生成的顺序不同。

下面是一个简单的示例,在该示例中,我们将响应流复制到文件并保存:

/// w ##class(PHA.TEST.HTTP).Stream()
ClassMethod Stream()
{
	set request=##class(%Net.HttpRequest).%New()
	set request.Server="tools.ietf.org"
	set request.Https=1
	set request.SSLConfiguration="yx"
	set status=request.Get("/html/rfc7158")
	if $$$ISERR(status) {
	     do $system.OBJ.DisplayError()
	} else {
	     set response=request.HttpResponse
	}

	Set file=##class(%FileCharacterStream).%New()
	set file.Filename="e:/temp/rfc7158.txt"
	set status=file.CopyFrom(response.Data)
	if $$$ISERR(status) {
	     do $system.OBJ.DisplayError()
	}
	do file.%Close()
	q ""
}

按名称获取HTTP标头

%Net.HttpResponse类将其HTTP标头存储在InterSystems IRIS多维数组中。要访问标头,请使用以下方法:

GetHeader()

返回给定头的值。

GetNextHeader()

返回给定标头之后的下一个标头的名称。

这些方法中的每一个都只有一个参数,即HTTP标头的名称字符串。

还可以使用OutputHeaders()方法,该方法将HTTP标头写入当前设备(尽管它们的生成顺序不同)。

访问有关响应的其他信息

%Net.HttpResponse 类提供了存储HTTP响应其他特定部分的属性:

  • StatusLine存储HTTP状态行,这是响应的第一行。
  • StatusCode存储HTTP状态码。
  • ReasonPhrase存储与StatusCode对应的人类可读的原因。
  • ContentInfo存储关于响应体的附加信息。
  • ContentType存储了Content-Type:标头的值。
  • HttpVersion表示发送响应的web服务器所支持的HTTP版本。
0
0 170
文章 姚 鑫 · 五月 19, 2021 2m read

IRIS 单元测试

第一章 单元测试概述☆☆☆☆

第二章 使用%UnitTest进行单元测试☆☆☆☆

第三章 执行测试☆☆☆

第四章 使用Setup和tear Down方法执行测试☆☆☆

前言

IRIS提供了用于对应用程序进行单元测试的类的%UnitTest包。该包为快速开发单元测试类、执行测试和创建报告提供了便利。可以直接使用该包,也可以扩展其类来自定义单元测试工具。%UnitTest在结构上与用于单元测试的xUnit框架非常相似。熟悉这些框架的开发人员使用%UnitTest会特别容易。

本教程提供对%UnitTest包的快速实践介绍。本教程的第一部分和第二部分是相互独立的,可以按任何顺序完成。

  • 完成本教程的第一部分,了解单元测试和集成测试之间的区别、xUnit框架和%UnitTest之间的结构相似性,以及单元测试在敏捷应用程序开发方法中的作用。
  • 完成本教程的第二部分,学习如何使用%UnitTest中的类为InterSystems IRIS应用程序创建和执行单元测试。

注意:本教程假定基本熟悉ObjectScript和InterSystems IRIS的面向对象开发。

预告

下一期系列将用一个月的时间连载,《Caché 网络实用工具》,敬请期待。

交流群

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9VqwzNP-1608850948003)(3E1D939266954ED48BDAEA9B8086B11E)]

0
0 189
文章 姚 鑫 · 五月 18, 2021 6m read

第四章 使用Setup和tear Down方法执行测试

示例:使用Setup和tear Down方法执行测试

以通常的方式执行新的单元测试。

  1. 在一直在使用的命名空间中打开终端。
  2. ^UnitTestRoot的值设置为包含测试类的目录的父级:
USER> Set ^UnitTestRoot="c:\unittests"
  1. 使用%UnitTest.Manager执行测试:
USER> Do ##class(%UnitTest.Manager).RunTest("mytests")
  1. IRIS加载测试类、编译类、执行测试并向终端发送报告。

===============================================================================
Directory: C:\unittests\mytests\cls\MyPackage\
===============================================================================
mytests\cls\MyPackage begins ...
Load of directory started on 01/09/2018 14:36:57 '*.xml;*.XML;*.cls;*.mac;*.int;*.inc;*.CLS;*.MAC;*.INT;*.INC'

Loading file C:\unittests\mytests\cls\MyPackage\Tests.xml as xml
Imported class: MyPackage.Tests

Compilation started on 01/09/2018 15:44:01 with qualifiers ''
Compiling class MyPackage.Tests
Compiling routine MyPackage.Tests.1
Compilation finished successfully in 0.033s.

Load finished successfully.
 
  MyPackage.Tests begins ...
      TestAdd() begins ...
        AssertEquals:Test Add(2,2)=4 (passed)
        AssertNotEquals:Test Add(2,2)'=5 (passed)
        LogMessage:Duration of execution: .000073 sec.
      TestAdd passed
      TestEditContact() begins ...
        AssertStatusNotOK:ContactType = Friend (passed)
        AssertStatusOK:ContactType = Personal (passed)
        LogMessage:Duration of execution: .001227 sec.
      TestEditContact passed
    MyPackage.Tests passed
  mytests\cls\MyPackage passed
 
Use the following URL to view the result:
http://10.0.75.1:52773/csp/sys/%25UnitTest.Portal.Indices.cls?Index=10&$NAMESPACE=USER
All PASSED
 

执行测试的选项:测试规格和限定符

通常,可以使用以下形式的命令执行RunTest

Do ##class(%UnitTest.Manager).RunTest("testspec","qualifiers")

Testspec参数确定要运行哪些测试以及在哪里可以找到它们。Testspec的一般形式是testSuite:testcase:testmethod,其中

  • testsuite(必填)。包含导出的测试类的文件目录。该目录必须是名为^UnitTestRoot的目录的子目录。默认情况下,测试管理器执行此目录及其子目录中包含的所有文件中的所有测试。
  • testcase测试用例(可选)。选择包含要执行的测试方法的单个类。格式为PackageName.ClassName。如果存在,则测试管理器仅执行命名类中的测试。
  • testmethod(可选)。挑选由测试用例指示的测试类的一个方法来执行。

限定符参数指定用于运行测试的各种选项。正如我们已经看到的,当想要从.cls文件加载测试时,可以使用“/loadudl”限定符。还可以使用限定符来控制测试类在执行后是否从服务器中删除,是否应该从这些外部文件加载测试,或者系统是否应该在测试失败后进入调试模式,等等。限定符参数是一个可选的命令行参数字符串,用于打开或关闭某些测试管理器行为。例如,“/NoLoad/DEBUG”告诉管理器不要从目录加载任何测试,也就是说,使用当前在InterSystems IRIS中的测试,并在调试模式下运行测试。这些限定符就是所谓的可否定布尔值。例如,这意味着“/NoLoad”等同于“/Load=0”

限定符含义
/load (default)从目录加载测试。使用/NoLoad不加载测试,并执行InterSystems IRIS中已包含的测试。
/run (default)运行测试。使用/norun加载但不运行任何测试。
/delete (default)执行后从InterSystems IRIS中删除测试类。使用/nodelete保存类。
/recursive (default)在指定目录的子目录中查找测试。使用/norecsive不执行子目录中包含的测试。
/debug (default is /nodebug)使用/DEBUG,第一次测试失败后不会执行任何测试。从终端执行时,终端将在第一次故障后进入调试模式。
/autoload使用/autoload=dir^UnitTestRoot目录的子目录“dir”加载测试。
/loadudl.cls而不是XML文件加载测试。

RunTest 示例

以下是使用RunTest执行单元测试的一些示例。

要使用RunTest,必须首先为^UnitTestRoot分配一个有效的目录名:

USER>Set ^UnitTestRoot = "C:\UnitTests"

例1:

USER>Do ##class(%UnitTest.Manager).RunTest()

^UnitTestRoot目录的所有子目录中搜索包含测试类的XML文件。加载它找到的任何测试类并执行测试。

执行后从InterSystems IRIS中删除所有加载的测试类。

例2:

USER>Do ##class(%UnitTest.Manager).RunTest("mytests")
  • 加载并执行^UnitTestRootmytests子目录(及其子目录)中的测试。
  • 在测试类执行后从InterSystems IRIS中删除它们。

例3:

USER>Do ##class(%UnitTest.Manager).RunTest("mytests:MyPackage.Tests")
  • ^UnitTestRoot目录的mytest子目录(及其子目录)加载测试。仅执行MyPackage.Tests中的测试。
  • 执行测试后从InterSystems IRIS中删除所有测试类。

例4:

USER>Do ##class(%UnitTest.Manager).RunTest("mytests:MyPackage.Tests", "/noload/nodelete")
  • 不将测试加载到IRIS。
  • MyPackage.Tests中执行测试。请注意,mytest必须仍然包含带有MyPackage.Tests类的XML文件。
  • 不从IRIS中删除MyPackage.Tests

DebugRunTestCase

%UnitTest.Manager类还包含DebugRunTestCase方法。若要使用此方法,仍必须先将^UnitTestRoot分配给有效目录:

USER>Set ^UnitTestRoot="C:\UnitTests"

例如:

USER>Do ##class(%UnitTest.Manager).DebugRunTestCase("mytests","MyPackage.Tests","","")
  • 该方法不从任何目录加载任何类,也不从InterSystems IRIS删除任何类。
  • 该方法执行MyPackage.Tests中包含的测试。
  • 可选的第三个参数用于限定符。
  • 可选的第四个参数用于指定测试类中要执行的单个测试方法。
  • 如果测试失败,该方法将继续执行其余的测试方法,但将在测试完成时中断。因此,如果从终端执行,则终端将进入调试模式。

注意:使用DebugRunTestCase时,mytest目录实际上不需要包含MyPackage.Tests。相比之下,RunTest总是要求要执行的测试包含在^UnitTestRoot的子目录中,即使在使用NoLoad”时也是如此。

练习

练习1:MyPackage.TestMe包含一个名为CreateContact的方法。此方法创建并返回Contact实例。它接受NameContactType值作为参数。创建一个测试以下内容的单元测试:

  • CreateContact返回的Contact实例具有正确的Name值。
  • CreateContact返回的Contact实例具有正确的ContactType值。
  • CreateContact返回的Contact实例保存正确,即%Save返回OK状态。

练习2:MyPackage.Contact包含名为ByContactType的类查询。它返回具有ContactType指定值的所有Contact实例的ID值。将单元测试添加到MyPackages.Tests,用于测试以下各项:

  • 该查询返回指定ContactType的正确ID值数量。为此,必须正确初始化数据库。
  • 查询返回的每个ID值对应于一个具有指定ContactType值的联系人。

请注意,添加此测试不应破坏在完成教程正文中的示例时添加到MyPackage.Tests中的测试。因此,必须以正确的方式初始化和恢复数据库。

把答案发到评论上!!! 或加群QQ 410039091 分享

源码

0
0 102
文章 姚 鑫 · 五月 17, 2021 5m read

第三章 执行测试

示例:执行测试

现在使用%UnitTest.Manager.RunTest执行单元测试。以下是方法:

  1. 在包含单元测试的名称空间中打开终端;在本例中为用户。如果终端未在正确的命名空间中打开,请使用ZN更改命名空间。
  2. ^UnitTestRoot全局值设置为包含导出的测试类的目录的父级。
DHC-APP>Set ^UnitTestRoot="d:\Temp"
  1. 使用方法%UnitTest.Manager.RunTest执行测试。
DHC-APP>do ##class(%UnitTest.Manager).RunTest("test")
  1. IRIS从XML文件加载测试类,编译类,执行测试,从服务器删除测试代码,并向终端发送报告。
HC-APP>do ##class(%UnitTest.Manager).RunTest("test")
 
===============================================================================
Directory: D:\Temp\test\
===============================================================================
  test begins ...
Load of directory started on 05/14/2021 14:07:17 '*.xml;*.XML;*.cls;*.mac;*.int;*.inc;*.CLS;*.MAC;*.INT;*.INC'
 
Loading file D:\Temp\test\Tests.xml as xml
Imported class: MyPackage.Tests
 
Compilation started on 05/14/2021 14:07:17 with qualifiers '', using up to 4 worker jobs
Compiling class MyPackage.Tests
Compiling routine MyPackage.Tests.1
Compilation finished successfully in 0.019s.
 
Load finished successfully.
 
    MyPackage.Tests begins ...
      TestAdd() begins ...
        AssertEquals:Test Add(2,2)=4 (passed)
        AssertNotEquals:Test Add(2,2)'=5 (passed)
        LogMessage:Duration of execution: .000061 sec.
      TestAdd passed
    MyPackage.Tests passed
  test passed
 
Use the following URL to view the result:
http://172.18.18.159:52773/csp/sys/%25UnitTest.Portal.Indices.cls?Index=3&$NAMESPACE=DHC-APP
All PASSED

image

image

最后一行显示了测试报告的URL。

注意:以这种方式运行测试会在它们执行后从InterSystems IRIS中删除它们。如果在执行测试后返回到Atelier查看测试,将看到一个指示,表明Atelier中可见的文件与服务器不同步。可以保存或重新编译该类,以将代码添加回服务器。 如果使用的是.cls文件而不是XML文件,则必须向RunTest提供/loadudl限定符。

USER>do ##class(%UnitTest.Manager).RunTest("mytests","/loadudl")

示例:UnitTest Portal

运行单元测试将生成测试报告。InterSystems IRIS提供了一个用于查看报告的UnitTest门户。报告按命名空间组织。

可以使用系统资源管理器System Explorer > Tools > UnitTest Portal导航到UnitTest门户。如有必要,请切换到用户命名空间。

image

示例:在单元测试门户中查看报告

门户将测试结果组织成一系列报告。每个测试报告将测试结果组织到一系列超链接页面中。按照链接查找越来越具体的信息。

第一页提供了所有测试套件的摘要。在这种情况下,所有测试套件都通过了。

image

单击要查看的报告的ID列中的ID号。

第二个页面显示每个测试套件的结果。在本例中,mytest是测试套件,并且通过了测试。

image

单击 mytests.

第三个页面显示每个测试用例的结果。在本例中,通过了单个测试用例MyPackage.Tests

image

单击 MyPackage.Tests

第四页显示了通过测试方法得出的结果。这里通过了单个测试方法TestAdd

image

单击 TestAdd.

最后一页显示测试方法中使用的每个AssertX宏的结果。在本例中,AssertEqualsAssertNotEquals都通过了。

image

设置和拆卸

%UnitTest.TestCase类提供的方法可用于在一个测试或一组测试执行之前设置测试环境,然后在测试完成后拆除该环境。以下是对这些方法的说明:

方法描述
OnBeforeAllTests在测试类中的任何测试方法执行之前执行一次。可以设置测试环境。
OnAfterAllTests在测试类中的所有测试方法执行后执行一次。可以破坏测试环境。
OnBeforeOneTest在测试类中的每个测试方法执行之前立即执行。
OnAfterOneTest在文本类中的每个测试方法执行后立即执行。

示例:向测试类添加Setup和Tear Down方法

在本例中,将添加一个名为TestEditContact的测试方法。此方法验证MyPackage.Contact类的ContactType属性是否限制为“Personal”“Business”。添加了一个OnBeforeAllTests方法,该方法在测试执行之前准备数据库。还可以添加一个OnAfterAllTests方法,该方法在测试执行后还原数据库状态。

  1. Studio中打开MyPackage.Tests(可能需要从^UnitTestRoot目录导入它)。
  2. 添加OnBeforeAllTestsOnAfterAllTests方法。
Method OnBeforeAllTests() As %Status
{
   Do ##class(MyPackage.Contact).Populate(1)
   Return $$$OK
}
Method OnAfterAllTests() As %Status
{
   Do ##class(MyPackage.Contact).%KillExtent()
   Return $$$OK
}

OnBeforeAllTests方法使用单个Contact实例填充数据库。OnAfterAllTests方法从数据库中删除所有Contact实例。 3. 现在将TestEditContact测试方法添加到MyPackage.Tests

Method TestEditContact()
{
   set contact=##class(MyPackage.Contact).%OpenId(1)
   set contact.Name="Rockwell,Norman"
   set contact.ContactType="Friend"
   Do $$$AssertStatusNotOK(contact.%Save(),"ContactType = Friend")
   Set contact.ContactType="Personal"
   Do $$$AssertStatusOK(contact.%Save(),"ContactType = Personal")
}

该方法在两种情况下测试执行%Save on Contact返回的状态值:为ContactType分配无效值之后和为ContactType分配有效值之后。 4. 将测试导出到c:\unittest\mytest,覆盖现有的Tests.xml

源码

0
0 136
文章 姚 鑫 · 五月 16, 2021 6m read

第二章 使用%UnitTest进行单元测试

本教程的第二部分介绍了如何使用%UnitTest包对InterSystems IRIS代码进行单元测试。完成本教程的这一部分后,将能够:

  • 解释%UnitTest包中三个主要类的角色。
  • 列出基于%UnitTest包的单元测试类和方法的要求。
  • 创建并执行方法的单元测试。
  • 浏览%UnitTest.Manager创建的测试报告。
  • 执行单元测试时,使用%UnitTest.TestCase方法初始化和还原数据库数据。

什么是%UnitTest?

%UnitTest包是一组为IRIS提供测试框架的类。在结构上,它类似于xUnit测试框架。%UnitTest为创建和执行以下各项的单元测试提供类和工具:

  • 类和方法
  • ObjectScript例程(routines)
  • InterSystems SQL脚本
  • Productions

创建和执行单元测试套件

以下是创建和执行一套单元测试的基本步骤:

  1. 创建一个(或多个)包含要测试的方法的类。
  2. 创建扩展%UnitTest.TestCase的测试类(或多个测试类)。
  3. 将方法添加到将测试方法输出的测试类。在每个方法中至少使用一个断言(AssertX宏)。每个测试方法名称都以Test开头。
  4. 将测试类导出到文件。
  5. 打开终端并切换到包含要测试的类的名称空间。为^UnitTestRoot分配一个字符串,该字符串包含包含导出的测试类文件的目录的父目录的路径。
  6. 在终端中,运行%UnitTest.Manager.RunTest,向其传递包含测试类文件的(子)目录的名称。
  7. 查看测试报告。终端中的输出包括网页的URL,该网页以易于阅读的表格形式显示结果。

%UnitTest类

此表描述了用于为InterSystems IRIS类和方法创建和执行单元测试的主要%UnitTest类。

  • TestCase 扩展此类以创建包含测试方法的类。如果一个或多个AssertX方法返回False,则测试失败;否则测试通过。将使用关联的宏调用AssertX方法。这些方法和宏是:
    • AssertEqualsViaMacro-如果表达式相等,则返回TRUE。使用$$$AssertEquals宏调用。
    • AssertNotEqualsViaMacro-如果表达式不相等,则返回TRUE。使用$$$AssertNotEquals宏调用。
    • AssertStatusOKViaMacro-如果返回的状态代码为1,则返回TRUE。使用$$$AssertStatusOK宏调用。
    • AssertStatusNotOKViaMacro-如果返回的状态码为0,则返回TRUE。使用$$$AssertStatusNotOK宏调用。
    • AssertTrueViaMacro-如果表达式为TRUE,则返回TRUE。使用$$$AssertTrue宏调用。
    • AssertNotTrueViaMacro-如果表达式不为TRUE,则返回TRUE。使用$$$AssertNotTrue宏调用。
    • AssertFilesSameViaMacro-如果两个文件相同,则返回TRUE。使用$$$AssertFilesSame宏调用。
    • LogMessage-将日志消息写入^UnitTestLog全局。使用$$$LogMessage宏调用。
    • 设置和拆除条件的方法包括:
    • OnBeforeOneTest-紧接在测试类中的每个测试方法之前执行。
    • OnBeforeAllTests-在测试类中的任何测试方法之前执行一次。
    • OnAfterOneTest-在测试类中的每个测试方法之后立即执行。
    • OnAfterAllTests-在测试类中的所有测试方法执行完毕后执行一次。
  • Manager 使用此类启动测试。其方法包括:
    • RunTest -在目录中执行一个测试或一组测试。
    • DebugRunTestCase-执行一个测试或一组测试,而不加载或删除任何测试类。
  • Report 定义报告执行一个测试或一组测试的结果的网页。

断言方法和宏

单元测试的主要测试操作来自AssertX方法及其关联宏。将直接调用宏来测试方法的输出。宏测试方法是否为给定的输入创建所需的输出。只要AssertX宏返回FALSE(或以错误结束),包含它的测试就会失败。

在创建代码时,请计划将创建的单元测试以测试代码。在这里的示例中,已经创建了一个名为TestMe的类,其中包含一个名为Add的方法。现在想测试一下新的TestMe类,看看它是否工作。

以下命令运行AssertEquals宏以测试Add方法的输入(2,2)是否等于4

 Do $$$AssertEquals(##class(MyPackage.TestMe).Add(2,2),4, "Test Add(2,2)=4")

AssertEquals宏比较两个值并接受三个参数:

  1. ##class(MyPackage.TestMe).Add(2,2)-第一个值是以2,2作为输入进行测试的方法。
  2. 4-第二个值。
  3. "Test Add(2,2)=4"-写在结果页上的文本说明。(此参数不影响测试。如果不包含测试描述,该类将使用求值的表达式创建一个测试描述。)

以下是用于测试对象是否正确保存的AssertStatusOK宏的示例。

 Do $$$AssertStatusOK(contact.%Save(),"Saving a Contact")

AssertStatusOk宏计算方法返回的状态。如果为1,则测试通过。

  1. Contact.%Save-返回状态代码的表达式。
  2. "Saving a Contact" -文本说明。这是测试报告的文档。这不会影响测试。

创建要在示例中使用的类

要完成以下动手示例,请使用Atelier创建以下类:MyPackage.TestMeMyPackage.Contact

  • MyPackage.TestMe
Class MyPackage.TestMe Extends %RegisteredObject
{

ClassMethod Add(arg1 As %Integer, arg2 As %Integer) As %Integer
{

    Return arg1 + arg2
}

ClassMethod CreateContact(name As %String, type As %String) As MyPackage.Contact
{

    Set contact = ##class(MyPackage.Contact).%New()
    Set contact.Name=name
    Set contact.ContactType=type
    Return contact
}

ClassMethod GetContactsByType(type As %String) As %ListOfObjects
{

    Set list=##class(%Library.ResultSet).%New()
}

}

  • MyPackage.Contact
Class MyPackage.Contact Extends (%Persistent, %Populate, %XML.Adaptor)
{

/// 描述联系的性质:: Personal or Business
Property ContactType As %String(TRUNCATE = 1, VALUELIST = ",Business,Personal");

/// 表示联系人的姓名
Property Name As %String(POPSPEC = "Name()", TRUNCATE = 1) [ Required ];

Query ByContactType(type As %String) As %SQLQuery(CONTAINID = 1)
{
    SELECT %ID FROM Contact
    WHERE (ContactType = :type)
    ORDER BY Name
}

Storage Default
{
<Data name="ContactDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>ContactType</Value>
</Value>
<Value name="3">
<Value>Name</Value>
</Value>
</Data>
<DataLocation>^MyPackage.ContactD</DataLocation>
<DefaultData>ContactDefaultData</DefaultData>
<IdLocation>^MyPackage.ContactD</IdLocation>
<IndexLocation>^MyPackage.ContactI</IndexLocation>
<StreamLocation>^MyPackage.ContactS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}

示例:创建并导出测试类

MyPackage.TestMe包含一个名为Add的方法,该方法将两个整数相加。在此示例中,将创建并运行单元测试以检查Add方法是否正确地将两个整数相加。

创建将包含单元测试的测试类。以下是方法:

  1. 使用Atelier在MyPackage包中创建名为Tests的新类。测试必须扩展%UnitTest.TestCase
  2. 添加以下名为TestAdd并编译测试的方法:
Class MyPackage.Tests Extends %UnitTest.TestCase
{

Method TestAdd()
{
 do $$$AssertEquals(##class(MyPackage.TestMe).Add(2,2),4, "Test Add(2,2)=4")
 do $$$AssertNotEquals(##class(MyPackage.TestMe).Add(2,2),5,"Test Add(2,2)'=5")
}

}

  1. 将类测试导出到单元测试目录中的XML文件。如果尚未创建测试目录,请创建一个。此示例使用 `C:\unittests\mytests\。

a. 在Atelier中,单击文件>导出。

b. 在“Atelier ”下,单击“旧版XML文件”。单击下一步

c. 选择项目Test.clsc:\unittests\mytests\ 目录。

d. 单击Finish(完成)。

e. Atelier将测试类导出到C:\unittests\mytests\cls\MyPackage

注意,目录名(在本例中为mytest)是一套测试的名称,也是^UnitTestRoot指定的目录的子级。运行Manager.RunTest(“mytest”)运行存储在mytest目录中的所有测试。

注意:还可以将测试类导出为.cls文件,而不是XML文件。也可以简单地从Atelier工作区复制它们,而不是导出它们。

源码

0
0 150
文章 姚 鑫 · 五月 15, 2021 4m read

第一章 单元测试概述

本教程的第一部分概述了单元测试。完成本教程的这一部分后,将能够:

  • 定义单元测试并区分单元测试和集成测试
  • 列出单元测试的几个好处
  • 描述InterSystems IRIS %UnitTest包和xUnit测试框架之间的相似性。
  • 列出软件开发中测试优先方法经常声称的几个好处。

什么是单元测试?

单元测试是对单个代码模块的正确性的测试,例如,方法或类的测试。通常,开发人员在开发代码时为其代码创建单元测试。典型的单元测试是一种执行方法的方法,该方法测试并验证该方法是否为给定的一组输入生成了正确的输出。

单元测试不同于集成测试。集成测试验证了一组代码模块交互的正确性。单元测试仅单独验证代码模块的正确性。一组代码模块的集成测试可能会失败,即使每个模块都通过了单元测试。

为什么要进行单元测试?

单元测试提供了许多好处,包括:

  • 提供代码模块是否正确的验证。这是单元测试的主要原因。
  • 提供自动回归测试。更改代码模块后,应重新运行单元测试,以确保代码模块仍然正确。也就是说,应该使用单元测试来确保更改没有破坏代码模块。理想情况下,所有代码模块的单元测试都应该在更改任何一个模块之后运行。
  • 提供文档。通常,代码模块的单元测试与代码模块一起交付。检查单元测试提供了大量有关代码模块如何工作的信息。

XUnit测试框架

单元测试框架是为开发和执行单元测试提供支持的类包。它们可以很容易地扩展以支持更具体或专门化类型的单元测试。

XUnit系列测试框架基于原始的Sunit框架(用于单元测试SmallTalk代码),包括以下框架:

  • JUnit-Java代码的单元测试框架。
  • NUnit-C#VB.NET和其他.NET语言代码的单元测试框架。
  • CppUnit-C++代码的单元测试框架。
  • PyUnit-Python代码的单元测试框架。

%UnitTest和xUnit框架的结构

%UnitTest包和xUnit框架共享相同的基本结构。熟悉任何Unit框架的开发人员都可以毫不费力地学习使用%UnitTest包。%UnitTestxUnit框架都围绕以下基本测试结构组织:

  • 测试装置-为一个测试或一组测试做准备和清理工作的代码。准备测试可能包括创建数据库连接,或使用测试数据初始化数据库。清理可能包括关闭数据库连接或恢复数据库状态。
  • 测试用例-测试的最小单元。验证特定的一组输入是否会产生给定模块的特定输出。
  • 测试套件-设计为一起执行的测试和测试套件的集合。
  • Test Runner-用于执行测试并显示其结果的实用程序。

测试自动化

%UnitTest包和xUnit框架都支持测试自动化。当单元测试完成执行时,它会报告测试是通过还是失败。不需要解释测试结果。这是非常重要的。可以为每个代码更改执行大量单元测试。如果必须不断地阅读和解释结果,这个过程很快就会变得非常乏味和容易出错。

许多xUnit框架提供了汇总测试结果的图形用户界面(GUI)。%UnitTest会生成一个显示测试结果的网页。它以绿色显示有关通过的测试的信息,以红色显示有关失败的测试的信息。开发人员可以一目了然地判断是否有任何测试失败。

这是由%UnitTest单元测试生成的测试报告。用户可以通过单击页面上的超链接深入查看提供有关测试的更多详细信息的页面。

image

测试优先方法论

敏捷软件方法论,例如测试驱动开发(TDD)和极限编程,特别强调单元测试。事实上,这些方法使用单元测试来驱动开发过程。他们提倡“测试优先”的软件开发方法。在这种方法中,开发人员在编写代码模块的一行代码之前设计并编写代码模块的单元测试。然后,开发人员创建代码模块,目标是通过单元测试。

Test First方法的倡导者声称该方法具有以下好处:

  • 它迫使开发人员在开发任何模块之前很久就决定代码模块的正确输入和输出。
  • 它集中了开发人员在创建代码模块时的注意力。开发人员关注的是在创建模块时通过单元测试的具体目标。
  • 它可以防止单元测试成为事后的想法。如果首先创建单元测试,则在项目结束之前不能忽略单元测试。
  • 它确保了代码的高度测试覆盖率。

注意:测试优先开发的支持者通常主张在代码模块之前执行单元测试,而不仅仅是创建单元测试。当然,在这一点上测试应该会失败。他们甚至可能不会编译。

Red – Green – Refactor

XUnit%UnitTest测试报告GUI报告以绿色表示通过测试,以红色表示未通过测试。下面是使用测试优先开发方法的开发节奏:

  1. 红色 - 编写一个不起作用的小测试,也许一开始不会编译。
  2. 绿色 - 让测试快速运行,在测试过程中犯下所有必要的错误。
  3. 重构 - 消除仅在使测试正常工作时产生的所有重复。

Kent Beck,《测试驱动的设计》

0
0 107
文章 姚 鑫 · 五月 14, 2021 2m read

Caché Global

第一章 简介global☆☆☆☆☆

第二章 全局变量结构(一)☆☆☆☆☆

第二章 全局变量结构(二)☆☆☆☆☆

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

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

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

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

第四章 多维存储的SQL和对象使用(一)☆☆☆☆☆

第四章 多维存储的SQL和对象使用(二)☆☆☆☆☆

第五章 管理全局变量(一)☆☆☆☆☆

第五章 管理全局变量(二)☆☆☆☆☆

第六章 临时全局变量和IRISTEMP数据库☆☆☆☆☆

前言

经过快一个月的连载 《Caché Global》 共12篇。对于刚接触M的语言的同学,由浅入深帮助你快速进步,对于老手,丰富更多的细节

涵盖以下主题:

  • “简介”概述了全局变量的功能和用途。
  • “全局变量结构”描述了全局变量是如何存储在磁盘上的,它们是如何命名和引用的,以及它们的结构。
  • “使用多维存储(全局)”介绍如何以编程方式使用全局变量。
  • “多维存储的SQL和对象使用”描述了对象和SQL引擎如何使用全局变量存储数据。
  • “管理全局变量”介绍了主要从管理门户管理全局的工具。
  • “临时全局变量和TEMP数据库”描述了如何使用临时全局变量来帮助进行复杂的处理。

预告

下一期系列将用一个月的时间连载,《IRIS 单元测试》《Caché 网络实用工具》,敬请期待。

交流群

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f9VqwzNP-1608850948003)(3E1D939266954ED48BDAEA9B8086B11E)]

0
0 292
文章 姚 鑫 · 五月 14, 2021 3m read

第六章 临时全局变量和IRISTEMP数据库

对于某些操作,可能需要全局变量的功能,而不需要无限期保存数据。例如,可能希望使用全局对某些不需要存储到磁盘的数据进行排序。对于这些操作,InterSystems IRIS提供了临时全局机制。该机制的工作方式如下:

  • 对于应用程序名称空间,可以定义一个全局映射,以便将具有特定命名约定的全局变量映射到IRISTEMP数据库,该数据库是一个特殊的数据库,如下所述。

例如,可以定义一个全局映射,以便将名称为^AcmeTemp*的所有全局变量映射到IRISTEMP数据库。

  • 当代码需要临时存储数据并再次读取它时,代码将向使用该命名约定的全局变量写入数据,并从全局变量读取数据。

例如,要保存值,代码可能会执行以下操作:

 set ^AcmeTempOrderApp("sortedarray")=some value

然后,稍后代码可能会执行以下操作:

 set somevariable = ^AcmeTempOrderApp("sortedarray")

通过使用临时全局变量,可以利用IRISTEMP数据库没有日志记录这一事实。因为数据库没有日记记录,所以使用该数据库的操作不会产生日记文件。日志文件可能会变得很大,并可能导致空间问题。但是,请注意以下几点:

不能回滚修改IRISTEMP数据库中的全局变量的任何事务;此行为特定于IRISTEMP。如果需要通过事务管理临时工作,请不要使用IRISTEMP中的全局变量来实现此目的。

请注意,仅对不需要保存的工作使用IRISTEMP

定义临时全局变量的映射

要定义临时全局变量的映射,请执行以下操作:

  1. 选择一个命名约定,并确保所有开发人员都知道这一点。请注意以下几点:
  • 考虑是要有多个临时全局变量还是要少一些具有多个节点的临时全局变量。与读取或写入相同数量的独立全局变量相比,InterSystems IRIS更容易高效地读取或写入同一全局变量中的不同节点。这种效率差异对于少数全局变量来说可以忽略不计,但当有数百个独立的全局变量时,效率差异就非常明显。

  • 如果计划在多个名称空间中使用相同的全局映射,那么设计一个系统,使一个名称空间中的工作不会干扰另一个名称空间中的工作。例如,可以使用命名空间名称作为全局变量中的下标。

  • 类似地,即使在一个命名空间内,也要设计一个系统,使代码的每个部分在同一全局中使用不同的全局或不同的下标,以避免干扰。

  • 请勿使用系统保留的全局名称。

  1. 在管理门户中,导航到命名空间页面(System Administration > Configuration > System Configuration > Namespaces)。
  2. 在应用程序命名空间所在的行中,单击Global Mappings。
  3. 在全局映射页面中,单击新建全局映射。
  4. 对于全局数据库位置,选择IRISTEMP
  5. 对于全局名称,输入以星号(*)结尾的名称。不要包括名称的第一个插入符号。

例如: AcmeTemp*

此映射会导致名称以AcmeTemp*开头的所有全局变量映射到IRISTEMP数据库。

  1. 单击OK。

注意:>> 显示在新映射行的第一列中,表示已打开映射进行编辑。

  1. 要保存映射以便InterSystems IRIS使用它们,请单击保存更改。

image

image

image

image

IRISTEMP的系统使用

请注意,InterSystems使用临时全局变量作为临时空间,例如,在执行某些查询(用于排序、分组、计算聚合等)期间用作临时索引。

IRISTEMP中包含的一些系统全局变量包括:

  • ^IRIS.Temp*
  • ^mtemp*

永远不要更改这些全局变量中的任何一个。

0
0 141
文章 姚 鑫 · 五月 13, 2021 4m read

第五章 管理全局变量(二)

在全局变量中查找值

“查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。

要访问和使用此页,请执行以下操作:

  1. 显示“全局变量”页。
  2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。
  3. 单击查找按钮。
  4. 对于查找内容,输入要搜索的字符串。
  5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。
  6. 单击Find First或Find All。

然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。

  1. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。
  2. 完成后,单击关闭窗口。

执行批量更换

注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。

出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项:

  1. 显示“全局”页面。
  2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
  3. 单击替换按钮。
  4. 使用此页面查找上一节中描述的值。
  5. 为“替换为”指定一个值。
  6. 单击全部替换。
  7. 单击确定确认此操作。然后,页面会显示变更的预览。
  8. 如果结果可以接受,请单击保存。
  9. 单击确定确认此操作。

导出全局变量

注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统;

“导出全局”页面允许导出全局。

要访问和使用此页面:

  1. 显示“全局”页面。
  2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
  3. 单击导出按钮。
  4. 指定要将全局文件导出到的文件。为此,请在输入服务器<主机名>上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
  5. 使用字符集列表选择导出文件的字符集。
  6. 在页面的中央框中:选择输出格式,选择记录格式
  7. 选择或清除“在此检查”以在后台运行导出...
  8. 单击导出。
  9. 如果文件已经存在,请单击“确定”用新版本覆盖它。

导出会创建一个. gof文件。

导入全局变量

注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。

“导入全局”页面允许导入全局。要访问和使用此页面:

  1. 显示“全局”页面。
  2. 单击导入按钮。
  3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
  4. 使用字符集列表选择导入文件的字符集。
  5. 选择下一步。
  6. 使用表中的复选框选择要导入的全局。
  7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。
  8. 单击导入。

删除全局变量

注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。

“删除全局”页面允许删除全局。要访问和使用此页面:

  1. 显示“全局”页面。
  2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
  3. 单击删除按钮。
  4. 单击确定确认此操作。

管理任务的应用程序接口

InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务:

  • %SYSTEM.OBJ提供了以下方法:
    • Export()使能够将全局导出到一个XML文件。
    • Load()LoadDir()使能够导入包含在XML文件中的全局。

这两者都可以通过$SYSTEM变量获得,例如:$SYSTEM.OBJ.Export

  • 类别%Library.Global提供了以下方法:
    • Export()使能够将全局导出到.gof和其他文件格式(不包括XML)。
    • Import()使能够将全局导入到.gof和其他文件格式(不包括XML)。

%Library.Global 还提供了Get()类查询,根据给定的搜索条件,可以使用该查询来查找全局。

image

0
0 108
文章 姚 鑫 · 五月 13, 2021 4m read

第五章 管理全局变量(二)

在全局变量中查找值

“查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。

要访问和使用此页,请执行以下操作:

  1. 显示“全局变量”页。
  2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。
  3. 单击查找按钮。
  4. 对于查找内容,输入要搜索的字符串。
  5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。
  6. 单击Find First或Find All。

然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。

  1. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。
  2. 完成后,单击关闭窗口。

执行批量更换

注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。

出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项:

  1. 显示“全局”页面。
  2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
  3. 单击替换按钮。
  4. 使用此页面查找上一节中描述的值。
  5. 为“替换为”指定一个值。
  6. 单击全部替换。
  7. 单击确定确认此操作。然后,页面会显示变更的预览。
  8. 如果结果可以接受,请单击保存。
  9. 单击确定确认此操作。

导出全局变量

注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统;

“导出全局”页面允许导出全局。

要访问和使用此页面:

  1. 显示“全局”页面。
  2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
  3. 单击导出按钮。
  4. 指定要将全局文件导出到的文件。为此,请在输入服务器<主机名>上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
  5. 使用字符集列表选择导出文件的字符集。
  6. 在页面的中央框中:选择输出格式,选择记录格式
  7. 选择或清除“在此检查”以在后台运行导出...
  8. 单击导出。
  9. 如果文件已经存在,请单击“确定”用新版本覆盖它。

导出会创建一个. gof文件。

导入全局变量

注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。

“导入全局”页面允许导入全局。要访问和使用此页面:

  1. 显示“全局”页面。
  2. 单击导入按钮。
  3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。
  4. 使用字符集列表选择导入文件的字符集。
  5. 选择下一步。
  6. 使用表中的复选框选择要导入的全局。
  7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。
  8. 单击导入。

删除全局变量

注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。

“删除全局”页面允许删除全局。要访问和使用此页面:

  1. 显示“全局”页面。
  2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3
  3. 单击删除按钮。
  4. 单击确定确认此操作。

管理任务的应用程序接口

InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务:

  • %SYSTEM.OBJ提供了以下方法:
    • Export()使能够将全局导出到一个XML文件。
    • Load()LoadDir()使能够导入包含在XML文件中的全局。

这两者都可以通过$SYSTEM变量获得,例如:$SYSTEM.OBJ.Export

  • 类别%Library.Global提供了以下方法:
    • Export()使能够将全局导出到.gof和其他文件格式(不包括XML)。
    • Import()使能够将全局导入到.gof和其他文件格式(不包括XML)。

%Library.Global 还提供了Get()类查询,根据给定的搜索条件,可以使用该查询来查找全局。

image

0
0 99
文章 Michael Lei · 五月 12, 2021 28m read

关键字:IRIS, IntegratedML, 机器学习, Covid-19, Kaggle 

目的

最近,我注意到一个用于预测 Covid-19 患者是否将转入 ICU 的 Kaggle 数据集。 它是一个包含 1925 条病患记录的电子表格,其中有 231 列生命体征和观察结果,最后一列“ICU”为 1(表示是)或 0(表示否)。 任务是根据已知数据预测患者是否将转入 ICU。

这个数据集看起来是所谓的“传统 ML”任务的一个好例子。数据看上去数量合适,质量也相对合适。它可能更适合在 IntegratedML 演示套件上直接应用,那么,基于普通 ML 管道与可能的 IntegratedML 方法进行快速测试,最简单的方法是什么?

0
0 132
文章 姚 鑫 · 五月 10, 2021 5m read

第四章 多维存储的SQL和对象使用(二)

索引

持久化类可以定义一个或多个索引;其他数据结构用于提高操作(如排序或条件搜索)的效率。InterSystems SQL在执行查询时使用这些索引。InterSystems IRIS对象和SQL在执行INSERTUPDATEDELETE操作时自动维护索引内的正确值。

标准索引的存储结构

标准索引将一个或多个属性值的有序集与包含属性的对象的对象ID值相关联。

例如,假设我们定义了一个简单的持久化MyApp.Person类,该类具有两个文本属性和一个关于其Name属性的索引:

Class MyApp.Person Extends %Persistent
{
Index NameIdx On Name;

Property Name As %String;
Property Age As %Integer;
}

如果我们创建并保存此Person类的多个实例,则生成的数据和索引全局变量类似于:

 // data global
 ^MyApp.PersonD = 3  // counter node
 ^MyApp.PersonD(1) = $LB("",34,"Jones")
 ^MyApp.PersonD(2) = $LB("",22,"Smith")
 ^MyApp.PersonD(3) = $LB("",45,"Jones")


 // index global
 ^MyApp.PersonI("NameIdx"," JONES",1) = ""
 ^MyApp.PersonI("NameIdx"," JONES",3) = ""
 ^MyApp.PersonI("NameIdx"," SMITH",2) = ""

请注意有关全局索引的以下事项:

  1. 默认情况下,它被放在一个全局变量中,全局变量的名称是后面附加“i”(表示索引)的类名。
  2. 默认情况下,第一个下标是索引名;这允许将多个索引存储在同一全局中,而不会发生冲突。
  3. 第二个下标包含整理后的数据值。在这种情况下,使用默认的SQLUPPER排序函数对数据进行排序。这会将所有字符转换为大写(不考虑大小写进行排序),并在前面加上一个空格字符(强制所有数据作为字符串进行排序)。
  4. 第三个下标包含包含索引数据值的对象的对象ID值。
  5. 节点本身是空的;所有需要的数据都保存在下标中。请注意,如果索引定义指定数据应与索引一起存储,则将其放置在全局索引的节点中。

该索引包含足够的信息来满足许多查询,比如按姓名列出所有Person类。

位图索引

位图索引类似于标准索引,不同之处在于它使用一系列位字符串来存储与索引值对应的一组对象ID值。

位图索引的逻辑运算

位字符串是一个包含一组特殊压缩格式的位(01值)的字符串。 InterSystems IRIS包含一组有效创建和使用位字符串的函数。 这些都列在下表中:

位操作

函数描述
$Bit在位串中设置或获取位。
$BitCount计算位串中的位数。
$BitFind查找位串中下一个出现的位。
$BitLogic对两个或多个位串执行逻辑(AND, OR)操作。

在位图索引中,位字符串中的顺序位置对应于索引表中的行(对象ID号)。 对于给定值,位图索引维护一个位字符串,在给定值存在的每一行中包含1,在没有给定值的每一行中包含0。 请注意,位图索引只适用于使用系统分配的默认存储结构的对象,数值型对象ID值。

例如,假设我们有一个类似如下的表:

IDStateProduct
1MAHat
2NYHat
3NYChair
4MAChair
5MAHat

如果StateProduct列有位图索引,则它们包含以下值:

State列上的位图索引包含以下位字符串值:


MA	1	0	0	1	1
NY	0	1	1	0	0

注意,对于值“MA”,在与State等于“MA”的表行对应的位置(1、4和5)中有一个1。

类似地,Product列上的位图索引包含以下位字符串值(注意,这些值在索引中被排序为大写):


CHAIR	0	0	1	1	0
HAT	1	1	0	0	1

InterSystems SQL Engine可以通过对这些索引维护的位串进行迭代、计算位内位数或执行逻辑组合(AND, or)来执行许多操作。 例如,要找到State等于“MA”Product等于“HAT”的所有行,SQL引擎可以简单地将适当的位串与逻辑and组合在一起。

除了这些索引之外,系统还维护一个额外的索引,称为“区段索引”,对于存在的每一行包含1,对于不存在的行(如已删除的行)包含0。 这用于某些操作,如否定。

位图索引的存储结构

位图索引将一个或多个属性值的有序集合与一个或多个包含与属性值对应的对象ID值的位字符串相关联。

例如,假设我们定义了一个简单的持久MyAppPerson类具有两个文字属性和Age属性上的位图索引:

Class MyApp.Person Extends %Persistent
{
Index AgeIdx On Age [Type = bitmap];

Property Name As %String;
Property Age As %Integer;
}

如果我们创建并保存这个Person类的几个实例,得到的数据和索引全局变量类似于:

 // data global
 ^MyApp.PersonD = 3  // counter node
 ^MyApp.PersonD(1) = $LB("",34,"Jones")
 ^MyApp.PersonD(2) = $LB("",34,"Smith")
 ^MyApp.PersonD(3) = $LB("",45,"Jones")

 // index global
 ^MyApp.PersonI("AgeIdx",34,1) = 110...
 ^MyApp.PersonI("AgeIdx",45,1) = 001...

 // extent index global
 ^MyApp.PersonI("$Person",1) = 111...
 ^MyApp.PersonI("$Person",2) = 111...

关于全局索引,请注意以下几点:

  1. 默认情况下,它被放置在一个全局变量中,全局变量的名称是类名,后面附加一个“I”(表示Index)。
  2. 默认情况下,第一个下标是索引名;这允许多个索引存储在同一个全局中,而不会发生冲突。
  3. 第二个下标包含经过整理的数据值。在这种情况下,不应用排序函数,因为这是数字数据的索引。
  4. 第三个下标包含块编号;为了提高效率,位图索引被分成一系列位串,每个位串包含表中大约64000行的信息。这些位串中的每一个都被称为块。
  5. 节点包含位串。

另请注意:因为该表有一个位图索引,所以会自动维护一个区索引。该盘区索引存储在索引GLOBAL中,并使用前缀有“$”字符的类名作为其第一个下标。

位图索引的直接访问

下面的示例使用类区索引来计算存储的对象实例(行)的总数。注意,它使用$ORDER来迭代区索引的块(每个块包含大约64000行的信息):

ClassMethod Count1() As %Integer
{
    New total,chunk,data
    Set total = 0
    
    Set chunk = $Order(^Sample.PersonI("$Person",""),1,data)
    While (chunk '= "") {
        Set total = total + $bitcount(data,1)
        Set chunk = $Order(^Sample.PersonI("$Person",chunk),1,data)
    }

    Quit total
}
DHC-APP>w ##class(PHA.TEST.SQL).Count1()
208
0
0 120
文章 姚 鑫 · 五月 9, 2021 6m read

第四章 多维存储的SQL和对象使用(一)

本章介绍InterSystems IRIS®对象和SQL引擎如何利用多维存储(全局变量)来存储持久对象、关系表和索引。

尽管InterSystems IRIS对象和SQL引擎会自动提供和管理数据存储结构,但了解其工作原理的详细信息还是很有用的。

数据的对象视图和关系视图使用的存储结构是相同的。为简单起见,本章仅从对象角度介绍存储。

数据

每个使用%Storage.Persistent存储类(默认)的持久化类都可以使用多维存储(全局变量)的一个或多个节点在InterSystems IRIS数据库中存储其自身的实例。

每个持久化类都有一个存储定义,用于定义其属性如何存储在全局变量节点中。这个存储定义(称为“默认结构”)由类编译器自动管理。

默认结构

用于存储持久对象的默认结构非常简单:

  • 数据存储在名称以完整类名(包括包名)开头的全局变量中。附加“D”以形成全局数据的名称,而附加“I”作为全局索引。
  • 每个实例的数据都存储在全局数据的单个节点中,所有非瞬态属性都放在$list结构中。
  • 数据全局变量中的每个节点都以对象ID值作为下标。默认情况下,对象ID值是通过调用存储在全局变量数据根(没有下标)的计数器节点上的$Increment函数提供的整数。

例如,假设我们定义了一个简单的持久化类MyApp.Person,它有两个文本属性:

Class MyApp.Person Extends %Persistent
{
Property Name As %String;
Property Age As %Integer;
}

如果我们创建并保存此类的两个实例,得到的全局变量结果将类似于:

 ^MyApp.PersonD = 2  // counter node
 ^MyApp.PersonD(1) = $LB("",530,"Abraham")
 ^MyApp.PersonD(2) = $LB("",680,"Philip")

注意,存储在每个节点中的$List结构的第一部分是空的; 这是为类名保留的。 如果定义Person类的子类,则此槽包含子类名。 当多个对象存储在同一个区段内时,%OpenId方法(由%Persistent类提供)使用此信息多态地打开正确的对象类型。 此槽在类存储定义中显示为名为“%%CLASSNAME”的属性。

IDKEY

IDKEY机制允许显式定义用作对象ID的值。为此,只需将IDKEY索引定义添加到类中,并指定将提供ID值的一个或多个属性。请注意,一旦保存对象,其对象ID值就不能更改。这意味着在保存使用IDKEY机制的对象后,不能再修改该对象ID所基于的任何特性。

Class MyApp.Person Extends %Persistent
{
Index IDKEY On Name [ Idkey ];

Property Name As %String;
Property Age As %Integer;
}

如果我们创建并保存Person类的两个实例,得到的全局变量结果现在类似于:

 ^MyApp.PersonD("Abraham") = $LB("",530,"Abraham")
 ^MyApp.PersonD("Philip") = $LB("",680,"Philip")

请注意,不再定义任何计数器节点。还要注意,通过将对象ID基于Name属性,我们已经暗示了Name的值对于每个对象必须是唯一的。

如果IDKEY索引基于多个属性,则主数据节点具有多个下标。例如:

Class MyApp.Person Extends %Persistent
{
Index IDKEY On (Name,Age) [ Idkey ];

Property Name As %String;
Property Age As %Integer;
}

在这种情况下,生成的全局变量现在类似于:

 ^MyApp.PersonD("Abraham",530) = $LB("",530,"Abraham")
 ^MyApp.PersonD("Philip",680) = $LB("",680,"Philip")

重要提示:IDKEY索引使用的任何属性的值中都不能有连续的一对竖线(||),除非该属性是对持久类实例的有效引用。 这种限制是由InterSystems SQL机制的工作方式强加的。 在IDKey属性中使用||会导致不可预知的行为。

Subclasses

默认情况下,持久性对象的子类引入的任何字段都存储在附加节点中。 子类的名称用作附加的下标值。

例如,假设我们定义了一个具有两个文本属性的简单持久MyApp.Person类:

Class MyApp.Person Extends %Persistent
{
Property Name As %String;

Property Age As %Integer;
}

现在,我们定义了一个持久子类MyApp.Students,它引入了两个额外的文本属性:

Class MyApp.Student Extends Person
{
Property Major As %String;

Property GPA As %Double;
}

如果我们创建并保存此MyApp.Student类的两个实例,得到的全局结果将类似于:

^MyApp.PersonD = 2  // counter node
^MyApp.PersonD(1) = $LB("Student",19,"Jack")
^MyApp.PersonD(1,"Student") = $LB(3.2,"Physics")

^MyApp.PersonD(2) = $LB("Student",20,"Jill")
^MyApp.PersonD(2,"Student") = $LB(3.8,"Chemistry")

Person类继承的属性存储在主节点中,而由Student类引入的属性存储在另一个子节点中。这种结构确保了学生数据可以作为人员数据互换使用。例如,列出所有Person对象名称的SQL查询正确地获取PersonStudent数据。当属性被添加到超类或子类时,这种结构还使类编译器更容易维护数据兼容性。

请注意,主节点的第一部分包含字符串“Student”-它标识包含学生数据的节点。

父子关系

在父子关系中,子对象的实例存储为它们所属的父对象的子节点。这种结构确保子实例数据与父数据在物理上是集群的。

/// An Invoice class
Class MyApp.Invoice Extends %Persistent
{
Property CustomerName As %String;

/// an Invoice has CHILDREN that are LineItems
Relationship Items As LineItem  [inverse = TheInvoice, cardinality = CHILDREN];
}

LineItem

/// A LineItem class
Class MyApp.LineItem Extends %Persistent
{
Property Product As %String;
Property Quantity As %Integer;

/// a LineItem has a PARENT that is an Invoice
Relationship TheInvoice As Invoice [inverse = Items, cardinality = PARENT];
}

如果我们存储多个Invoice对象的实例,每个实例都有关联的LineItem对象,则得到的全局变量结果将类似于:

^MyApp.InvoiceD = 2  // invoice counter node
^MyApp.InvoiceD(1) = $LB("","Wiley Coyote")
^MyApp.InvoiceD(1,"Items",1) = $LB("","Rocket Roller Skates",2)
^MyApp.InvoiceD(1,"Items",2) = $LB("","Acme Magnet",1)

^MyApp.InvoiceD(2) = $LB("","Road Runner")
^MyApp.InvoiceD(2,"Items",1) = $LB("","Birdseed",30)

嵌入对象

存储嵌入对象的方法是先将它们转换为序列化状态(默认情况下是包含对象属性的$List结构),然后以与任何其他属性相同的方式存储此串行状态。

例如,假设我们定义了一个具有两个文字属性的简单串行(可嵌入)类:

Class MyApp.MyAddress Extends %SerialObject
{
Property City As %String;
Property State As %String;
}

现在,我们修改前面的示例以添加嵌入的Home Address属性:

Class MyApp.MyClass Extends %Persistent
{
Property Name As %String;
Property Age As %Integer;
Property Home As MyAddress;
}

如果我们创建并保存此类的两个实例,则生成的全局变量相当于:

 ^MyApp.MyClassD = 2  // counter node
 ^MyApp.MyClassD(1) = $LB(530,"Abraham",$LB("UR","Mesopotamia"))
 ^MyApp.MyClassD(2) = $LB(680,"Philip",$LB("Bethsaida","Israel"))

通过将全局流的数据拆分成一系列块(每个块小于32K字节)并将这些块写入一系列顺序节点,全局流被存储在全局流中。文件流存储在外部文件中。

0
0 129
文章 姚 鑫 · 五月 8, 2021 7m read

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

管理事务

InterSystems IRIS提供了使用全局变量实现完整事务处理所需的基本操作。 InterSystems IRIS对象和SQL自动利用这些特性。 如果直接将事务性数据写入全局变量,则可以使用这些操作。

事务命令是TSTART,它定义事务的开始; TCOMMIT,它提交当前事务; 和TROLLBACK,它将中止当前事务,并撤消自事务开始以来对全局变量所做的任何更改。

例如,下面的ObjectScript代码定义了事务的开始,设置了一些全局变量节点,然后根据ok的值提交或回滚事务:


/// w ##class(PHA.TEST.Global).GlobalTro(0)
ClassMethod GlobalTro(ok)
{

	TSTART

	Set ^Data(1) = "Apple1"
	Set ^Data(2) = "Berry1"

	If (ok) {
		TCOMMIT
	}
	Else {
	 	TROLLBACK
	}
	zw ^Data
	q ""
}

TSTART在InterSystems IRIS日志文件中写入事务开始标记。 这定义了事务的起始边界。 在上面的示例中,如果变量oktrue(非零),则TCOMMIT命令标记事务成功结束,并将事务完成标记写入日志文件。 如果okfalse(0),那么TROLLBACK命令将撤消自事务开始以来进行的每一个setkill操作。 在这种情况下,^Data(1)^Data(2)被恢复到原来的值。

注意,在事务成功完成时,不会写入任何数据。 这是因为事务期间对数据库的所有修改都是在事务过程中正常执行的。 只有在回滚的情况下,数据库中的数据才会受到影响。 这意味着本例中的事务具有有限的隔离性; 也就是说,其他进程可以在事务提交之前看到修改后的全局值。 这通常被称为未提交的读取。 这是好是坏取决于应用程序的需求; 在许多情况下,这是完全合理的行为。 如果应用程序需要更高级别的隔离,则可以通过使用锁来实现。 这将在下一节中进行描述。

锁和事务

要创建隔离事务-也就是说,为了防止其他进程在提交事务之前看到修改的数据-需要使用锁。在ObjectScript中,可以通过lock命令直接获取和释放锁定。锁按照约定工作;对于给定的数据结构(如用于持久对象),所有需要锁的代码都使用相同的逻辑锁引用(即,锁命令使用相同的地址)。

在事务中,锁有一个特殊的行为; 在事务过程中获取的任何锁在事务结束之前都不会被释放。 要了解为什么会这样,请考虑典型事务执行的操作:

  1. 使用TSTART启动事务。
  2. 获取要修改的一个或多个节点上的锁。这通常被称为“写”锁。
  3. 修改一个或多个节点。
  4. 释放锁(或多个锁)。因为我们处于事务中,所以这些锁在此时实际上不会被释放。
  5. 使用TCOMMIT提交事务。此时,上一步中释放的所有锁实际上都已释放。

如果另一个进程想要查看此事务中涉及的节点,并且不想看到未提交的修改,则它只需在从节点读取数据之前测试锁(称为“读”锁)。因为写锁定一直保持到事务结束,所以在事务完成(提交或回滚)之前,读取进程看不到数据。

大多数数据库管理系统使用类似的机制来提供事务隔离。InterSystems IRIS的独特之处在于它让开发人员可以使用这种机制。这使得有可能为新的应用程序类型创建自定义数据库结构,同时仍然支持事务。当然,可以简单地使用InterSystems IRIS对象或SQL来管理数据,并让事务得到自动管理。

对TSTART的嵌套调用

InterSystems IRIS维护一个特殊的系统变量$TLEVEL,该变量跟踪TSTART命令被调用的次数。$TLEVEL从值0开始;每次调用TSTART时,$TLEVEL的值递增1,而每次调用TCOMMIT时,$TLEVEL的值递减1。如果调用TCOMMIT导致将$TLEVEL设置回0,则事务结束(以COMMIT结束)。

调用TROLLBACK命令总是终止当前事务,并将$TLEVEL设置回0,而不管$TLEVEL的值是多少。

此行为使应用程序能够将事务包装在本身包含事务的代码(如对象方法)周围。例如,持久对象提供的%Save方法始终将其操作作为事务执行。通过显式调用TSTARTTCOMMIT,可以创建包含几个对象保存操作的更大事务:

    TSTART
    Set sc = object1.%Save()
    If ($$$ISOK(sc)) {
        // 第一次保存有效,执行第二次保存
        Set sc = object2.%Save()
    }
    
    If ($$$ISERR(sc)) {
        // 其中一个保存失败,正在回滚
        TROLLBACK
    }
    Else {
        // 提交
        TCOMMIT
    }

管理并发性

设置或检索单个全局变量节点的操作是原子的;它可以保证始终成功并获得一致的结果。对于多个节点上的操作或控制事务隔离,InterSystems IRIS提供获取和释放锁的功能。

锁由IRIS锁管理器管理。在ObjectScript中,可以通过lock命令直接获取和释放锁定。(InterSystems IRIS对象和SQL根据需要自动获取和释放锁)。

检查最新的全局变量引用

最新的全局变量引用记录在ObjectScript $ZREFERENCE特殊变量中。$ZREFERENCE包含最新的全局引用,包括下标和扩展全局引用(如果指定)。请注意,$ZREFERENCE既不指示全局引用是否成功,也不指示指定的全局是否存在。InterSystems IRIS只记录最近指定的全局引用。

裸全球变量引用

在带下标的全局引用之后,InterSystems IRIS会将裸指示符设置为该全局名称和下标级别。然后,可以使用裸全局引用(省略全局名称和更高级别的下标)对相同的全局变量和下标级别进行后续引用。这简化了在相同(或更低)下标级别对相同全局变量的重复引用。

在裸引用中指定较低的下标级别会将裸指示符重置为该下标级别。因此,在使用裸全局变量引用时,始终使用由最新全局引用建立的下标级别。

裸指示符值记录在$ZREFERENCE特殊变量中。裸露指示符被初始化为空字符串。在未设置裸指示器的情况下尝试裸全局引用会导致<NAKED> 错误。更改命名空间会重新初始化裸体指示符。可以通过将$ZREFERENCE设置为空字符串(“”)来重新初始化裸指示符。

在下面的示例中,第一个引用中指定了带下标的GLOBAL ^Produce(“fruit”,1)。InterSystems IRIS将此全局变量名称和下标保存在裸体指示符中,以便后续的裸体全局引用可以省略全局名称“Production”和更高下标级别的“Fruit”。当^(3,1)裸引用达到更低的下标级别时,此新的下标级别将成为任何后续裸全局变引用的假设。

/// w ##class(PHA.TEST.Global).GlobalNake()
ClassMethod GlobalNake()
{
	SET ^Produce("fruit",1)="Apples"  /* 完整的全局变量引用  */
	SET ^(2)="Oranges"                /* 裸全局变量全局引用 */
	SET ^(3)="Pears"                  /* 假设下标级别为2 */
	SET ^(3,1)="Bartlett pears"       /* 转到下标级别3  */
	SET ^(2)="Anjou pears"            /* 假设下标级别为3 */
	WRITE "latest global reference is: ",$ZREFERENCE,!
	ZWRITE ^Produce
	KILL ^Produce
	q ""
}
DHC-APP>w ##class(PHA.TEST.Global).GlobalNake()
latest global reference is: ^Produce("fruit",3,2)
^Produce("fruit",1)="Apples"
^Produce("fruit",2)="Oranges"
^Produce("fruit",3)="Pears"
^Produce("fruit",3,1)="Bartlett pears"
^Produce("fruit",3,2)="Anjou pears"

除了极少数例外,每个全局变量变引用(全引用或裸引用)都会设置裸指示器。$ZREFERENCE特殊变量包含最新全局变引用的完整全局名称和下标,即使这是一个裸全局引用。ZWRITE命令还显示每个全局的完整全局名称和下标,无论它是否使用裸引用设置。

应谨慎使用裸全局变量引用,因为InterSystems IRIS在不总是明显的情况下设置裸指示器,包括以下情况:

  • 完整全局变量引用最初设置裸露指示符,随后的完整全局引用或裸露全局引用会更改裸露指示符,即使全局引用不成功。例如,试图写入不存在的全局变量的值会设置裸指示符。
  • 无论InterSystems IRIS如何计算后置条件,引用下标全局的后置条件命令都会设置裸指示符。
  • 引用下标全局变量的可选函数参数可能设置或不设置裸指示符,具体取决于IRIS是否计算所有参数。例如,$get的第二个参数总是设置裸指示符,即使它包含的默认值没有使用。InterSystems IRIS按从左到右的顺序计算参数,因此最后一个参数可能会重置由第一个参数设置的裸指示符。
  • 回滚事务的TROLLBACK命令不会将裸指示符回滚到事务开始时的值。

如果完整全局变量引用包含扩展全局变量引用,则后续的裸全局变量引用将采用相同的扩展全局引用;不必将扩展引用指定为裸全局引用的一部分。

0
0 107
文章 姚 鑫 · 五月 7, 2021 7m read

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

在全局变量中复制数据

若要将全局变量(全部或部分)的内容复制到另一个全局变量(或局部数组)中,请使用ObjectScript Merge命令。

下面的示例演示如何使用Merge命令将OldData全局变量的全部内容复制到NewData全局变量中:

 Merge ^NewData = ^OldData

如果合并命令的source参数有下标,则复制该节点及其后代中的所有数据。如果Destination参数有下标,则使用目标地址作为顶级节点复制数据。例如,以下代码:

 Merge ^NewData(1,2) = ^OldData(5,6,7)

^OldData(5,6,7)及其下的所有数据复制到^NewData(1,2)

维护全局变量内的共享计数器

大规模事务处理应用程序的一个主要并发瓶颈可能是创建唯一标识符值。例如,考虑一个订单处理应用程序,在该应用程序中,必须为每一张新发票指定一个唯一的标识号。传统的方法是维护某种计数器表。每个创建新发票的进程都会等待获取此计数器上的锁,递增其值,然后将其解锁。这可能会导致对此单个记录的激烈资源争用。

为了解决此问题,InterSystems IRIS提供了ObjectScript $INCREMENT函数。$INCREMENT自动递增全局节点的值(如果该节点没有值,则设置为1)。$INCREMENT的原子性意味着不需要锁;该函数保证返回一个新的增量值,不会受到任何其他进程的干扰。

可以使用$INCREMENT,如下所示。首先,必须决定在其中存放计数器的全局节点。接下来,无论何时需要新的计数器值,只需调用$INCREMENT

 SET counter = $INCREMENT(^MyCounter)

InterSystems IRIS对象和SQL使用的默认存储结构使用$INCREMENT来分配唯一的对象(行)标识符值。

对全局变量中的数据进行排序

存储在全局变量中的数据会根据下标的值自动排序。例如,下面的ObjectScript代码定义了一组全局变量(按随机顺序),然后遍历它们以演示全局节点按下标自动排序:

/// w ##class(PHA.TEST.Global).GlobalSort()
ClassMethod GlobalSort()
{
	Kill ^Data

	Set ^Data("Cambridge") = ""
	Set ^Data("New York") = ""
	Set ^Data("Boston") = ""
	Set ^Data("London") = ""
	Set ^Data("Athens") = ""


	Set key = $Order(^Data(""))
	While (key '= "") {
		Write key,!
		Set key = $Order(^Data(key)) 
	}
	q ""
}
DHC-APP> w ##class(PHA.TEST.Global).GlobalSort()
Athens
Boston
Cambridge
London
New York
 

应用程序可以利用全局函数提供的自动排序来执行排序操作或维护对某些值的有序、交叉引用的索引。 InterSystems SQL和ObjectScript使用全局变量自动执行这些任务。

全局变量节点排序规则

全局变量节点的排序顺序(称为排序)在两个级别上进行控制:全局变量本身内部和使用全局变量的应用程序。

在应用程序级别,可以通过对用作下标的值执行数据转换来控制全局节点的排序方式(InterSystems SQL和对象通过用户指定的排序函数来执行此操作)。 例如,如果创建一个按字母顺序排序但忽略大小写的名称列表,那么通常你会使用名称的大写版本作为下标:

/// w ##class(PHA.TEST.Global).GlobalSortAlpha()
ClassMethod GlobalSortAlpha()
{
	Kill ^Data

	For name = "Cobra","jackal","zebra","AARDVark" {
		Set ^Data($ZCONVERT(name,"U")) = name
	}


	Set key = $Order(^Data(""))
	While (key '= "") {
		Write ^Data(key),!  
		Set key = $Order(^Data(key)) 
	}
	q ""
}
DHC-APP>w ##class(PHA.TEST.Global).GlobalSortAlpha()
AARDVark
Cobra
jackal
zebra
 

此示例将每个名称转换为大写(使用$ZCONVERT函数),以便对下标进行排序,而不考虑大小写。每个节点都包含未转换的值,以便可以显示原始值。

数值和字符串值下标

数字值在字符串值之前进行排序;也就是说,值1在值“a”之前。如果对给定的下标同时使用数值和字符串值,则需要注意这一点。如果将全局变量用于索引(即根据值对数据进行排序),则最常见的是将值排序为数字(如薪水salaries)或字符串(如邮政编码postal codes)。

对于按数字排序的节点,典型的解决方案是使用一元+运算符将下标值强制为数字值。例如,如果要构建按年龄对id值进行排序的索引,则可以强制年龄始终为数字:

 Set ^Data(+age,id) = ""

如果希望将值排序为字符串(如“0022”“0342”“1584”),则可以通过添加空格(“”)字符来强制下标值始终为字符串。例如,如果正在构建一个按邮政编码对id值进行排序的索引,则可以强制zipcode始终为字符串:

 Set ^Data(" "_zipcode,id) = ""

这确保带有前导零的值(如“0022”)始终被视为字符串。

$SORTBEGIN$SORTEND函数

通常,不必担心在InterSystems IRIS中对数据进行排序。无论使用SQL还是直接全局访问,排序都是自动处理的。

然而,在某些情况下,可以更有效地进行排序。 具体来说,在以下情况下(1)需要设置大量随机(即未排序)的全局节点,(2)生成的全局节点的总大小接近InterSystems IRIS缓冲池的很大一部分,那么性能可能会受到不利影响- 因为很多SET操作涉及到磁盘操作(因为数据不适合缓存)。 这种情况通常出现在涉及创建索引全局函数的情况下,例如批量数据加载、索引填充或对临时全局函数中的未索引值进行排序

为了有效地处理这些情况,ObjectScript提供了$SORTBEGIN$SORTEND函数。 $SORTBEGIN函数为全局变量(或其中的一部分)启动了一种特殊模式,在这种模式中,进入全局变量的数据集被写入一个特殊的临时缓冲区,并在内存(或临时磁盘存储)中进行排序。 当在操作结束时调用$SORTEND函数时,数据将按顺序写入实际的全局存储中。 总体操作效率更高,因为实际的写操作是按照要求更少磁盘操作的顺序完成的。

$SORTBEGIN函数很容易使用; 在开始排序操作之前,用你想要排序的全局变量的名称调用它,并在操作完成时调用$SORTEND:

/// w ##class(PHA.TEST.Global).GlobalSortBeginEnd()
ClassMethod GlobalSortBeginEnd()
{

	Kill ^Data
	
	// 为^Data全局初始化排序模式
	Set ret = $SortBegin(^Data)

	For i = 1:1:10000 {
		Set ^Data($Random(1000000)) = ""
	}

	Set ret = $SortEnd(^Data)

	// ^Data现在已经设置和排序

	Set start = $ZH 
	// 现在迭代并显示(按顺序)
	Set key = $Order(^Data(""))
	While (key '= "") {
		Write key,!
		Set key = $Order(^Data(key)) 
	}
	
	Set elap = $ZH - start  
	Write "Time (seconds): ",elap
	q ""
}

$SORTBEGIN函数是为全局变量创建的特殊情况而设计的,在使用时必须小心。 特别地,在$SORTBEGIN模式下,不能从正在写入的全局变量中读取数据; 由于数据没有写入,读取将是不正确的。

InterSystems SQL自动使用这些函数创建临时全局索引(例如对未索引的字段进行排序)。

在全局变量中使用间接

通过间接方式,ObjectScript提供了一种在运行时创建全局变量引用的方法。 这对于在程序编译时不知道全局变量结构或名称的应用程序非常有用。

间接操作符@支持间接操作,它解除了对包含表达式的字符串的引用。 根据@操作符的使用方式,有几种间接类型。

下面的代码提供了一个名称间接引用的示例,在这个示例中,使用@操作符对包含全局引用的字符串进行解引用:

/// w ##class(PHA.TEST.Global).GlobalIndirect()
ClassMethod GlobalIndirect()
{
	Kill ^Data
	Set var = "^Data(100)"
	// 现在使用间接设置^Data(100)
	Set @var = "This data was set indirectly."
	// 现在直接显示值:
	Write "Value: ",^Data(100)
	q ""
}
DHC-APP> w ##class(PHA.TEST.Global).GlobalIndirect()
Value: This data was set indirectly.

也可以使用下标间接在间接语句中混合表达式(变量或文字值):

/// w ##class(PHA.TEST.Global).GlobalIndirect1()
ClassMethod GlobalIndirect1()
{

	Kill ^Data
	Set glvn = "^Data"
	For i = 1:1:10 {
		Set @glvn@(i) = "This data was set indirectly."
	}

	Set key = $Order(^Data(""))
	While (key '= "") {
		Write "Value ",key, ": ", ^Data(key),!
		Set key = $Order(^Data(key))
	}
	q ""
}
DHC-APP>w ##class(PHA.TEST.Global).GlobalIndirect1()
Value 1: This data was set indirectly.
Value 2: This data was set indirectly.
Value 3: This data was set indirectly.
Value 4: This data was set indirectly.
Value 5: This data was set indirectly.
Value 6: This data was set indirectly.
Value 7: This data was set indirectly.
Value 8: This data was set indirectly.
Value 9: This data was set indirectly.
Value 10: This data was set indirectly.

间接是ObjectScript的一个基本特性; 它并不局限于全局引用。

0
0 163
文章 姚 鑫 · 五月 6, 2021 5m read

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

遍历全局变量中的数据

有许多方法可以遍历(迭代)存储在全局变量中的数据。

$ORDER(下一个/上一个)函数

ObjectScript $Order函数允许顺序访问全局中的每个节点。

$ORDER函数返回给定级别(下标编号)的下一个下标的值。例如,假设定义了以下全局设置:

 Set ^Data(1) = ""
 Set ^Data(1,1) = ""
 Set ^Data(1,2) = ""
 Set ^Data(2) = ""
 Set ^Data(2,1) = ""
 Set ^Data(2,2) = ""
 Set ^Data(5,1,2) = ""

要查找第一个第一级下标,我们可以使用:

 SET key = $ORDER(^Data(""))

这将返回空字符串(“”)之后的第一个第一级下标。(空字符串用于表示第一个条目之前的下标值;作为返回值,它用于指示没有后续的下标值。)。在本例中,key现在将包含值1

我们可以通过在$ORDER表达式中使用1或键来查找下一个第一级下标:

 SET key = $ORDER(^Data(key))

如果key的初始值为1,则此语句将其设置为2(因为^Data(2)是下一个第一级下标)。再次执行此语句会将key设置为5,因为这是下一个第一级下标。请注意,即使没有直接存储在^Data(5)中的数据,也会返回5。再次执行此语句将把key设置为空字符串(“”),表示没有更多的一级下标。

通过将附加下标与$ORDER函数一起使用,可以迭代不同的下标级别。$order返回其参数列表中最后一个下标的下一个值。使用上述数据,该语句如下:

 SET key = $ORDER(^Data(1,""))

将关键字设置为1,因为^Data(1,1)是下一个二级下标。再次执行此语句会将KEY设置为2,因为这是下一个二级下标。再次执行此语句将把key设置为“”,表示在节点^Data(1)下没有更多的二级下标。

使用$ORDER循环

下面的ObjectScript代码定义了一个简单的全局变量,然后循环遍历其所有第一级子脚本:

/// w ##class(PHA.TEST.Global).ReadGlobalSimpleFor()
ClassMethod ReadGlobalSimpleFor()
{
	// 清除^Data,以防它有数据
	Kill ^Data

	// 使用示例数据填写^Data
	For i = 1:1:100 {
		// 将每个节点设置为随机人名
		Set ^Data(i) = ##class(%PopulateUtils).Name()
	}

	// 在每个节点上循环 查找第一个节点
	Set key = $Order(^Data(""))

	While (key '= "") {
		Write "#", key, " ", ^Data(key),!
		// F查找下一个节点
		Set key = $Order(^Data(key))
	}
	
	q ""
}
DHC-APP>w ##class(PHA.TEST.Global).ReadGlobalSimpleFor()
#1 Edwards,Barbara T.
#2 Ragon,Kevin K.
#3 Avery,Josephine U.
#4 Townsend,Buzz R.
#5 Joyce,Quentin V.
#6 Xenia,Ted F.
#7 Chadwick,Wilma N.
#8 Duquesnoy,Orson A.
#9 Uberoth,Orson X.
#10 Jones,Joe O.
#11 Hills,Barb R.
#12 Yakulis,Pat J.
#13 Tesla,Al P.
#14 Goncharuk,Sam J.
#15 Presley,Amanda D.
#16 Olsen,Kristen I.
#17 Roentgen,John T.
#18 Minichillo,Elmo N.
#19 Koivu,Patrick R.
#20 Harrison,Lawrence I.
#21 Page,Agnes P.
#22 Wijnschenk,Hannah L.
#23 Chesire,Bart S.
#24 Klingman,Liza K.
#25 Smyth,Imelda J.
#26 Alton,Filomena L.
#27 Minichillo,Charles U.
#28 Nichols,Jeff W.
#29 O'Rielly,Thelma X.
#30 Schaefer,Kristen G.
#31 Black,Filomena R.
#32 Vivaldi,Xavier B.
#33 Allen,Phyllis U.
#34 Mastrolito,Zelda Z.
#35 Quilty,Jane V.
#36 Zevon,Maureen H.
#37 O'Rielly,Maureen C.
#38 Olsen,Robert W.
#39 Page,Milhouse D.
#40 Nelson,Dick R.
#41 Ironhorse,Danielle I.
#42 Tweed,Rhonda T.
#43 Quincy,Terry L.
#44 Tsatsulin,Jocelyn C.
#45 Yeats,Michelle E.
#46 Jackson,Paul V.
#47 Humby,Dave I.
#48 Kelvin,Natasha R.
#49 Kelvin,Kyra R.
#50 Yoders,Agnes R.
#51 Tesla,Amanda F.
#52 Harrison,Christen T.
#53 Allen,Nataliya J.
#54 Xenia,Diane W.
#55 Xenia,Phyllis E.
#56 Isaksen,Pam D.
#57 Waterman,Charles M.
#58 Peters,Sophia N.
#59 Peterson,Bart B.
#60 Eastman,Edward S.
#61 Young,Belinda F.
#62 White,Fred G.
#63 Ubertini,Lola U.
#64 Uhles,Xavier T.
#65 Quine,Phyllis T.
#66 Hernandez,Umberto B.
#67 Allen,Zelda S.
#68 Harrison,David Z.
#69 Harrison,Danielle T.
#70 Ott,Dick D.
#71 Lennon,Joe Y.
#72 Quigley,Alfred M.
#73 Klausner,Mario J.
#74 Tsatsulin,Emily S.
#75 Anderson,Edward R.
#76 Lennon,Fred H.
#77 DeSantis,Molly J.
#78 Browne,Dave H.
#79 Cunningham,Buzz L.
#80 Ingersol,Edgar G.
#81 Paraskiv,Linda O.
#82 Beatty,Kim H.
#83 Quilty,Wilma P.
#84 Dunlap,Jules I.
#85 Waterman,Buzz D.
#86 Edison,Kim C.
#87 Eagleman,Michael N.
#88 Huff,Hannah K.
#89 Vanzetti,Maria E.
#90 Zampitello,Angela Q.
#91 Anderson,Angela Z.
#92 Isaacs,Charlotte Q.
#93 O'Donnell,Paul A.
#94 Underman,Zeke R.
#95 Schultz,James I.
#96 Chadbourne,Janice N.
#97 Lennon,William T.
#98 Vonnegut,Pam V.
#99 Miller,Patricia T.
#100 Hills,Charles C.

其他$ORDER参数

ObjectScript $ORDER函数接受可选的第二个和第三个参数。 第二个参数是一个方向标志,指示希望在哪个方向上遍历全局变量。 默认值1指定正向遍历,而-1指定反向遍历。

第三个参数(如果存在)包含一个局部变量名。 如果$ORDER找到的节点包含数据,则将找到的数据写入这个本地变量。 当您在一个全局循环中,并且您对节点值和下标值感兴趣时,这样操作更有效。

$QUERY函数

如果需要访问全局变量中的每个节点和子节点,在子节点上上下移动,请使用ObjectScript $Query函数。(或者,可以使用嵌套的$ORDER循环)。

$Query函数接受全局变量引用,并返回一个字符串,其中包含全局变量中下一个节点的全局引用(如果没有后续节点,则返回"")。若要使用$QUERY返回的值,必须使用ObjectScript间接运算符(@)

例如,假设定义了以下全局设置:

 Set ^Data(1) = ""
 Set ^Data(1,1) = ""
 Set ^Data(1,2) = ""
 Set ^Data(2) = ""
 Set ^Data(2,1) = ""
 Set ^Data(2,2) = ""
 Set ^Data(5,1,2) = ""

以下是对$QUERY的调用:

 SET node = $QUERY(^Data(""))

将节点设置为字符串“^Data(1)”,即全局中第一个节点的地址。然后,要获取全局中的下一个节点,请再次调用$QUERY并在节点上使用间接运算符:

此时,节点包含字符串“^Data(1,1)”

以下示例定义一组全局变量节点,然后使用$QUERY遍历它们,同时写入每个节点的地址:

/// w ##class(PHA.TEST.Global).ReadGlobalSimpleQuery()
ClassMethod ReadGlobalSimpleQuery()
{
	Kill ^Data // 确保^Data为空

	// 将一些数据放入^Data // 
	Set ^Data(1) = ""
	Set ^Data(1,1) = ""
	Set ^Data(1,2) = ""
	Set ^Data(2) = ""
	Set ^Data(2,1) = ""
	Set ^Data(2,2) = ""
	Set ^Data(5,1,2) = ""

	// 现在浏览^Data 查找第一个节点
	Set node = $Query(^Data(""))
	While (node '= "") {
		Write node,!
		// 获取下一个节点
		Set node = $Query(@node)
	}
	q ""
}
DHC-APP>w ##class(PHA.TEST.Global).ReadGlobalSimpleQuery()
^Data(1)
^Data(1,1)
^Data(1,2)
^Data(2)
^Data(2,1)
^Data(2,2)
^Data(5,1,2)
0
0 137
文章 姚 鑫 · 五月 5, 2021 6m read

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

本章描述了使用多维存储(全局变量)可以执行的各种操作。

以全局变量存储数据

在全局节点中存储数据很简单:像对待任何其他变量一样对待全局变量。 区别在于对全局变量的操作是自动写入数据库的。

创建全局变量

创建新的全局变量不需要设置工作;只需将数据设置为全局变量即可隐式创建新的全局结构。可以创建全局变量(或全局变量下标)并通过单个操作将数据放入其中,也可以创建全局变量(或下标)并通过将其设置为空字符串将其保留为空。在ObjectScript中,这些操作是使用SET命令完成的。

下面的例子定义了一个名为Color(如果还不存在)的全局变量,并将值“Red”与之关联。 如果已经存在一个名为Color的全局变量,那么这些示例将其修改为包含新信息。

在ObjectScript中:

 SET ^Color = "Red"

注意:在应用程序中使用直接全局访变量问时,应制定并遵守命名约定,以防止应用程序的不同部分相互“遍历”;这类似于为类、方法和其他变量开发命名约定。

在全局变量节点中存储数据

要在全局下标节点中存储值,只需像设置任何其他变量数组一样设置全局节点的值。如果指定的节点以前不存在,则会创建该节点。如果它确实存在,则其内容将替换为新值。

可以通过表达式(称为全局引用)指定全局内的节点。全局引用由脱字符(^)、全局名称和(如果需要)一个或多个下标值组成。下标(如果有)用括号“()”括起来,并用逗号分隔。每个下标值本身都是一个表达式:文字值、变量、逻辑表达式,甚至是全局引用。

设置全局节点的值是一个原子操作:它肯定会成功,不需要使用任何锁来确保并发性。

以下都是有效的全局引用:

在ObjectScript中:

   SET ^Data = 2
   SET ^Data("Color")="Red"
   SET ^Data(1,1)=100        /*第二级下标(1,1)设置为值100。第一级下标(^DATA(1))不存储任何值。 */   
   SET ^Data(^Data)=10       /*全局变量^data的值是下标的名称。 */
   SET ^Data(a,b)=50         /*局部变量a和b的值是下标的名称 */
   SET ^Data(a+10)=50       

此外,还可以在运行时使用间接方式构造全局引用。

在全局变量节点中存储结构化数据

每个全局节点可以包含最多32K个字符的单个字符串。

数据通常以以下方式之一存储在节点中:

  • 作为最多32K个字符的单个字符串(具体地说,32K - 1)。
  • 作为包含多条数据的字符分隔字符串。

要使用字符分隔符在节点中存储一组字段,只需使用连接操作符(_)将这些值连接在一起。下面的ObjectScript示例使用#字符作为分隔符:

   SET ^Data(id)=field(1)_"#"_field(2)_"#"_field(3)

检索数据时,可以使用$PIECE函数将字段拆分:

    SET data = $GET(^Data(id))
    FOR i=1:1:3 {
        SET field(i) = $PIECE(data,"#",i)
    }
    QUIT
  • 作为包含多条数据的$LIST编码字符串。

$LIST函数使用特殊的长度编码方案,不需要保留分隔符。(这是InterSystems IRIS对象和SQL使用的默认结构。)

要在节点中存储一组字段,请使用$LISTBUILD函数构造列表:

   SET ^Data(id)=$LISTBUILD(field(1),field(2),field(3))

检索数据时,可以使用$LIST$LISTGET函数将字段拆分:

    SET data = $GET(^Data(id))
    FOR i = 1:1:3 {
        SET field(i)=$LIST(data,i)
    }
    QUIT
  • 作为较大数据集(例如流或“BLOB”)的一部分。

由于单个节点的数据量限制在略低于32K,因此可以通过将数据存储在一组连续节点中来实现更大的结构(如流):

   SET ^Data("Stream1",1) = "First part of stream...."
   SET ^Data("Stream1",2) = "Second part of stream...."
   SET ^Data("Stream1",3) = "Third part of stream...."

获取流的代码(如%GlobalCharacterStream类提供的流)循环遍历结构中的连续节点,该结构将数据作为连续字符串提供

  • 作为一个位串。

如果正在实现位图索引(位字符串中的位对应表中的行的索引),应该将全局索引的节点值设置为位字符串。 请注意IRIS使用压缩算法来编码位串; 因此,位串只能使用IRIS $BIT函数来处理。

  • 作为一个空节点。

如果感兴趣的数据是由节点本身提供的,那么通常将实际下标设置为空字符串("")。 例如,将名称与ID值相关联的索引通常是这样的:

  SET ^Data("APPLE",1) = ""
  SET ^Data("ORANGE",2) = ""
  SET ^Data("BANANA",3) = ""

删除全局节点

要从数据库中删除一个全局节点、一组子节点或整个全局节点,请使用ObjectScript killZKILL命令。

Kill命令删除特定全局引用处的所有节点(数据及其在数组中的相应条目),包括任何子代节点。也就是说,所有以指定下标开头的节点都将被删除。

例如,ObjectScript语句:

  KILL ^Data

删除整个^Data全局变量。对此全局变量的后续引用将返回<UNDEFINED>错误。

ObjectScript语句:

   KILL ^Data(100)

删除^Data全局变量中节点100的内容。如果有子代节点,如^data(100,1)^data(100,2)^data(100,1,2,3),这些子节点也会被删除。

ObjectScript ZKILL命令用于删除指定的全局或全局下标节点。它不会删除子代子节点。

注意:在杀死一个大型全局变量之后,该全局变量曾经占用的空间可能没有完全释放,因为垃圾收集器守护进程在后台将这些块标记为空闲。因此,在终止大型全局变量之后立即调用SYS.Database类的ReturnUnusedSpace方法可能不会返回预期大小的空间,因为该全局占用的块可能尚未释放。

不能对全局变量使用new命令。

测试全变量局节点的存在

要测试特定全局变量(或其后代)是否包含数据,请使用$DATA函数。

$DATA返回一个值,该值指示指定的全局变量引用是否存在。可能的返回值包括:

状态值含义
0全局变量未定义。
1全局变量存在并包含数据,但没有子代。请注意,空字符串(“”)可用作数据。
10全局变量有后代(包含指向子节点的向下指针),但本身不包含数据。对此类变量的任何直接引用都将导<UNDEFINED>错误。例如,如果$data(^y)返回10,则SET x=^y将产生<UNDEFINED>错误。
11全局变量既包含数据,又有后代(包含指向子节点的向下指针)。

检索全局变量节点的值

要获取存储在特定全局变量节点中的值,只需使用全局引用作为表达式:

   SET color = ^Data("Color")    ; assign to a local variable
   WRITE ^Data("Color")          ; use as a command argument
   SET x=$LENGTH(^Data("Color")) ; use as a function parameter

$GET函数

还可以使用$GET函数获取全局节点的值:

   SET mydata = $GET(^Data("Color"))

这将检索指定节点的值(如果存在),如果该节点没有值,则返回空字符串(“”)。如果节点没有值,可以使用可选的第二个参数$get返回指定的默认值。

WRITEZWRITEZZDUMP命令

可以使用各种ObjectScript显示命令显示全局变量或全局变量子节点的内容。WRITE命令以字符串形式返回指定全局或子节点的值。ZWRITE命令返回全局变量的名称及其值,以及它的每个子代节点及其值。ZZDUMP命令以十六进制转储格式返回指定全局或子节点的值。

0
0 95
文章 姚 鑫 · 五月 4, 2021 7m read

第二章 全局变量结构(二)

全局变量物理结构

全局变量使用高度优化的结构存储在物理文件中。管理此数据结构的代码也针对运行InterSystems IRIS的每个平台进行了高度优化。这些优化确保全局操作具有高吞吐量(每单位时间的操作数)、高并发性(并发用户总数)、缓存的高效使用,并且不需要与性能相关的持续维护(例如频繁的重建、重新索引或压缩)。

用于存储全局变量的物理结构是完全封装的;应用程序不会以任何方式担心物理数据结构。

全局变量存储在磁盘上的一系列数据块中;每个块的大小(通常为8KB)是在创建物理数据库时确定的。为了提供对数据的高效访问,InterSystems IRIS维护了一种复杂的B树状结构,该结构使用一组指针块将相关数据块链接在一起。InterSystems IRIS维护一个缓冲池-经常引用的块的内存缓存-以降低从磁盘获取块的成本。

虽然许多数据库技术使用类似B树的结构来存储数据,但InterSystems IRIS在许多方面都是独一无二的:

  • 存储机构通过安全、易于使用的接口显露出来。
  • 压缩下标和数据以节省磁盘空间和宝贵的内存缓存空间。
  • 存储引擎针对事务处理操作进行了优化:插入、更新和删除都很快。与关系系统不同,InterSystems IRIS从不需要重建索引或数据来恢复性能。
  • 存储引擎针对最大并发访问进行了优化。
  • 数据会自动群集,以实现高效检索。

引用全局变量

全局变量驻留在特定的InterSystems IRIS数据库中。如果使用适当的映射,全局变量的部分可以驻留在不同的数据库中。数据库可以在物理上位于当前系统上,也可以位于通过ECP网络访问的远程系统上。术语数据集是指包含InterSystems IRIS数据库的系统和目录。

命名空间是共同构成一组相关信息的数据集和全局映射的逻辑定义。

简单的全局变量引用适用于当前选定的命名空间。名称空间定义可能导致它物理访问本地系统或远程系统上的数据库。不同的全局变量可以映射到不同的位置或数据集(其中数据集是指包含InterSystems IRIS数据库的系统和目录)。

例如,要在当前已映射到的命名空间中创建对全局顺序的简单引用,请使用以下语法:

^ORDER

设置全局变量映射

可以将全局变量和例程从一个数据库映射到相同或不同系统上的另一个数据库。这允许简单地引用可以存在于任何地方的数据,这是命名空间的主要特征。可以映射整个全局或部分全局;映射全局(或下标)的一部分称为下标级别映射(SLM)。因为可以映射全局下标,所以数据可以轻松地跨磁盘。

全局映射是分层应用的。例如,如果NSX命名空间有一个关联的DBX数据库,但将^x全局变量映射到DBY数据库,将^x(1)映射到DBZ数据库,则^x全局变量的任何下标形式(属于^x(1)层次结构的那些除外)都映射到DBY;属于^x(1)层次结构的那些全局变量映射到DBZ。下图说明了此层次结构:

image

在此图中,全局变量及其层次结构显示为灰色,它们映射到的数据库显示为黑色。

还可以将映射的、下标的全局的一部分映射到另一个数据库,甚至映射回初始全局映射到的数据库。假设前面的示例有^x(1,2)全局变量返回到DBY数据库的附加映射。这将如下所示:

image

同样,全局变量及其层次结构显示为灰色,它们映射到的数据库显示为黑色。

一旦将全局从一个命名空间映射到另一个命名空间,就可以引用映射的全局变量,就像它在当前命名空间中一样-只需一个简单的引用,如^Order^X(1)

重要提示:建立下标级别映射范围时,字符串下标的行为与整数下标的行为不同。对于字符串,第一个字符确定范围,而对于整数,范围使用数值。例如,下标范围("A"):("C") 不仅包含AA,还包含ACABCDEF;相比之下,下标范围(1):(2) 不包含11

使用全局和下标的不同范围

命名空间的每个映射必须引用不同范围的全局变量或下标。映射验证可防止建立任何类型的重叠。例如,如果使用管理门户创建与现有映射重叠的新映射,则门户会阻止这种情况发生,并显示一条错误消息。

记录更改

通过门户对映射的成功更改也会记录在messages.log中;不成功的更改不会记录。通过手动编辑配置参数(CPF)文件来建立映射的任何失败尝试都会记录在messages.log中.

扩展的全局变量引用

可以引用位于当前命名空间以外的命名空间中的全局变量。这称为扩展全局变量引用或简称为扩展引用。

有两种形式的扩展引用:

  • 显式命名空间引用-将全局所在命名空间的名称指定为全局变量引用语法的一部分。
  • 隐含名称空间引用-指定目录和系统名称(可选)作为全局变量引用语法的一部分。在这种情况下,不适用全局变量映射,因为物理数据集(目录和系统)是作为全局变量引用的一部分提供的。

最好使用显式名称空间,因为这允许在需求更改时在外部重新定义逻辑映射,而无需更改应用程序代码。

InterSystems IRIS支持两种形式的扩展引用:

  • 方括号语法,它用方括号([])将扩展引用括起来。
  • 环境语法,用竖线(||)括起扩展引用。

注意:扩展全局引用的示例使用Windows目录结构。实际上,此类引用的形式取决于操作系统。

方括号语法

可以使用方括号语法来指定具有显式命名空间或隐含命名空间的扩展全局引用:

显式命名空间:

^[nspace]glob

隐含命名空间:

^[dir,sys]glob

在显式名称空间引用中,nspace是全局全局当前尚未映射或复制到的已定义名称空间。在隐含的名称空间引用中,dir是目录(其名称包括尾随反斜杠:“\”),sysSYSTEMglob是该目录中的全局目录。如果将nspacedir指定为(“^”),则引用的是进程私有全局变量。

除非将目录和系统名称或命名空间名称指定为变量,否则必须在目录和系统名称或命名空间名称两边加上引号。目录和系统一起构成一个隐含的命名空间。隐含的命名空间可以引用以下任一项:

  • 指定系统上的指定目录。
  • 本地系统上的指定目录(如果未在引用中指定系统名称)。如果在隐含的命名空间引用中省略了系统名称,则必须在目录引用内提供双脱字符(^^)以指示省略的系统名称。

要在远程系统上指定隐式命名空间,请执行以下操作:

["dir","sys"]

在本地系统上指定一个隐含的命名空间:

["^^dir"]

例如,要访问名为SALES的计算机上的C:\BUSINESS\目录中的全局变量ORDER:

  SET x = ^["C:\BUSINESS\","SALES"]ORDER

要访问本地计算机上的C:\BUSINESS\目录中的全局ORDER:

   SET x = ^["^^C:\BUSINESS\"]ORDER

要访问定义的命名空间MARKETING中的全局ORDER:

   SET x = ^["MARKETING"]ORDER

要访问进程私有的全局ORDER:

   SET x = ^["^"]ORDER

注意:在创建涉及镜像数据库的隐含命名空间扩展引用时,可以使用镜像数据库路径,格式为:mirror:mirror_name:mirror_DB_name。 例如,当在镜像CORPMIR中引用镜像数据库名称为mirdb1的数据库时,可以形成如下的隐含引用:

["^^:mirror:CORPMIR:mirdb1"]

镜像数据库路径既可以用于本地数据库,也可以用于远程数据库。

环境语法

环境语法被定义为:

^|"env"|global

"env"可以有以下五种格式之一:

  • 空字符串("")-本地系统上的当前命名空间。
  • "namespace" -定义的命名空间,当前没有全局映射到。 命名空间名称不区分大小写。 如果namespace具有特殊值"^",则它是进程私有的全局变量。
  • "^^dir" -一个隐含的命名空间,它的默认目录是本地系统上的指定目录,其中dir包含一个末尾的反斜杠(" \ ")
  • "^system^dir"——一个隐含的命名空间,默认目录是指定的远程系统上的指定目录,其中dir包含一个结尾的反斜杠(" \ ")。
  • 省略-如果根本没有"env",它是进程私有的全局变量。

要访问当前系统上当前命名空间中的全局ORDER,如果没有为ORDER定义映射,请使用以下语法:

   SET x = ^|""|ORDER

这与简单的全局变量引用相同:

   SET x = ^ORDER

要访问映射到定义的命名空间MARKETING的全局ORDER:

   SET x = ^|"MARKETING"|ORDER

可以使用一个隐含的命名空间来访问本地系统上C:\BUSINESS\目录下的全局ORDER:

   SET x = ^|"^^C:\BUSINESS\"|ORDER

可以使用一个隐含的命名空间来访问一个名为SALES的远程系统上的目录C:\BUSINESS中的全局ORDER:

   SET x = ^|"^SALES^C:\BUSINESS\"|ORDER

要访问进程私有的全局ORDER:

   SET x = ^||ORDER
   SET x=^|"^"|ORDER
0
0 156
文章 姚 鑫 · 五月 3, 2021 6m read

第二章 全局变量结构(一)

本章描述全局变量的逻辑视图,并概述全局变量是如何在磁盘上物理存储的。

全局变量的逻辑结构

全局变量是存储在物理InterSystems IRIS®数据库中的命名多维数组。 在应用程序中,全局变量到物理数据库的映射基于当前名称空间——名称空间提供一个或多个物理数据库的逻辑统一视图。

全局命名约定和限制

全局名称指定其目标和用途。有两种类型的全局变量和一组单独的变量,称为“进程私有全局变量”:

  • 全局变量 - 这就是所谓的标准全局变量;通常,这些变量被简称为全局变量。它是驻留在当前命名空间中的永久性多维数组。
  • 扩展全局引用-这是位于当前命名空间以外的命名空间中的全局引用。
  • 进程私有全局变量-这是一个数组变量,只有创建它的进程才能访问。

全局变量的命名约定如下:

  • 全局变量名称以脱字符(^)前缀开头。这个插入符号区分全局变量和局部变量。
  • 全局变量名称中脱字符(^)前缀后的第一个字符可以是:
    • 字母或百分号字符(%)-仅适用于标准全局变量。对于全局变量名称,字母被定义为ASCII 65ASCII 255范围内的字母字符。如果全局名称以“%”开头(但不是“%Z”“%z”),则此全局名称供InterSystems IRIS系统使用。%GLOBAL通常存储在IRISSYS或IRISLIB数据库中。
    • 竖线(|)或左方括号([)-表示扩展全局引用或进程专用全局变量。使用取决于后续字符。
  • 全局变量名称的其他字符可以是字母、数字或句号(.)字符。 百分比(%)字符不能使用,除非作为全局名称的第一个字符。 “.”字符不能作为全局名称的最后一个字符。
  • 全局名称最长可达31个字符(不包括脱字符前缀)。可以指定更长的全局名称,但InterSystems IRIS只将前31个字符视为重要字符。
  • 全局名称区分大小写。
  • InterSystems IRIS对全局引用的总长度施加限制,而该限制又对任何下标值的长度施加限制。

在IRISSYS数据库中,InterSystems将除以“z”“Z”“%z”“%Z”开头的所有全局变量名称保留给自己。在所有其他数据库中,InterSystems保留所有以“ISC”开头的全局名称。和“%isc.”

示例全局名称及其用法

以下是各种全局名称的示例以及每种名称的用法:

  • ^globalname - 标准全局变量
  • ^|"environment"|globalname - 扩展全局变量引用的环境语法
  • ^||globalname - 进程私有全局变量
  • ^|"^"| - 进程私有全局变量
  • ^[namespace]globalname - 扩展全局变量引用中显式命名空间的括号语法
  • ^[directory,system]globalname - 扩展全局变量引用中隐含命名空间的括号语法
  • ^["^"]globalname - 进程私有全局变量
  • ^["^",""]globalname - 进程私有全局变量

注意:全局名称只能包含有效的标识符字符;默认情况下,这些字符如上所述。但是,NLS(国家语言支持)定义了一组不同的有效标识符字符集。全局名称不能包含Unicode字符。

因此,以下都是有效的全局名称:

   SET ^a="The quick "
   SET ^A="brown fox "
   SET ^A7="jumped over "
   SET ^A.7="the lazy "
   SET ^A1B2C3="dog's back."
   WRITE ^a,^A,^A7,!,^A.7,^A1B2C3
   KILL ^a,^A,^A7,^A.7,^A1B2C3 // keeps the database clean 

全局节点和下标简介

全局通常有多个节点,通常由一个下标或一组下标标识。下面是一个基本示例:

 set ^Demo(1)="Cleopatra"

此语句引用全局节点^Demo(1),它是^Demo全局节点中的一个节点。此节点由一个下标标识。

再举一个例子:

 set ^Demo("subscript1","subscript2","subscript3")=12

该语句指的是全局节点^Demo("subscript1","subscript2","subscript3"),它是同一全局中的另一个节点。此节点由三个下标标识。

再举一个例子:

 set ^Demo="hello world"

该语句引用不使用任何下标的全局节点^Demo

全局的节点形成分层结构。ObjectScript提供了利用此结构的命令。例如,可以删除节点或删除节点及其所有子节点。

全局变量下标

下标有以下规则:

  • 下标数值区分大小写。
  • 下标值可以是任何ObjectScript表达式,前提是该表达式的计算结果不是空字符串("")。

该值可以包括所有类型的字符,包括空格、非打印字符和Unicode字符。(请注意,非打印字符在下标数值中不太实用。)

  • 在解析全局引用之前,InterSystems IRIS计算每个下标的方式与计算任何其他表达式的方式相同。在下面的示例中,我们设置了^Demo全局的一个节点,然后以几种等效的方式引用该节点:
DHC-APP>s ^Demo(1+2+3)="a value"
 
DHC-APP>w ^Demo(3+3)
a value
DHC-APP>w ^Demo(03+03)
a value
DHC-APP>w ^Demo(03.0+03.0)
a value
DHC-APP>set x=6
 
DHC-APP>w ^Demo(x)
a value
  • InterSystems IRIS对全局引用的总长度施加限制,而该限制又对任何下标值的长度施加限制。

注意:上述规则适用于IRIS支持的所有排序规则。对于出于兼容性原因仍在使用的旧归类,如“pre-ISM-6.1”,下标的规则有更多限制。例如,字符下标不能以控制字符作为其初始字符;整数下标中可以使用的位数也有限制。

全局变量节点

在应用程序中,节点通常包含以下类型的结构:

  1. 字符串或数字数据,包括本机Unicode字符。
  2. 具有由特殊字符分隔的多个字段的字符串:
 SET ^Data(10) = "Smith^John^Boston"

可以使用ObjectScript $PIECE 函数来拆分这些数据。

  1. InterSystems IRIS $LIST 结构中包含多个字段。$LIST结构是包含多个长度编码值的字符串。它不需要特殊的分隔符。
  2. 空字符串 ("")。在下标本身用作数据的情况下,实际节点中不存储任何数据。
  3. 一个位串。如果全局变量用于存储位图索引的一部分,那么存储在节点中的值就是位字符串。位串是包含10值的逻辑压缩集的字符串。可以使用$BIT函数构造位串。
  4. 更大的数据集的一部分。例如,对象和SQL引擎将流(BLOB)存储为全局中连续的32K节点系列。通过流接口,流的用户不知道流是以这种方式存储的。

请注意,任何全局节点都不能包含长度超过字符串长度限制的字符串,字符串长度限制非常长。

全局变量排序规则

在全局中,节点按排序(排序)顺序存储。

应用程序通常通过将转换应用于用作下标的值来控制节点的排序顺序。例如,SQL引擎在为字符串值创建索引时,会将所有字符串值转换为大写字母,并在前面加上一个空格字符,以确保索引不区分大小写并且以文本形式排序(即使数值存储为字符串)。

全局变量引用的最大长度

全局变量引用(即对特定全局节点或子树的引用)的总长度限制为511个编码字符(少于511个键入字符)。

要保守地确定给定全局变量引用的大小,请使用以下准则:

  1. 全局变量名称:每个字符加1
  2. 对于纯数字下标:每个数字、符号或小数点加1
  3. 对于包含非数字字符的下标:为每个字符添加3

如果下标不是纯数字的,则根据用于编码字符串的字符集的不同,下标的实际长度会有所不同。一个多字节字符最多可以占用3个字节。

请注意,ASCII字符可能占用12字节。 如果排序规则进行大小写折叠,那么ASCII字符可以使用1个字节表示字符,1个字节表示消除歧义字节。 如果排序不执行大小写折叠,ASCII字符占用1字节。

  1. 每个下标加1

如果这些数字的总和大于511,则引用太长。

由于确定限制的方式,如果必须使用长下标或全局名称,这有助于避免使用大量下标级别。 相反,如果使用多个下标级别,则应避免长全局名称和长下标。 因为无法控制正在使用的字符集,所以保持全局名称和下标更短是很有用的。

当对特定引用有疑问时,创建与最长预期全局变量引用长度相等(甚至稍长一点)的全局变量引用的测试版本是有用的。 这些测试的数据为构建应用程序之前可能修订的命名约定提供了指导。

0
0 118