【IRIS/Cache】続・マクロの料理法!

本記事は、前回に引き続きマクロについて解説致します。

※この記事は下記の方向けになります。
  • マクロを使いこなしたい方
  • マクロのコマンドを確認したい方

システム・プリプロセッサ・コマンド

マクロで使用するコマンドを一覧にしました。

サンプルも添えているので、使い方等に迷ったら参考にして下さい。

メイン

#defin

マクロの基本
下記記事で紹介したので割愛します。

#def1Arg

引数を1つだけ設定が可能で、引数に「,」を含ませることが可能

ClassMethod testSample()
{
	#def1Arg TEST1(%arg) w !,%arg
	#def1arg clsMth(%subs) ##expression("##class("_$li(%literalargs,1)_")."_$li(%literalargs,2)_"("_$lts($li(%literalargs,3,*),",")_")")
	$$$TEST1("a","b","c")
	w $$$clsMth("developer.macro.Sample","WriteCmnt",1,2)
}

コンパイル結果

testMacro() methodimpl {
	w !,"a","b","c"
	w ##class("developer.macro.Sample")."WriteCmnt"(1,2) }

便利系

#include

インクルードファイルをロードする。

使い方は「Include」と変わらないです。

#dim

「#dim」を使い変数の型定義を行うと、関数やパラメータがインテリセンスとして表示されます。

ClassMethod testSample()
{
	#dim e as %Exception.AbstractException
	try{
	}catch e{
		w e.DisplayString()
	}
}

#execute

コンパイル時に ObjectScript の行を実行する。

下記サンプルは、コンパイル日時をグローバル「^sample.compile」に設定する。

ClassMethod testSample()
{
	#Execute s ^sample.compile = $zdt($h,3,1)
}

補助的なマクロ

##continue

マクロを複数行に渡って記述する際に利用します。

#define SQLGO(%rset, %query, %param) ##continue
	s stmt = ##class(%SQL.Statement).%New(1) ##continue
	s %rset = stmt.%ExecDirect(, %query, data...) ##continue

##expression

コンパイル時に式を評価します。

ClassMethod testSample()
{
	#define dSec1 60*60*24
	#define dSec2 ##Expression(60*60*24)
	w $$$dSec1
	w $$$dSec2
	
	#define now ##Expression("""一日の秒数"_$$$dSec2_"秒""")
	w !,$$$now
}

コンパイル結果

testSample() methodimpl {
	w 60*60*24
	w 86400
	w !,"一日の秒数86400秒" }

##function

コンパイル時に関数を評価します。

ClassMethod testSample()
{
	#define dSec ##Function(##class(developer.macro.Sample).retVal())
	w $$$dSec
}
ClassMethod retVal()
{
	q 60*60*24
}

コンパイル結果

testSample() methodimpl {
	w 86400 }

##unique

マクロ定義内で、一意の新規ローカル変数を作成する。
##Unique(new)と##Unique(old)は対となっていて、「new」で格納した値を「old」で返す。

set ##Unique(new) = [値]
set [変数] = ##Unique(old)
ClassMethod testSample(int As %Integer)
{
	#define SAMPLE(%from, %to) s ##Unique(new) = %from, to = ##Unique(old)
	#define SET(%arg) s ##Unique(new) = %arg * 10
	#define GET ##Unique(old)
	
	$$$SAMPLE(int, to)
	w to,!
	
	$$$SET(10)
	w $$$GET
}

コンパイル結果

testSample(int) methodimpl {
	s %mmmu1 = int, to = %mmmu1
	w to,!
	s %mmmu2 = 10 * 10
	w %mmmu2 }

コンパイル時の判定とマクロの削除

#If, #Else, #ElseIf, #EndIf

「#IF, #Else, #ElseIf」で引数の条件を判定し、trueの場合に該当のブロックをコンパイルします。

下記サンプルは、コンパイル時の「$$$SAI」の値によって、変数「val」の値が確定し出力します。
変数「val」の値は、コンパイルするまで変更されません。

ClassMethod testSample()
{
	#define SAI $r(6)+1 ##;サイコロの目
	#If $$$SAI=1 
		s val = "1の目"
	#ElseIf $$$SAI=2
		s val = "2の目"
	#ElseIf $$$SAI=3
		s val = "3の目"
	#ElseIf $$$SAI=4
		s val = "4の目"
	#ElseIf $$$SAI=5
		s val = "5の目"
	#Else
		s val = "6の目"
	#EndIf
		w !,val
}

コンパイル結果

testSample() methodimpl {
		s val = "5の目"
		w !,val }

#IfDef, #IfNDef

コンパイル時、マクロが存在している/していないの判定で、実行されるブロックが変わります。

「#IfDef」マクロが存在している場合trueとなる
「#IfNDef」マクロが存在していない場合trueとなる

ClassMethod testSample()
{
	#define Exi
	
	w !,"マクロがあるのか?"
	
	#IfDef Exi 
		w "ある!"
	#Else
		w "ない!"
	#EndIf
}

コンパイル結果

testSample() methodimpl {
	w !,"マクロがあるのか?"
		w "ある!"
}
ClassMethod testSample()
{
	w !,"マクロがあるのか?"
	
	#IfDef Exi 
		w "ある!"
	#Else
		w "ない!"
	#EndIf
}

コンパイル結果

testSample() methodimpl {
	w !,"マクロがあるのか?"
		w "ない!"
}

#undef

定義されているマクロの定義を削除する。

ClassMethod testMacro()
{
	#define TEST w !,"sample"
	#undef TEST
	#ifNDef TEST
		s txt = "未定義"
	#else
		s txt = "定義済"
	#endif
		w txt
}

コンパイル後

testMacro() methodimpl {
		s txt = "未定義"
		w txt }

コメント系

#;

intファイルに反映されないコメントを作成します。

ClassMethod testSample()
{
	#; 表示されない
	w !,表示される
}

コンパイル結果

testSample() methodimpl {
	w !,表示される }

##;

「##;」以降の文字列は、intファイルに反映されない。

ClassMethod testSample()
{
	w !,表示される ##; 表示されない
}

コンパイル結果

testSample() methodimpl {
	w !,表示される }

#show, #noshow

#Show ~ #NoShowまでの間のコメントは、参照元に表示される。

インクルードファイルに下記コメントを記載すると・・・

	// 範囲外のコメント前
#Show
	// 範囲内のコメント
	// 範囲内改行
#noShow
	// 範囲外のコメント後
	// 改行!

INTファイルに下記文言が表示されます。

	// 範囲内のコメント
	// 範囲内改行

INTファイルに出力されるのか。
普段、あまり見ないファイルに出力されてもなぁ・・・勿体ない。

文字列操作系

##quote

引数に対し、引用符を付けて返す。
引数の中に「”」があると、エスケープして返す。

ClassMethod testSample()
{
	w !,##Quote(藤原道長)
	w !,##Quote(娘の名前は"彰子"です。)
}

コンパイル結果

testSample() methodimpl {
	w !,"藤原道長"
	w !,"娘の名前は""彰子""です。"
}

##quoteExp

コンパイル時に評価される式を引数として取る。

ClassMethod testMacro()
{
	s ^sample("test","data","sample")="でます"
	
	#def1arg gbl(%subs) ^sample("test"##expression($s(%literalargs'=$lb(""):","_$LTS(%literalargs,","),1:"")))
	#def1arg gblStr(%subs) ##quoteExp($$$gbl(%subs))

	w $$$gbl("data","sample"),!
	w @$$$gblStr("data","sample")
}

コンパイル結果

testMacro() methodimpl {
	s ^sample("test","data","sample")="でます"
	w ^sample("test","data","sample"),!
	w @"^sample(""test"",""data"",""sample"")" }

##beginquote ~ ##EndQuote

引用符を付与する。「”」があるとエスケープする。
公式ドキュメントのサンプルにある「##EndQuote」は「##endquote」に書き換えると動作します。

ClassMethod testMacro()
{
	s a=##beginquote 紫"式"部 ##endquote	
	s b=##beginquote SET def="SQL code-generation" &SQL(SELECT Name ##endquote
}

コンパイル結果

testMacro() methodimpl {
	s a=" 紫""式""部 "	
	s b=" SET def=""SQL code-generation"" &SQL(SELECT Name "
}

・・・前後に半角スペースが入りますね。
恐らく、##beginquoteと##endquoteの間の半角スペースです。

なので、半角スペースを削ると、赤い波線が表示されますが・・・

testMacro() methodimpl {
	s a="紫""式""部"	
	s b=" SET def=""SQL code-generation"" &SQL(SELECT Name "
}

コンパイル後のINTファイルから、半角スペースが消えました。

うん。
使いづらい。

括弧で括ると、赤い波線が消えますが・・・

testMacro() methodimpl {
	s a="(紫""式""部)"	
	s b=" SET def=""SQL code-generation"" &SQL(SELECT Name "
}

コンパイルすると当然括弧が付きます。
「xecute」実行用として、関数を文字列で構築する時に使えるかも?

##stripq

引用符を削除してその引数を返す。「##quote」とは逆の動き

ClassMethod testMacro()
{
	s str = "サンプル"
	#define TEST(%arg) ##stripq(%arg)
	w $$$TEST("str")
}

コンパイル後

testMacro() methodimpl {
	s str = "サンプル"
	w str }

SQL関連

#SQL

実行時にSQLを実行します。「&sql()」と同じ。

#Import

1つ以上のスキーマ名を指定する。
 ※全てののスキーマが、ネームスペースに存在している事

クエリが短くなるので、可読性が上がるかも?

ClassMethod testSample()
{
	#Import developer_data
	&sql(select count(*) INTO :cnt2 FROM Patient2)	
	w cnt2,!
}

複数指定した場合、同じテーブル名が複数存在するとエラーとなる。
 →指定した全スキーマを検索するため

ClassMethod testSample2()
{
	#Import developer_data, developer_copy
	&sql(
		select ID,カナ氏名,性別,漢字氏名,生年月日
		 into :ID, :kana, :sex, :name, :birthday 
		from Patient2
		where patientId = '11730000001'
	)
	w %msg,!
}

【実行結果】
スキーマ内でテーブル ‘PATIENT2’ があいまいです: DEVELOPER_MACRO,DEVELOPER_COPY, DEVELOPER_DATA compiling embedded cached query from developer.macro.Sample.CLS

#sqlcompile (audit, mode, path, select)

先ずはサンプル

ClassMethod testSample()
{
	#SQLCompile Audit=ON
	#SQLCompile Mode=Embedded
	#SQLCompile Path=developer_data
	#SQLCompile Select=ODBC
	&sql(
		select ID,カナ氏名,性別,漢字氏名,生年月日
		 into :ID, :kana, :sex, :name, :birthday 
		from Patient2
		where patientId = '11730000001'
	
	)
	w birthday,!
}
#sqlcompile path

#Importとほぼ同じ動作。

ただし、複数指定した場合、左から検索していき最初にHITしたスキーマを適用するので、「#Import」と異なり、複数テーブルが検索されても問題ない。

#sqlcompile select

データ形式モードを指定する。

説明
Display表示モード
Logical 論理モード
ODBC ODBCモード
Runtime$SYSTEM.SQL.Util.SetOptionを使用して、値の変換が行える。
TextDisplayと同じ。表示モード
FDBMS埋め込み SQLが FDBMS と同じデータをフォーマットできるようにする

Runtimeのサンプル

ClassMethod testSample3()
{
	#Import developer_data
	#SQLCompile select=Runtime
	s ptnId = "11730000001"
	&sql(select 生年月日 into :birthday from Patient2 where patientId=:ptnId)
	w birthday,!
	d $SYSTEM.SQL.Util.SetOption("SelectMode",1) // 1:ODBC
	&sql(select 生年月日 into :birthday from Patient2 where patientId=:ptnId)
	w birthday
}
#sqlcompile mode

非推奨の為割愛しまーす。

#sqlcompile audit

後続の埋め込み SQL 文を監査データに登録するか、制御を行う。

スタジオで記述すると赤い波線が出ちゃいますが、動作に問題はありません。

コンパイル時に謎のエラーが出るときがありますが・・・問題無いようです。

さて、気を取り直して。
この機能「audit=ON」は、これだでは終わりません。

監査「%System/%SQL/EmbeddedStatement」をONにする

管理ポータルを起動し、[システム管理] > [セキュリティ] > [監査] > [システムイベントを構成]をクリックし「システム監査イベント」画面を表示します。

項目「%System/%SQL/EmbeddedStatement」の「状態変更」をクリックし、「Enabled=はい」に変更します。

監査データベースの閲覧

※先ずは、先ほどのSQLをターミナル等から実行します。

管理ポータルの[システム管理] > [セキュリティ] > [監査] > [監査データベースの閲覧]をクリックし「監査データベースの閲覧」画面を表示します。

先ほど実行したSQLが、監査レコードとして登録されています。

右端の「詳細」をクリックすると、SQLの実行結果が登録されます。

システムに対し、クリティカルなSQLに関しては、念のため監査データを作成しておくことで、安全対策を行うことができます。