第九章 疯狂Caché 宏和宏预处理器(三)

第九章 疯狂Caché 宏和宏预处理器(三)

#SQLCompile Select

#SQLCompile Select预处理器指令指定任何后续嵌入式SQL语句的数据格式模式。它的形式是:

#SQLCompile Select=value
  • Display-格式化用于屏幕和打印的数据。

  • Logical — 将数据保留在内存中的格式。并打印。

  • ODBC — 格式化数据以通过ODBC或JDBC表示。

  • Runtime — 支持根据执行时选择模式值将输入数据值从显示格式(显示或ODBC)自动转换为逻辑存储格式。输出值将转换为当前模式。可以使用%SYSTEM.SQL类的GetSelectMode方法获取执行时选择模式值。可以使用%SYSTEM.SQL类的SetSelectMode方法设置执行时选择模式值。

  • Text — 显示的同义词。

  • FDBMS — 允许嵌入式SQL以与FDBMS相同的方式格式化数据。

此宏的值确定SELECT OUTPUT主机变量的嵌入式SQL输出数据格式,以及嵌入式SQL INSERT、UPDATE和SELECT INPUT主机变量所需的输入数据格式。

下面的嵌入式sql示例使用不同的编译模式从Sample.Person表返回三个字段,即Name(字符串字段)、DOB(日期字段)和Home(列表字段):

