第九章 瘋狂Caché 宏和宏預處理器(二)

第九章 瘋狂Caché 宏和宏預處理器(二)

#EndIf

#EndIf預處理器指令結束一組預處理器條件。它可以跟在#IfDef#IfUnDef#If#ElseI#Else之後。它的形式是:。

  // 指定條件開始的#IfDef、#If或#Else
  // 指定操作的後續縮進行
#EndIf

#EndIf指令關鍵字應該單獨出現在一行中。同一行中#EndIf後面的任何內容都被視爲註釋,不會進行解析。

#Execute

#EXECUTE預處理器指令在編譯時執行一行ObjectScript。它的形式是:

#Execute <ObjectScript code>

其中,#EXECUTE後面的內容是有效的ObjectScript代碼。此代碼可以引用編譯時具有值的任何變量或屬性;它還可以調用編譯時可用的任何方法或例程。ObjectScript命令和函數始終可供調用。

#EXECUTE不返回任何指示代碼是否成功運行的值。應用程序代碼負責檢查狀態代碼或其他此類信息;這可以使用附加的#EXECUTE指令或其他代碼。

注意:如果將#EXECUTE與局部變量一起使用,可能會出現意想不到的結果。原因包括:

  • 編譯時使用的變量在運行時可能超出範圍。
  • 對於多個例程或方法,變量在引用時可能不可用。應用程序員不控制編譯順序的事實可能會加劇此問題。

例如,可以在編譯時確定星期幾並使用以下代碼保存它:

/// d ##class(PHA.TEST.ObjectScript).TestMExecute()
ClassMethod TestMExecute()
{
	#Execute KILL ^DayOfWeek
	#Execute SET ^DayOfWeek = $ZDate($H,12)

	WRITE "Today is ",^DayOfWeek,".",!
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestMExecute()
Today is Wednesday.

其中,每次進行編譯時都會更新^DayOfWeek全局設置。

#If

#if預處理器指令開始一個條件文本塊。它將ObjectScript表達式作爲參數,測試參數的真值,如果其參數的真值爲真,則編譯代碼塊。該代碼塊以#Else#ElseIf#EndIf指令結束。

#If <expression>
  // subsequent indented lines for specified actions

  // next preprocessor directive

其中<expression>是有效的ObjectScript表達式。如果<expression>的計算結果爲非零值,則爲true。

/// d ##class(PHA.TEST.ObjectScript).TestMIF()
ClassMethod TestMIF()
{
	KILL ^MyColor, ^MyNumber
	#Define ColorDay $ZDate($H,12)
	#If $$$ColorDay="Monday"
	SET ^MyColor = "Red"
	SET ^MyNumber = 1
	#ElseIf $$$ColorDay="Tuesday"
	SET ^MyColor = "Orange"
	SET ^MyNumber = 2
	#ElseIf $$$ColorDay="Wednesday"
	SET ^MyColor = "Yellow"
	SET ^MyNumber = 3
	#ElseIf $$$ColorDay="Thursday"
	SET ^MyColor = "Green"
	SET ^MyNumber = 4
	#ElseIf $$$ColorDay="Friday"
	SET ^MyColor = "Blue"
	SET ^MyNumber = 5
	#Else
	SET ^MyColor = "Purple"
	SET ^MyNumber = -1
	#EndIf
	WRITE ^MyColor, ", ", ^MyNumber
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).TestMIF()
Yellow, 3

此代碼將ColorDay宏的值設置爲編譯時的日期名稱。以#If 開頭的條件語句使用ColorDay的值來確定如何設置^MyColor變量的值。這段代碼有多個條件可以應用於ColorDay-週一之後的每個工作日都有一個條件;代碼使用#ElseIf指令檢查這些條件。失敗的情況是#Else指令後面的代碼。#EndIf關閉條件。

任意數量的空格可以分隔#if<expression>。但是,<expression>內不允許有空格。同一行<expression>後面的任何內容都被視爲註釋,不會進行解析。

注意:如果#if出現在方法代碼中,並且參數不是文字值0或1,編譯器將在子類中生成代碼(而不是調用父類中的方法)。若要避免生成此代碼,請測試值爲0或1的條件,這會使代碼更簡單並優化性能。

#IfDef

#IfDef預處理器指令標記條件代碼塊的開始,其中執行取決於已定義的宏。它的形式是:

#IfDef macro-name

其中宏名稱不帶任何前導“$$$”字符。同一行上宏名後面的任何內容都被視爲註釋,不會進行解析。

代碼的執行取決於已定義的宏。繼續執行,直到到達#Else指令或結束#EndIf指令。

#IfDef僅檢查是否定義了宏,而不檢查其值是什麼。因此,如果宏存在並且值爲0(零),#IfDef仍然執行條件代碼(因爲宏確實存在)。

此外,由於#IfDef只檢查宏的存在,因此只有一種替代情況(如果沒有定義宏),由#Else指令處理。#ElseIf指令不能與#IfDef一起使用。

例如,下面根據宏的存在提供了一個簡單的二進制開關:

/// d ##class(PHA.TEST.ObjectScript).Testifdef()
ClassMethod Testifdef()
{
	#Define Heads

	#IfDef Heads
		WRITE "定義了宏",!
	#Else
		WRITE "未定義宏",!
	#EndIf
}
DHC-APP>d ##class(PHA.TEST.ObjectScript).Testifdef()
定義了宏

#IfNDef

#IfNDef預處理器指令標記條件代碼塊的開始,其中執行依賴於未定義的宏。它的形式是:

#IfNDef macro-name

其中宏名稱不帶任何前導“$$$”字符。同一行上宏名後面的任何內容都被視爲註釋,不會進行解析。

代碼的執行取決於尚未定義宏。繼續執行,直到到達#Else指令或結束#EndIf指令。#ElseIf指令不能與#IfNDef一起使用。

注意:#IfNDef有一個備用名稱#IfUnDef。這兩個名字的行爲是一樣的。

例如,下面提供了一個基於未定義宏的簡單二進制開關:

/// d ##class(PHA.TEST.ObjectScript).TestIfNDef()
ClassMethod TestIfNDef()
{
	#Define Multicolor 256

	#IfNDef Multicolor
		SET NumberOfColors = 2
	#Else
		SET NumberOfColors = $$$Multicolor
	#EndIf
	
	WRITE "There are ",NumberOfColors," colors in use.",!
}

DHC-APP>d ##class(PHA.TEST.ObjectScript).TestIfNDef()
There are 256 colors in use.
 

#Import

#Import預處理器指令指定任何後續嵌入式SQL DML語句的模式搜索路徑。

#Import指定要搜索的一個或多個架構名稱,以提供非限定表、視圖或存儲過程名稱的架構名稱。可以指定單個架構名稱,也可以指定以逗號分隔的架構名稱列表。在當前命名空間中搜索架構。下面的示例顯示了這一點,該示例定位Employees.Person表:

#Import Customers,Employees,Sales
  &sql(SELECT Name,DOB INTO :n,:date FROM Person)
  WRITE "name: ",n," birthdate: ",date,!
  WRITE "SQLCODE=",SQLCODE

搜索#Import指令中指定的所有架構。Person表必須正好位於#Import中列出的一個架構中。因爲#Import需要在模式搜索路徑中進行匹配,所以不使用系統範圍的默認模式。

動態SQL使用%SchemaPath屬性提供架構搜索路徑來解析非限定名稱。

#Import#SQLCompile path都指定了一個或多個用於解析非限定表名的架構名稱。這兩個指令之間的一些區別如下:

  • #IMPORT檢測不明確的表名。#Import搜索所有指定的架構,檢測所有匹配項。#SQLCompile path按從左到右的順序搜索指定的架構列表,直到找到第一個匹配項。因此,#Import可以檢測到不明確的表名;#SQLCompile path不能。例如,#Import Customers,Employees,Sales必須在Customers、Employees和Sales模式中恰好找到一個Person的匹配項;如果它找到此表名的多個匹配項,則會出現SQLCODE-43錯誤:“表‘Person’在模式中不明確”。

  • #IMPORT不能採用系統範圍的默認值。如果#Import在其列出的任何模式中都找不到Person表,則會出現SQLCODE-30錯誤。如果#SQLCompile path在其列出的任何模式中都找不到Person表,它將檢查系統範圍的默認模式。

  • #Import指令是累加性的。如果有多個#Import指令,則所有指令中的架構必須精確解析爲一個匹配項。指定第二個#Import不會停用前一個#Import中指定的架構名稱列表。指定#SQLCompile path指令會覆蓋前面的#SQLCompile path指令中指定的路徑;#SQLCompile path不會覆蓋前面的#Import指令中指定的架構名稱。

Caché 忽略#import指令中不存在的架構名稱。Caché 忽略#IMPORT指令中的重複方案名稱。

如果表名已經限定,則#Import指令不適用。例如:

  #Import Voters
  #Import Bloggers
  &sql(SELECT Name,DOB INTO :n,:date FROM Sample.Person)
  WRITE "name: ",n," birthdate: ",date,!
  WRITE "SQLCODE=",SQLCODE

在本例中,Caché在示例模式中搜索Person表.

  • #Import適用於SQL DML語句。它可用於解析SQL SELECT查詢以及INSERT、UPDATE和DELETE操作的非限定表名和視圖名。#Import還可以用於解析SQL CALL語句中的非限定過程名稱。
  • #Import不適用於SQL DDL語句。它不能用於解析數據定義語句(如CREATE TABLE和其他CREATE、ALTER和DROP語句)中的非限定表、視圖和過程名稱。如果在創建、修改或刪除該項的定義時爲表、視圖或存儲過程指定了非限定名稱,則Caché將忽略#Import值並使用系統範圍的默認模式。

請比較#SQLCompile路徑預處理器指令。

#Include

#include預處理器指令加載包含預處理器指令的指定文件名。它的形式是:

#Include <filename>

其中,filename是包含文件的名稱,不包括.inc後綴。包含文件通常與調用它們的文件位於同一目錄中。它們的名稱區分大小寫。

要列出系統提供的所有#include文件名,請發出以下命令:

  ZWRITE ^rINC("%occInclude",0)
DHC-APP>  ZWRITE ^rINC("%occInclude",0)
^rINC("%occInclude",0)="64191,43009"
^rINC("%occInclude",0,0)=30
^rINC("%occInclude",0,1)="#include %occOptions"
^rINC("%occInclude",0,2)="#include %occConstant"
^rINC("%occInclude",0,3)="#include %occKeyword"
^rINC("%occInclude",0,4)="#include %occProcedure"
^rINC("%occInclude",0,5)="#include %occLocation"
^rINC("%occInclude",0,6)="#include %occReference"
^rINC("%occInclude",0,7)="#include %occCompiler"
^rINC("%occInclude",0,8)="#include %occReference2"
^rINC("%occInclude",0,9)="#include %occReferenceStorage"
^rINC("%occInclude",0,10)="#include %occXXX"
^rINC("%occInclude",0,11)="#include %occObject"
^rINC("%occInclude",0,12)="#include %occOID"
^rINC("%occInclude",0,13)="#include %occFlag"
^rINC("%occInclude",0,14)="#include %occQualifier"
^rINC("%occInclude",0,15)="#include %occEnvironment"
^rINC("%occInclude",0,16)="#include %occMessages"
^rINC("%occInclude",0,17)="#include %occStatus"
^rINC("%occInclude",0,18)="#include %occDiagnostics"
^rINC("%occInclude",0,19)="#include %occName"
^rINC("%occInclude",0,20)="#include %occClassname"
^rINC("%occInclude",0,21)="#include %occFunctions"
^rINC("%occInclude",0,22)="#include %occVersion"
^rINC("%occInclude",0,23)="#include %occRoutine"
^rINC("%occInclude",0,24)="#include %occJavaMetaDictionary"
^rINC("%occInclude",0,25)="#include %occDepend"
^rINC("%occInclude",0,26)="#include %occFile"
^rINC("%occInclude",0,27)="#include %sySecurity"
^rINC("%occInclude",0,28)="#include %syAudit"
^rINC("%occInclude",0,29)="#include %xmlDOM"
^rINC("%occInclude",0,30)=" "
^rINC("%occInclude",0,"SIZE")=628

要列出其中一個#include文件的內容,請指定所需的include文件。例如:

  ZWRITE ^rINC("%occStatus",0)
DHC-APP>  ZWRITE ^rINC("PHA.MOB.TEST.Macros",0)
^rINC("PHA.MOB.TEST.Macros",0)="65462,81298.178795"
^rINC("PHA.MOB.TEST.Macros",0,0)=36
^rINC("PHA.MOB.TEST.Macros",0,1)="#Define SelfString ""自定義"",!,""宏!"""
^rINC("PHA.MOB.TEST.Macros",0,2)=""
^rINC("PHA.MOB.TEST.Macros",0,3)="#Define sysDate +$h"
^rINC("PHA.MOB.TEST.Macros",0,4)=""
^rINC("PHA.MOB.TEST.Macros",0,5)="#Define sysTime $p($h,"","",2)"
^rINC("PHA.MOB.TEST.Macros",0,6)=""
^rINC("PHA.MOB.TEST.Macros",0,7)="#define zdh(%Date) ##class(websys.Conversions).DateHtmlToLogical(%Date)"
^rINC("PHA.MOB.TEST.Macros",0,8)="#define zd(%Date) ##class(websys.Conversions).DateLogicalToHtml(%Date)"
^rINC("PHA.MOB.TEST.Macros",0,9)=""
^rINC("PHA.MOB.TEST.Macros",0,10)="#Define PHA ""PHA"""
^rINC("PHA.MOB.TEST.Macros",0,11)="#Define IP ""IP"""
^rINC("PHA.MOB.TEST.Macros",0,12)="#Define OP ""OP"""
^rINC("PHA.MOB.TEST.Macros",0,13)="#Define DEC ""DEC"""
^rINC("PHA.MOB.TEST.Macros",0,14)=""

要列出在生成int例程時預處理的#include文件,請使用^例程global。請注意,這些#include指令不必在ObjectScript代碼中引用:

ZWRITE ^ROUTINE("myroutine",0,"INC")

注意:在存儲過程代碼中使用#include時,它的前面必須有冒號字符“”,例如:

CREATE PROCEDURE SPxx() Language OBJECTSCRIPT {
 :#Include %occConstant
     SET x=##Lit($$$NULLOREF)
} 
```java
當在類的開頭包含文件時,該指令不包括井號。因此,對於單個文件,它是:
```java
Include MyMacros

對於多個文件,它是:

Include (MyMacros, YourMacros)

例如,假設有一個包含宏的OS.inc頭文件:

 #Define Windows
 #Define UNIX
#Include OS
 
#IfDef Windows
  WRITE "The operating system is not case-sensitive.",!
#Else
  WRITE "The operating system is case-sensitive.",!
#EndIf

#NoShow

#noshow預處理器指令結束作爲包含文件一部分的註釋部分。它的形式是:

#NoShow

其中#noshow跟在#show指令之後。強烈建議每個#Show都有相應的#noshow,即使註釋部分繼續到文件末尾也是如此。有關示例,請參閱#Show的條目。

#Show

#Show 指令開始一個註釋部分,該部分是包含文件的一部分。默認情況下,包含文件中的註釋不會出現在調用代碼中。因此,#Show-#noshow括號外的include文件註釋不會出現在引用代碼中。

#Show

強烈建議每個#Show都有相應的#noshow,即使註釋部分繼續到文件末尾也是如此。

在下面的示例中,文件OS.inc(來自#include示例)包含以下注釋:

#Show
  // If compilation fails, check the file 
  // OS-errors.log for the statement "No valid OS."
#NoShow
  // Valid values for the operating system are 
  // Windows or UNIX (and are case-sensitive).

其中前兩行註釋(以“如果編譯失敗.”開頭)。出現在包含包含文件和後兩行註釋的代碼中(以“Valid Values.”開頭)。僅出現在包含文件本身中。

#SQLCompile Audit

SQLCompile Audit預處理器指令是一個布爾值,它指定是否審覈任何後續的嵌入式SQL語句。它的形式是:

#SQLCompile Audit=value

其中,Value處於啓用或禁用狀態。

要使此宏預處理器指令生效,必須啓用%SYSTEM/%SQL/EmbeddedStatement系統審覈事件。默認情況下,此係統審覈事件未啓用。

#SQLCompile Mode

#SQLCompile Mode預處理器指令指定任何後續嵌入式SQL語句的編譯模式。它的形式是:

#SQLCompile Mode=value
  • Embedded - 在運行前編譯ObjectScript代碼和嵌入式SQL代碼。這是默認設置。
  • Deferred - 編譯ObjectScript代碼,但將編譯嵌入式SQL代碼推遲到運行時。這能夠編譯包含引用編譯時尚不存在的表的SQL的例程。

注意:不應將#SQLCompile Mode=DEFERED%SYSTEM.SQL.SetCompileModeDeferred()方法混爲一談。

延遲模式和嵌入式模式語句在其他方面是相同的。延遲模式語句和嵌入式模式語句都是靜態的。也就是說,它們不能在運行時動態組裝並提交以供處理。如果需要動態代碼,請使用動態SQL。

延遲模式可用於INSERT、UPDATE和DELETE操作,以及返回單行數據的SELECT語句。延遲SQL不能用於聲明遊標和提取數據行的多行SELECT語句;嘗試這樣做會生成#5663編譯錯誤。與嵌入式SQL一樣,延遲SQL不檢查權限。

在延遲模式下,SQL可以引用編譯時尚不存在的表、用戶定義函數和其他實體。

如果嵌入式SQL語句包含無效的SQL語句(例如,SQL語法錯誤),宏預處理器將生成代碼\“**SQL語句無法編譯**\”,並繼續編譯ObjectScript代碼。因此,當使用包含無效嵌入式SQL的方法編譯類時,會報告SQL錯誤,但會生成該方法。運行此方法時,無效的SQL會導致錯誤。

嵌入式SQL提供最佳的SQL性能,應儘可能使用。延遲SQL通常比動態SQL更有效。將Transact-SQL或Informix SPL存儲過程轉換爲Caché時,可能需要延遲SQL。存儲過程的Caché轉換工具支持此功能。

#SQLCompile Path

#SQLCompile path預處理器指令指定任何後續嵌入式SQL DML語句的模式搜索路徑。它的形式是:

#SQLCompile Path=schema1[,schema2[,...]]

其中schema是用於在當前名稱空間中查找非限定SQL表名、視圖名或過程名的模式名。可以指定一個架構名稱或以逗號分隔的架構名稱列表。將按指定的順序搜索架構。搜索結束,並在出現第一個匹配時執行DML操作。如果沒有模式包含匹配項,則搜索系統範圍內的默認模式。

因爲架構是按指定的順序搜索的,所以不會檢測到有歧義的表名。#Import預處理程序指令還從模式名列表中向非限定的SQL表、視圖或過程名提供模式名;#Import會檢測不明確的名稱。

Caché忽略#SQLCompile path指令中不存在的架構名稱。Caché忽略#SQLCompile path`指令中的重複模式名稱。

  • #SQLCompile path應用於SQL DML語句。它可用於解析SQL SELECT查詢以及INSERT、UPDATE和DELETE操作的非限定表名和視圖名。#SQLCompile path還可用於解析SQL CALL語句中的非限定過程名稱。
  • #SQLCompile path不適用於SQL DDL語句。它不能用於解析數據定義語句(如CREATE TABLE和其他CREATE、ALTER和DROP語句)中的非限定表、視圖和過程名稱。如果在創建、修改或刪除該項的定義時爲表、視圖或存儲過程指定了非限定名稱,則Caché將忽略#SQLCompile path值並使用系統範圍的默認模式。

動態SQL使用%SchemaPath屬性提供架構搜索路徑來解析非限定名稱。

以下示例將非限定表名Person解析爲Sample.Person表。它首先搜索Cinema模式(不包含名爲Person的表),然後搜索示例模式:

#SQLCompile Path=Cinema,Sample
  &sql(SELECT Name,Age
       INTO :a,:b
       FROM Person)
  WRITE "Name is: ",a,!
  WRITE "Age is: ",b

除了將架構名稱指定爲搜索路徑項外,還可以指定以下關鍵字:

  • CURRENT_PATH:指定在前面的#SQLCompile path預處理器指令中定義的當前模式搜索路徑。
    這通常用於將架構添加到現有架構搜索路徑的開頭或結尾,如下例所示:
#SQLCompile Path=schema_A,schema_B,schema_C
#SQLCompile Path=CURRENT_PATH,schema_D
  • CURRENT_SCHEMA:指定當前模式容器類名。如果在類方法中定義了#SQLCompile path,則CURRENT_SCHEMA是映射到當前類包的模式。如果在.Mac例程中定義了#SQLCompile path,則CURRENT_SCHEMA爲配置默認模式。

例如,如果在類User.MyClass中定義一個類方法,指定#SQLCompile path=CURRENT_SCHEMA,則默認情況下,CURRENT_SCHEMA將解析爲SQLUser,因爲SQLUser是用戶包的默認架構名稱。當在不同的包中有一個父類和子類,並且在父類中定義了一個具有未限定表名的SQL查詢的方法時,這很有用。使用CURRENT_SCHEMA,可以將表名解析爲父類中的父類模式和子類中的子類模式。如果沒有設置CURRENT_SCHEMA搜索路徑,表名將解析爲兩個類中的父類模式。

如果在觸發器中使用#SQLCompile PATH=CURRENT_SCHEMA,則使用架構容器類名。例如,如果類pkg1.myclass具有指定#SQLCompile path=current_schema的觸發器,並且類pkg2.myclass擴展了pkg1.myclass,則在編譯pkg2.myclass時,caché將觸發器中的SQL語句中的非限定表名解析爲包pkg2的模式。

  • DEFAULT_SCHEMA指定系統範圍的默認模式。此關鍵字使能夠在搜索其他列出的架構之前,將系統範圍內的默認架構作爲架構搜索路徑中的一個項目進行搜索。如果搜索路徑中指定的所有架構都不匹配,則在搜索架構搜索路徑後,始終搜索系統範圍內的默認架構。

如果指定架構搜索路徑,則SQL查詢處理器在嘗試解析非限定名稱時首先使用架構搜索路徑。如果它沒有找到指定的表或過程,則會查找通過#Import(如果指定)提供的模式或配置的系統範圍默認模式。如果在這些位置中沒有找到指定表,則會生成SQLCODE-30錯誤。

#SQLCompile path可以與#SQLCompile mode值嵌入或延遲一起使用。

架構搜索路徑的範圍是在其中定義它的例程或方法。如果在類方法中指定了架構路徑,則它僅適用於該類方法,而不適用於類中的其他方法。如果它是在.Mac例程中指定的,它將從例程中的該點向前應用,直到找到另一個#SQLCompile path指令,或者到達例程的末尾。

架構是爲當前命名空間定義的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章