文章目錄
第九章 瘋狂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
指令,或者到達例程的末尾。
架構是爲當前命名空間定義的。