OUTQをPDFへコピーする
「Copy from OUTQ(CPYOUTQ)」(OUTQからコピー)というCLコマンドを私が作成したのは、ずいぶん前のことになります。このコマンドは、1つのOUTQから別のOUTQまたはIFSのいずれかへ、PDFまたはテキスト ファイルとしてスプール ファイルを選択的にコピーすることを可能にするものでした。私の顧客は、毎月のスプール ファイルのアーカイブ処理や出力の再配分処理に、このコマンドをいつも使用しています。読者の皆さんのシステムでもご使用かもしれません。
IBM OS/400(現、IBM i)向けのいわゆる「Openness API」の数少ない元々の提唱者の1人として、私はすぐにシステムAPIを採用し、数十年にわたって幅広く使用してきました。1つの課題としては、他のユーザーにそれらのAPIないしは私のAPIラッパーを使用してもらうことでした。APIの使用は、今日でも多くの抵抗に遭います。今日では、ほぼどのようなプログラムまたはAPIも実行できるSQL関数を作成する機能があるため、/COPYステートメントおよびバインディング ディレクトリーを組み込んで、マッチするシグニチャーで別個の*SRVPGMをインストールする必要なく、我らが愛しのIBM i APIとインターフェースするSQL関数を作成してきました。「SQLサービス」は、同じことを効率的に行います。IBMは、より効率的でより低いレベルのインターフェースにアクセスできますが、単にRPG内で「VALUES iQuery.SYSVAL('QCCSID') INTO :sysCCSID」とコーディングするほうが、はるかに簡単でよりエラーが少ないというメリットがあると思います。
今日、私がこのコマンドを書くとしたら、SQLを使用してそれを行うでしょう。CLコマンドは使用しやすいですが、ますます多くの処理が、直接RPG IV内またはSQLスクリプト環境の1つから、SQLを使用して行われるようになっています。レガシーなCLコマンドと対比して、SQLおよびSQLサービスを使用する際に、ほぼ唯一、ないことが本当に残念に思えるのは、コマンド プロンプターです。
CPYOUTQ CLコマンドは、QGYOLSPL「リスト」APIを使用して、OUTQにあるスプール ファイル名のリストを生成します。今日、それを行うとしたら、QSYS2ライブラリーにあるSQL「サービス」、OUTPUT_QUEUE_ENTRIES()表関数(UDTF)を使用するでしょう。
たとえば、このUDTFを使用して、ライブラリーQUSRSYS内のCOZZIという名前のOUTQに対するスプール ファイルのリストを生成するには、次のようにコーディングします。
SELECT SPOOLED_FILE_NAME, JOB_NAME, FILE_NUMBER
FROM TABLE (
QSYS2.OUTPUT_QUEUE_ENTRIES(OUTQ_LIB => 'QUSRSYS',
OUTQ_NAME => 'COZZI')
) outq;
UDTFによって返される、使用することができるフィールドは、他にも多数ありますが、PDFへのコピーでは、スプール ファイル名、ジョブ名、およびスプール ファイル番号のみが必要です。このSQLステートメントを実行すると、次のようなリストが表示されます。
OUTPUT_QUEUE_ENTRIES UDTFは、定位置構文または名前付きパラメーター構文のいずれかを使用して、ライブラリーおよび出力待ち行列名を受け取ります(上述)。また、ユーザー作成関数はこの構文をサポートすることになるため、CLコマンドで行うのと同様に、パラメーターに明確に名前を付けることが重要です。
PDFへの変換
スプール ファイル名のリストを生成する方法ができたら、次に必要になるのは、それらのスプール ファイルをPDFに変換する方法です。それを行う最も簡単な方法は、システムに組み込まれているCPYSPLF(スプール・ファイル・コピー)CLコマンドを使用する方法です。まずは、上のリストのスプール ファイルの1つを使用して、シンプルなPDFへのコピー ステートメントをスタブアウトしましょう。
CPYSPLF FILE(RUNIQRY)
JOB(777933/COZZI/MACPR0)
SPLNBR(77)
TOFILE(*TOSTMF) WSCST(*PDF)
STMFOPT(*REPLACE)
TOSTMF('/home/cozzi/runiqry.pdf')
このCPYSPLFコマンドを前述のOUTPUT_QUEUE_ENTRIES UDTFに統合するために、ストアード プロシージャーを作成することができます。ストアード プロシージャーは、呼び出し可能なオブジェクトにコンパイルされるSQLスクリプトの、SQL風の言い方です。
CPYOUTQ2PDFストアード プロシージャー
ストアード プロシージャー という用語は、初めて耳にしたときから違和感を感じてきました。ともかく、ファンクションは ストアード ファンクションとは呼ばれません。 ユーザー定義プロシージャー はないため、普及してきたように思われる、よりシンプルな SQLプロシージャー の命名方式を使用することとします。では、以下に、CPYOUTQ2PDF SQLプロシージャーのコードを示します。このプロシージャーは、最大で5つのパラメーターを受け付けます。 PDF_DIRパラメーターは、PDFファイルが保存されるIFSフォルダー/ディレクトリーを識別します。また、このディレクトリー内のサブフォルダーにPDFイメージを入れるために、このディレクトリーに出力待ち行列名も追加することに注意してください。直接フォルダー名を指定したい場合は、このプロシージャーのコードの set PDF ステートメントを変更して、連結プロセスでOUTQ_NAMEを使用しないようにします。そうでない場合は、サブディレクトリーをすでに作成していることを確認するか、MKDIRまたはMDコマンドで、QCMDEXCの2番目の呼び出しを使用してプロシージャー内でそのサブディレクトリーを作成します。
このプロシージャーを呼び出すには、SQLを開始するか、SQLスクリプト プロセッサーを実行して(STRSQL、ACSの「SQL スクリプトの実行」、またはRUNiQRY)、次のようなコードを実行します。
call cpyOutQ2PDF( OUTQ_NAME => 'COZZI', OUTQ_LIB => 'QUSRSYS');
これで、現在、COZZI出力待ち行列にあるすべての既存のスプール ファイルがPDFファイルとしてIFSへコピーされます。
JOB_NAMEまたはSPOOLED_FILEパラメーターを指定して、コピーするジョブおよびスプール ファイルを制御できます。指定しない場合は、OUTQにあるすべてのスプール ファイル エントリーがコピーされます。SPOOLED_FILEは総称名を受け付けるため、SQL総称構文の'ORD%'とともにSPOOLED_FILE => 'ORD*'がサポートされます。
また、既存のPDFファイルが検出された場合にそれを置き換えるかどうかを制御するのに役立つREPLACE(PDFを置き換える)パラメーターもあります。REPLACEのデフォルトはYESです。
自分のソフトウェア製品の「リリース」ビルド(暫定ビルドではなく)を行うときは、念のために、疑似バックアップとしてコンパイラー リストをアーカイブするようにしています。この新たなCPYOUTQ2PDFプロシージャーを使用してそれを行うには、以下を実行します。
call cpyOutQ2PDF( OUTQ_NAME=>'IQUERY', OUTQ_LIB=>'QUSRSYS',
SPOOLED_FILE => 'IQRY*', REPLACE=>'yes');
これで、IQUERY出力待ち行列からIFSへ、すべてのスプール ファイルがPDFファイルとしてコピーされます。コピーされるのは、名前が「IQRY」で始まるスプール ファイルのみです(その他のスプール ファイルはスキップされます)。このプロシージャーは総称記号としてアスタリスクまたはパーセント記号を受け付けるため、総称スプール ファイル名でSQLおよびCL構文の両方がサポートされることに注目してください。
1つ機能強化を行うとすれば、出力PDFディレクトリーを作成することもできます(存在しない場合)。たとえば、上述のcallを実行するとしたら、デフォルトの出力ディレクトリーは'/home/cozzi/iquery'となるでしょうが、そのディレクトリーが存在しないとしたらどうでしょうか。QSYS2.QCMDEXCプロシージャーを使用して、FOR LOOPが始まる前に、CPYOUTQ2PDFプロシージャーに次のようなステートメントを組み込むことができます。
call qsys2.qcmdexc('MD ' || pdf_dir);
注意事項がいくつかあります。ストアード プロシージャーはSQLまたは組み込みSQL内からのみ呼び出されるため、このルーチンをコマンド入力画面またはCLから直接呼び出すことはできません。CL環境で実行するには、SQLステートメントとしてそれを実行するCLコマンドでラッピングする必要があります。これを行うには、RUNSQL、RUNSQLSTM、およびRUNiQRYコマンドなど、いくつかのオプションが利用可能です。RUNSQLを使用してこのプロシージャーを呼び出すには、CLプログラムまたはコマンド入力画面内から以下のようにします。
RUNSQL SQL('CALL cpyoutq2pdf(OUTQ_NAME => ''IQUERY'', OUTQ_LIB => ''QUSRSYS'') ')
また、次のように、RPG IVにこのストアード プロシージャーを組み込むこともでき、そうするには、前にEXEC SQLを付けるだけです。
EXEC SQL CALL cpyoutq2pdf(OUTQ_NAME => :MYOUTQ, OUTQ_LIB => :MYOUTQLIB);
この記事の最後に、CPYOUTQ2PDFのソース コードを記載しています。コンパイルするには次のようにします。
- ソースファイル メンバーへコピーします。
- RUNSQLSTMを使用してそのメンバーをコンパイルします。
ヒント
コードが、ソースファイル メンバーのソース行の79桁目を超えないようにしてください。RUNSQLSTMは、79桁目を超える位置のテキストを無視します。
RUNSQLSTMコマンドでOUTPUT(*PRINT)を指定して、コンパイラー リストを出力することもできます。
「CREATE or REPLACE」ステートメントでCPYOUTQ2PDF名を修飾するか、または、現行ライブラリーを、プロシージャーが作成されるようにしたいライブラリーへ変更します。
このプロシージャーは、*SRVPGMとして作成されます。
このプロシージャーについて、ご意見・ご感想をお聞かせください。また、このようなものについて他にもご覧になりたいでしょうか。Twitterは@SQLiQuery、メールは cozzi@rpgiv.comまで、お知らせいただければと思います。
CREATE or REPLACE PROCEDURE CPYOUTQ2PDF(
in OUTQ_LIB varchar(10),
in OUTQ_NAME varchar(10),
in JOB_NAME varchar(28) default '*ALL',
in PDF_DIR varchar(640) default '/home',
in REPLACE varchar(10) default '*YES'
)
LANGUAGE SQL
SPECIFIC CPYOUTQ2PDF
A1: BEGIN ATOMIC
DECLARE STMFOPT varchar(10) not null default '*REPLACE';
DECLARE PDF varchar(2048) not null default '';
DECLARE JOB varchar(28) not null default '*ALL';
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION
begin end; -- Do nothing (i.e., "MONMSG CPF0000")
if (CPYOUTQ2PDF.REPLACE is not null and
CPYOUTQ2PDF.REPLACE <> '') then
set STMFOPT = ltrim(upper(CPYOUTQ2PDF.REPLACE),'* ');
if (STMFOPT = 'YES' or STMFOPT = 'REPLACE') then
set stmfOpt = '*REPLACE';
else
set stmfOpt = '*NONE';
end if;
end if;
set job = upper(CPYOUTQ2PDF.job_Name);
forEach: FOR V1 AS C1 CURSOR FOR
SELECT SPOOLED_FILE_NAME, JOB_NAME, FILE_NUMBER
FROM TABLE (QSYS2.OUTPUT_QUEUE_ENTRIES(
OUTQ_LIB => OUTQ_LIB,
OUTQ_NAME => OUTQ_NAME)
) outq
WHERE JOB_NAME = CASE WHEN JOB = '*ALL'
then outq.JOB_NAME
else JOB
end
DO
set PDF = rtrim(pdf_Dir,'/ ') || '/' || user || '/' ||
OUTQ_NAME || '/' || -- OUTPUT QUEUE subdir
FILE_NUMBER || '_' || SPOOLED_FILE_NAME || '.pdf';
CALL QSYS2.QCMDEXC('CPYSPLF FILE(' || SPOOLED_FILE_NAME || ') ' ||
' TOFILE(*TOSTMF) WSCST(*PDF) ' ||
' STMFOPT(' || STMFOPT || ') ' ||
' JOB(' || JOB_NAME || ') ' ||
' SPLNBR(' || FILE_NUMBER || ') ' ||
' TOSTMF(''' || PDF || ''')');
end for forEach;
END A1;