2023年4月11日火曜日

JSONのファイルから32KBを超えるSASプログラムを抽出する。

LIBNAMEのJSONエンジンの欠点は、32KBを超える長さのデータを抽出できない点です。できないものは仕方ないので、32KBを超えてもSASプログラムを抽出するコードを書きました。 字句解析の状態遷移を考えて "code": の値を抽出してバックスラッシュのエンコードを解いてファイルの保存します。入力のJSONファイルは整形されていないコンパクトや書式の前提です。昔、Lex, Yaccを使っていた記憶を掘り起こした。

/* Code extraction from JSON file of job definition */
%macro extractJobCode(infile=, outfile=, debug=0);
	%local/readonly DQ='22'x;
	%local/readonly BSL='5c'x;
	%local/readonly BS='08'x;
	%local/readonly FF='0c'x;
	%local/readonly NL='0a'x;
	%local/readonly CR='0d'x;
	%local/readonly TB='09'x;
	%local/readonly SP='20'x;

	data _NULL_;
		attrib filein length=8 label='input file id';
		attrib fileid length=8 label='output file id';
		attrib stat length=8 label='State of lexical analysis';
		attrib token length=$256 label='Token';
		attrib ucode length=$4 label='Unicode';
		attrib count length=8;

		/* Open input/output files */
		filein=fopen("&infile" , 'I', 1, 'B');
		fileid=fopen("&outfile", 'O', 1, 'B');
		count=0;
		stat=0;
		rec=&SP;

		do while(fread(filein)=0);
			rc=fget(filein, rec, 1);

			%if &debug=1 %then
				%do;

					if count < 512 then
						put stat=rec=token=;
					count=count + 1;
				%end;

			if stat eq 0 and rec eq &DQ then
				stat=1;
			else if stat eq 1 then
				do;

					if rec eq &DQ then
						do;

							/* Double quotes */
							if token eq 'code' then
								stat=2;
							else
								stat=0;
							token='';
						end;
					else
						do;
							token=catt(token, rec);
							stat=1;
						end;
				end;
			else if stat eq 2 then
				do;

					if rec in (&SP, &TB, &NL) then
						stat=2;
					else if rec eq ':' then
						stat=3;
					else
						stat=0;
				end;
			else if stat eq 3 then
				do;

					if rec in (&SP, &TB, &NL) then
						stat=3;
					else if rec eq &DQ then
						stat=4;
					else
						stat=0;
				end;
			else if stat eq 4 then
				do;

					if rec eq &DQ then
						stat=0;
					else if rec eq &BSL then
						stat=5;
					else
						do;
							rc=fput(fileid, rec);
							rc=fwrite(fileid);
						end;
				end;
			else if stat eq 5 then
				do;

					/* Handling of backslash-escaped characters */
					if rec eq 'b' then
						do;
							rc=fput(fileid, &BS);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq &DQ then
						do;
							rc=fput(fileid, &DQ);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq &BSL then
						do;
							rc=fput(fileid, &BSL);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq 'f' then
						do;
							rc=fput(fileid, &FF);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq 'n' then
						do;
							rc=fput(fileid, &NL);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq 'r' then
						do;
							rc=fput(fileid, &CR);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq 't' then
						do;
							rc=fput(fileid, &TB);
							rc=fwrite(fileid);
							stat=4;
						end;
					else if rec eq 'u' then
						do;
							ucode='';
							do i=1 to 4;
								rc=fread(filein);
								rc=fget(filein, rec, 1);
								ucode=catt(ucode, rec);
							end;
							if ucode eq '0026' then
								do;
									rc=fput(fileid, '&');
									rc=fwrite(fileid);
								end;
							else if ucode eq '003c' then
								do;
									rc=fput(fileid, '<');
									rc=fwrite(fileid);
								end;
							else if ucode eq '003e' then
								do;
									rc=fput(fileid, '>');
									rc=fwrite(fileid);
								end;
							else
								do;
									rc=fput(fileid, &BSL);
									rc=fput(fileid, 'u');
									rc=fput(fileid, ucode);
									rc=fwrite(fileid);
								end;
							stat=4;
						end;
				end;
		end;

		/* Close input/output files */
		rc=fclose(filein);
		rc=fclose(fileid);
	run;

%mend;

2022年12月19日月曜日

遅い PROC SQLのデバッグ