/// d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILE()
ClassMethod TestSQLCOMPILE()
{
	#SQLCOMPILE SELECT=Logical
	&sql(SELECT Name,DOB,Home
		INTO :n,:d,:h
		FROM Sample.Person)
	WRITE "name is: ",n,!
	WRITE "birthdate is: ",d,!
	WRITE "home is: ",h
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILE()
name is: yaoxin
birthdate is: 54536
home is: 889 Clinton Drive
                           St LouisWI78672
/// d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILEDisplay()
ClassMethod TestSQLCOMPILEDisplay()
{
	#SQLCOMPILE SELECT=Display
	&sql(SELECT Name,DOB,Home
		INTO :n,:d,:h
		FROM Sample.Person)
	WRITE "name is: ",n,!
	WRITE "birthdate is: ",d,!
	WRITE "home is: ",h
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILEDisplay()
name is: yaoxin
birthdate is: 04/25/1990
home is: 889 Clinton Drive
                           St LouisWI78672
/// d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILEODBC()
ClassMethod TestSQLCOMPILEODBC()
{
	#SQLCOMPILE SELECT=ODBC
	&sql(SELECT Name,DOB,Home
		INTO :n,:d,:h
		FROM Sample.Person)
	WRITE "name is: ",n,!
	WRITE "birthdate is: ",d,!
	WRITE "home is: ",h
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILEODBC()
name is: yaoxin
birthdate is: 1990-04-25
home is: 889 Clinton Drive,St Louis,WI,7867
/// d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILERuntime()
ClassMethod TestSQLCOMPILERuntime()
{
	#SQLCOMPILE SELECT=Runtime
	&sql(SELECT Name,DOB,Home
		INTO :n,:d,:h
		FROM Sample.Person)
	WRITE "name is: ",n,!
	WRITE "birthdate is: ",d,!
	WRITE "home is: ",h
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestSQLCOMPILERuntime()
name is: yaoxin
birthdate is: 54536
home is: 889 Clinton Drive
                           St LouisWI78672

#UnDef

#UnDef预处理器指令删除已定义的宏的定义。它的形式是:

#UnDef macro-name

其中宏名称是已经定义的宏。

#UnDef跟随#Define#Def1Arg的调用。它与#IfDef及其相关的预处理器指令(#Else#EndIf#IfNDef)协同工作。

下面的示例演示有条件的代码,条件是先定义宏,然后再取消定义。

/// d ##class(PHA.TEST.ObjectScript).TestUnDef()
ClassMethod TestUnDef()
{
	#Define TheSpecialPart

	#IfDef TheSpecialPart
		WRITE "We're in the special part of the program.",!
	#EndIf

	#UnDef TheSpecialPart

	#IfDef TheSpecialPart
		WRITE "We're in the special part of the program.",!
	#Else
		WRITE "We're no longer in the special part of the program.",!
	#EndIf

	#IfNDef TheSpecialPart
		WRITE "We're still outside the special part of the program.",!
	#Else
		WRITE "We're back inside the special part of the program.",!
	#EndIf
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestUnDef()
We're in the special part of the program.
We're no longer in the special part of the program.
We're still outside the special part of the program.

其中,用于此操作的.int代码为:

DHC-APP>d ##class(PHA.TEST.ObjectScript).TestUnDef()
We're in the special part of the program.
We're no longer in the special part of the program.
We're still outside the special part of the program.

##;

##;预处理指令使当前行的其余部分成为未出现在.int代码中的注释。注释仅出现在.mac代码或包含文件中。##;注释指示符应始终用于预处理器指令中的注释:

#Define alphalen ##Function($LENGTH("abcdefghijklmnopqrstuvwxyz")) ##; + 100
   WRITE $$$alphalen," is the length of the alphabet"

##;注释指示符可以出现在#DEFINE#Def1Arg#Dim预处理器指令中。它不能在##CONTINUE预处理器指令之后使用。在预处理器指令中应避免使用//或;行内注释。

##;还可以在ObjectScript代码行或嵌入式SQL代码行的任何位置使用,以指定未出现在.int代码中的注释。当前行的其余部分将继续注释。

##;在嵌入式HTML或嵌入式JavaScript之前进行计算。

#;相比,#;出现在第1列,并使整行成为注释。##;使当前行的其余部分成为注释。当##;出现在该行的第一列中时,它在功能上与#;预处理器指令相同。

##Continue

##CONTINUE预处理器指令在下一行继续宏定义,以支持多行宏定义。它出现在宏定义的行尾,表示继续,格式为:

#Define <beginning of macro definition> ##Continue
     <continuation of macro definition>

一个宏定义可以使用多个##CONTINUE指令。

#Define Multiline(%a,%b,%c) ##Continue
    SET v=" of Oz" ##Continue
    SET line1="%a"_v ##Continue
    SET line2="%b"_v ##Continue
    SET line3="%c"_v
    
 $$$Multiline(Scarecrow,Tin Woodman,Lion)
 WRITE "Here is line 1: ",line1,!
 WRITE "Here is line 2: ",line2,!
 WRITE "Here is line 3: ",line3,!   

##Continue必须出现在宏定义行的末尾。因此,##CONTINUE不能后跟##;注释或/注释文本/注释。#;整行注释也不能在##CONTINUE多行指令中使用。可以按如下方式注释##Continue行:

#Define Multiline(%a,%b,%c) ##Continue
    SET v=" of Oz" /* set a variable to a string */ ##Continue
    SET line1="%a"_v ##Continue
    SET line2="%b"_v ##Continue
    SET line3="%c"_v

##Expression

##Expression预处理器函数在编译时计算ObjectScript表达式。它的形式是:

##Expression(content)

其中,content是不包含任何带引号的字符串或任何预处理器指令的有效ObjectScript代码(嵌套的##Expression除外,如下所述)。

预处理器在编译时计算指令参数的值,并将##Expression(内容)替换为ObjectScript.int代码中的计算结果。变量必须出现在##Expression内的引号中;否则,将在编译时计算它们。##Expression在计算嵌入式HTML或嵌入式JavaScript之前会先计算表达式。

以下示例显示了一些简单的表达式:

/// d ##class(PHA.TEST.ObjectScript).TestExp()
ClassMethod TestExp()
{
	#Define NumFunc ##Expression(1+2*3)
	#Define StringFunc ##Expression("""This is"_" a concatenated string""")
	WRITE $$$NumFunc,!
	WRITE $$$StringFunc,!
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestExp()
9
This is a concatenated string

下面的示例定义一个包含当前例程的编译时间戳的表达式:

#Define CompTS ##Expression("""Compiled: " _ $ZDATETIME($HOROLOG) _ """,!")
  WRITE $$$CompTS
Compiled: 03/26/2020 14:09:11

其中,##Expression的参数被分成三个部分进行解析,并使用“_”运算符进行连接:

  • 初始字符串"""Compiled: "。它由双引号分隔。其中,这对双引号指定求值后出现的双引号。
  • $ZDATETIME($HOROLOG)。由$ZDATETIME函数转换和格式化的$HOROLOG特殊变量在编译时的值。
  • 最后一个字符串,""",!"。这也是用双引号分隔的。其中有一对双引号(求值后会产生一个单双引号)。由于正在定义的值将传递给写入命令,因此最终字符串包括,,因此写入命令包含回车符。

然后,例程的中间(.int)代码将包括一行,如下所示:

  WRITE "Compiled: 05/19/2014 07:49:30",! 

##Expression和文字字符串

使用##Expression式进行分析不能识别文字字符串;引号内的括号字符不会被特殊处理。例如,在指令中

#Define MyMacro ##Expression(^abc(")",1))

带引号的右括号被视为用于指定参数的右括号。

##Expression嵌套

Caché 支持表达式嵌套。可以定义包含展开到其他##Expression的宏的##Expression,只要可以在ObjectScript级别(即,它不包含预处理器指令)计算展开并将其存储在ObjectScript变量中。对于嵌套的##Expression,首先展开带有##Expression的宏,然后展开嵌套的##Expression。不能在##Expression中嵌套其他##预处理器函数。

##Expression、子类、##SafeExpression

如果方法包含##Expression,则在编译类时会检测到这一点。由于编译器不解析##Expression的内容,因此此##Expression可能会在子类中生成不同的代码。为了避免这种情况,Caché使编译器为每个子类重新生成方法代码。例如,##Expression(%classname)插入当前类名;当编译子类时,代码期望它插入子类classname。Caché强制在子类中重新生成此方法,以确保发生这种情况。

如果知道子类中的代码永远不会不同,则可以避免为每个子类重新生成方法。为此,请用##SafeExpression预处理器指令替换##表达式。这两个预处理器指令在其他方面是相同的。

##Expression如何工作的

##Expression的参数通过ObjectScript XECUTE命令设置为一个值:

 SET value="Set value="_expression XECUTE value

其中,Expression是确定Value的值的ObjectScript表达式,不能包含宏或##Expression指令。

但是,XECUTE值的结果可能包含宏和/或另一个##Expression。ObjectScript预处理器进一步扩展其中的任何一个,如本例所示。

假设例程A.mac的内容包括:

#Define BB ##Expression(10_"_"_$$aa^B())
 SET CC = $$$BB
 QUIT

例程B.mac包括:

aa()
 QUIT "##Expression(10+10+10)"

然后,A.int包括以下内容:

 SET CC = 10_30
 QUIT 

##Function

##Function预处理器函数在编译时计算ObjectScript函数。它有这样的形式

##Function(content)

其中,Content是一个ObjectScript函数,可以由用户定义。##Function##Function(Content)替换为该函数的返回值。

下面的示例从ObjectScript函数返回值:

#Define alphalen ##Function($LENGTH("abcdefghijklmnopqrstuvwxyz"))
   WRITE $$$alphalen
```java
在下面的示例中,假设GetCurrentTime.mac文件中有一个用户定义的函数:

Tag1()
KILL ^x
SET ^x = “”"" _ $Horolog _ “”""
QUIT ^x

然后,可以在名为ShowTimeStamps.mac的单独例程中调用此代码,如下所示:
```java
Tag2
#Define CompiletimeTimeStamp ##function($$Tag1^GetCurrentTime())
#Define RuntimeTimeStamp $$Tag1^GetCurrentTime()
 SET x=$$$CompiletimeTimeStamp
 WRITE x,!
 SET y=$$$RuntimeTimeStamp
 WRITE y,! 
```java
该命令在终端的输出类似于:

USER>d ^ShowTimeStamps
60569,43570
“60569,53807”

其中,第一行输出是编译时`$Horolog`的值,第二行是运行时`$Horolog`的值。(输出的第一行不带引号,第二行带引号,因为x用带引号的字符串代替其值,因此终端中不显示引号,而y将带引号的字符串直接打印到终端。)

注意:在给定调用上下文的情况下,应用程序编程人员有责任确保`##Function`调用的返回值具有语义和语法意义。

## `##Lit`

`##LIT`预处理器函数以文字形式保留其参数的内容:
```java
##Lit(content)

其中,Content是有效的ObjectScript表达式字符串。##LIT预处理器指令确保它接收到的字符串不会被求值,而是被视为文字文本。

 #Define Macro1 "Row 1 Value"
 #Define Macro2 "Row 2 Value"
   ##Lit(;;) Column 1 Header ##Lit(;) Column 2 Header
   ##Lit(;;) Row 1 Column 1  ##Lit(;) $$$Macro1
   ##Lit(;;) Row 2 Column 1  ##Lit(;) $$$Macro2 

在.int代码中创建组成表格的一组行:

 ;; Column 1 Header ; Column 2 Header
 ;; Row 1 Column 1  ; "Row 1 Value"
 ;; Row 2 Column 1  ; "Row 2 Value"  

通过使用##LIT指令,宏被求值,并由.int代码中的分号分隔

##Quote

##Quote预处理器函数接受单个参数并返回引用的参数。如果参数已经包含引号字符,则通过将它们加倍来转义这些引号字符。它的形式是:

##Quote(value)

其中value是转换为带引号的字符串的文字。在值中,必须成对使用括号字符或引号字符。例如,以下是有效值:

/// d ##class(PHA.TEST.ObjectScript).TestContinue()
ClassMethod TestContinue()
{
 	#Define qtest ##Quote(He said "Yes" after much debate)
    ZZWRITE $$$qtest
}
DHC-APP> d ##class(PHA.TEST.ObjectScript).TestContinue()
%val="He said ""Yes"" after much debate"

值字符串中的圆括号必须成对出现。以下是有效值:

#Define qtest2 ##Quote(After (a lot of) debate)
   ZZWRITE $$$qtest2

以下示例显示##Quote的使用:

#Define AssertEquals(%e1,%e2) DO AssertEquals(%e1,%e2,##Quote(%e1)_" == "_##Quote(%e2))
Main ;
  SET a="abstract"
  WRITE "Test 1:",!
  $$$AssertEquals(a,"abstract")
  WRITE "Test 2:",!
  $$$AssertEquals(a_"","abstract")
  WRITE "Test 3:",!
  $$$AssertEquals("abstract","abstract")
  WRITE "All done"
  QUIT
AssertEquals(e1,e2,desc) ;
  WRITE desc_" is "_$SELECT(e1=e2:"true",1:"false"),!
  QUIT

##SQL

##SQL预处理器指令在编译时调用指定的SQL语句。它的形式是:

##SQL(SQL-statement)

其中,sql-Statement是有效的SQL语句。##sql预处理器指令类似于&sql指令-##sql()在编译时调用该语句,而&sql()在运行时调用该语句。

如果##SQL指令包含无效的SQL(如语法错误)或引用不存在的表或列,则宏预处理器将生成编译错误。

例如,下面的代码首先在编译时运行查询,然后在运行时再次运行:

/// d ##class(PHA.TEST.ObjectScript).TestSQL()
ClassMethod TestSQL()
{
	##sql(SELECT COUNT(*) INTO :count1 FROM Sample.Person)
	&sql(SELECT COUNT(*) INTO :count2 FROM Sample.Person)
	WRITE "Number of instances of Sample.Person at compile time: ",count1,!
	WRITE "Number of instances of Sample.Person at runtime:      ",count2,!
}

##Unique

##UNIQUE预处理器函数在宏定义中创建一个新的、唯一的局部变量,以在编译时或运行时使用。此指令只能作为#DEFINE#Def1Arg调用的一部分使用。它的形式是:

 ##Unique(new)
 ##Unique(old)

其中NEW指定创建新的唯一变量,OLD指定对同一变量的引用。

SET ##UNIQUE(NEW)创建的变量是名为%mmmu1的局部变量,随后的SET ##UNIQUE(NEW)操作将创建名为%mmmu2%mmmu3等的局部变量。这些局部变量与所有%LOCAL变量遵循相同的作用域规则;%VARIABLE始终是公共变量。与所有局部变量一样,它们可以使用ZWRITE显示,也可以使用无参数KILL终止。

用户代码可以引用##Unique(old)变量,就像它可以引用任何其他ObjectScript变量一样。可以无限次使用##UNIQUE(old)语法来引用创建的变量。

##Unique(New)的后续调用将创建一个新变量;再次调用##Unique(New)之后,对##Unique(Old)的后续调用将引用随后创建的变量。

例如,下面的代码使用##UNIQUE(new)和##UNIQUE(old)在两个变量之间交换值:

/// d ##class(PHA.TEST.ObjectScript).TestUn()
ClassMethod TestUn()
{
	#Define Switch(%a,%b) SET ##Unique(new)=%a, %a=%b, %b=##Unique(old)
	READ "First variable value? ",first,!
	READ "Second variable value? ",second,!
	$$$Switch(first,second)
	WRITE "The first value is now ",first," and the second is now ",second,!
}
DHC-APP> d ##class(PHA.TEST.ObjectScript).TestUn()
First variable value? a
Second variable value? b
The first value is now b and the second is now a

要保持这些变量的唯一性,请执行以下操作:

  • 请勿尝试在#DEFINE#Def1Arg预处理器指令之外设置##UNIQUE(NEW)
  • 不要在方法或过程内的预处理器指令中设置##UNIQUE(NEW)。它们将生成方法(%mmmu1)唯一的变量名;但是,因为这是一个%变量,所以它的作用域是全局的。调用另一个设置##UNIQUE(NEW)的方法也会创建%mmmu1,覆盖第一个方法创建的变量。
  • 切勿直接设置%mmmu1变量。Caché保留所有%变量(%z%Z变量除外)供系统使用;它们不应由用户代码设置。

使用系统提供的宏

可访问系统提供的宏

这些宏可用于%RegisteredObject的所有子类。要使这些在未扩展%RegisteredObject的例程或类中可用,请包括相应的文件:

  • 对于与状态相关的宏,请包括%occStatus.inc。
  • 对于与消息相关的宏,请包含%occMessages.inc

此类语句的语法为:

#Include %occStatus

这些包含文件的名称区分大小写。

系统支持的宏引用

宏名称区分大小写。Caché附带的宏包括:

ADDSC(sc1, sc2)

ADDSC宏将 %Status代码(Sc2)附加到现有的 %Status代码(Sc1)。此宏需要%occStatus.inc。

EMBEDSC(sc1, sc2)

EMBEDSC宏将%Status代码(Sc2)嵌入到现有的%Status代码(Sc1)中。此宏需要%occStatus.inc。

ERROR(errorcode, arg1, arg2, …)

ERROR宏使用对象错误代码(ErrorCode)创建%STATUS对象,该对象的关联文本可能接受某些形式为%1、%2等的参数。然后,Error根据这些附加参数的顺序,将这些参数替换为errorcode后面的宏参数(arg1、arg2等)。此宏需要%occStatus.inc。

FormatMessage(language,domain,id,default,arg1,arg2,…)

FormatMessage宏使能够在同一宏调用中从此命名空间的消息字典中检索文本,并用文本替换消息参数。它返回%字符串。此宏需要%occMessages.inc。

参数 描述
language RFC1766语言代码。在CSP上下文中,可以指定·%Response.Language·以使用默认区域设置。
domain 消息域。在CSP上下文中,可以指定·%Response.Domain·
id 消息ID。
default 找不到由语言、域和ID标识的消息时使用的字符串。
arg1, arg2, and so on 消息参数的替换文本。所有这些都是可选的,因此即使消息没有参数,也可以使用·$FormatMessage·。。

FormatText(text, arg1, arg2, …)

FormatText宏接受可能包含%1%2等形式的参数的输入文本消息(Text)。FormatText然后根据这些附加参数的顺序将这些参数替换为文本参数后面的宏参数(arg1、arg2等)。然后,它返回结果字符串。此宏需要%occMessages.inc。。

FormatTextHTML(text, arg1, arg2, …)

FormatTextHTML宏接受可能包含%1%2等形式的参数的输入文本消息(Text)。FormatTextHTML然后根据这些附加参数的顺序将这些参数替换为文本参数后面的宏参数(arg1、arg2等);然后宏应用HTML转义。然后,它返回结果字符串。此宏需要%occMessages.inc。

FormatTextJS(text, arg1, arg2, …)

FormatTextJS宏接受可能包含%1%2等形式的参数的输入文本消息(TEXT)。然后,FormatTextJS根据这些附加参数的顺序,将这些参数替换为文本参数后面的宏参数(arg1、arg2等);然后,宏将应用JavaScript转义。然后,它返回结果字符串。此宏需要%occMessages.inc。

GETERRORCODE(sc)

GETERRORCODE宏从提供的%状态代码(Sc)返回错误代码值。此宏需要%occStatus.inc。

ISERR(sc)

如果提供%Status代码(Sc)是错误代码,则ISERR宏将返回True。否则,它返回FALSE。此宏需要%occStatus.inc。

ISOK(sc)

如果提供的%Status代码(Sc)成功完成,则ISOK宏将返回True。否则,它返回FALSE。此宏需要%occStatus.inc。

OK

OK宏为成功完成创建%Status代码。此宏需要%occStatus.inc。

Text(text, domain, language)

文本宏用于本地化。它在编译时生成一条新消息,并生成代码以在运行时检索该消息。此宏需要%occMessages.inc。

TextHTML(text, domain, language)

TextHTML宏用于本地化。它执行与文本宏相同的处理;然后额外应用HTML转义。然后,它返回结果字符串。此宏需要%occMessages.inc。

TextJS(text, domain, language)

TextJS宏用于本地化。它执行与文本宏相同的处理;然后额外应用JavaScript转义。然后,它返回结果字符串。此宏需要%occMessages.inc。

ThrowOnError(sc)

ThrowOnError宏计算指定的%状态代码(Sc)。如果sc表示错误状态,ThrowOnError将执行抛出操作,向异常处理程序抛出%Exception.StatusException类型的异常。此宏需要%occStatus.inc。

THROWONERROR(sc, expr)

THROWONERROR宏计算表达式(EXPR),其中表达式的值被假定为%状态代码;宏将%状态代码存储在作为sc传递的变量中。如果%Status代码是错误,则THROWONERROR执行一个抛出操作,向异常处理程序抛出%Exception.StatusException类型的异常。此宏需要%occStatus.inc。

ThrowStatus(sc)

ThrowStatus宏使用指定的%状态代码(Sc)执行抛出操作,以向异常处理程序抛出%Exception.StatusException类型的异常。此宏需要%occStatus.inc。

猜你喜欢

转载自blog.csdn.net/yaoxin521123/article/details/106272532