PROC SQLに 昔からあるオプション、機能だが使うのは初めて _METHOD _TREEを試しました。処理時間に問題ありということで、PROC SQL _METHOD _TREE; という具合にオプションを足すと、クエリを評価する順序や評価のツリー構造をログに出力してくれます。

これ DATAステップでインデクス作ったのに使っていないとかログを見ると判ります。性能調整のヒントに FULLSTIMERと一緒に合わせて使えます。

2022年11月1日火曜日

ユニークなライブラリ参照名、ファイル参照名を定義するマクロ

マクロを書いていて地味に困るのがテンポラリのファイル参照名、ライブラリ参照名を定義することです。ユニークでぶつからない名前を定義するために、小さいコードを書きました。参照するファイルが、SASファイルでも動くようにアンダースコアで始まるようにしています。filename関数でテンポラリの名前を作ると #LN00058 のような名前になります。これだと正しくアサインできないときがあるので、アンダースコア始まりにしています。

それと、マクロの定義を削除するステートメントを良く忘れるのでメモしておきます。%SYSMACDELETE で定義したマクロを削除します。AUTOEXECの中で使うけど最後には後始末しておきたいときに使います。

%macro _uname();
  %local n;
  %let n=%sysfunc(monotonic());
  _LN%sysfunc(putn(&n, z5.))
%mend;

%put %_uname;

2022年10月25日火曜日

CPORTで移送ファイルを作成と圧縮

CPORTで移送ファイルを作成するときに圧縮できることを実験で確認した。移送ファイル形式は圧縮されてはいるが圧縮率は高くない。なので、FILENAMEステートメントでZIPの圧縮を試してみた。圧縮した移送ファイルからCIMPORTできて /tmp/new のディレクトリにもSASファイルがあることを確認できた。。

filename myfile1 '/tmp/foo.xpt';

proc cport lib=sashelp file=myfile1 memtype=data;
   select air baseball class;
run;

filename myfile2 zip "/tmp/foo.xpt.zip";

proc cport lib=sashelp file=myfile2 memtype=data;
   select air baseball class;
run;

libname newlib '/tmp/new';

proc cimport lib=newlib infile=myfile2;
run;

2022年10月14日金曜日

本番環境とテスト環境の差で問題が再現しないときの1つの手段

 リッチな本番環境だと問題が起きるが、開発環境だと問題が起きにくいというのはよくある。本番のデータは大きいが開発環境はダミーで小さなものしかない。そんなときは、メモリとかのパラメータをわざと低くしたりすると再現しやすくなる。メモリが不足していると辺りが付いていたら使える手です。

追記: タイムアウト系のエラーは、起動のスクリプトに sleep を入れることで再現させることができます。例えば、Workspace Serverの起動のタイムアウトは *_usermods.sh にシェルのコマンド sleep 120; とか入れると再現できます。

負荷が高いというのは Linuxであれば stress(1) とかで疑似的な負荷を掛ける手もあります。負荷掛けると狙っているのとは別なエラーも出るので見極め必要です。

2022年10月12日水曜日

PROC HTTPのクエリパラメータのエンコーディング

クエリパラーメータを渡すときにエンコーディングが面倒だと思っていたら、PROC HTTPのQUERYのオプションで自動的にエンコードしてくれることに気が付いた。REST APIで日本語の名前でフィルタするときに使える。

%let name=%str(負荷テスト);
proc http url="https://%sysget(SAS_JOB_FLOW_SCHEDULING_SERVICE_HOST):443/jobFlowScheduling/flows" 
    query=("filter"="eq(name,'&name')")
    out=outjson oauth_bearer=sas_services;
  headers 'Accept'='application/vnd.sas.collection+json';
run;

2022年9月21日水曜日

SAS/ACCESSでIn-Database処理になるか?確認する方法

 SAS/ACCESSのデータベースを乗り換えたときに、WHERE句の関数がプッシュダウンされているか知りたいという問合せがあった。DBの種類にもよるかもしれないが、OBS=1 を指定すると SQLにLMIT=1が効く場合がある。これでSASログを確認してプッシュダウンされない部分を特定していく。

options sastrace=',,t,d' sastraceloc=saslog stsuffix obs=1;
proc sql;
  create table x as select * from hoge.xyz;
quit;
options sastrace=off obs=max;